Home Reference Source Repository

src/IsolatedProperty.js

import { assert, uuid } from "akutils";

import IsolatedAccess from "./IsolatedAccess";
import IsolatedShadow from "./IsolatedShadow";
import IsolatedShadowImpl from "./IsolatedShadowImpl";
import KeyedApi from "./KeyedApi";
import MapProperty from "./MapProperty";
import Property from "./Property";
import StateType from "./StateType";


// child property names
export const _dataKey = "data";

// private instance variable symbols
const _id = Symbol("id");
const _key = Symbol("key");
const _owner = Symbol("owner");


/**

*/
export default class IsolatedProperty extends Property {
	/**
		If a {@link StateType} is not passed to this constructor then one is located using
		{@link StateType.from} thus ensuring the f.lux shadowing process is defined for this
		property.

		@param {StateType} [stateType] - a specialized {@link StateType} instance describing how f.lux should
			shadow this property.
	*/
	constructor(stateType) {
		super(stateType);

		assert( a => a.is(stateType.getManagedType(), "Managed type must be set") );

		this._keyed = new KeyedApi(this);

		this.setImplementationClass(IsolatedShadowImpl);
		this.setShadowClass(IsolatedShadow);

		const managedType = this.stateType().getManagedType();
		const shader = this.shader();

		shader.addProperty(_dataKey, managedType);

		this[_id] = uuid("_");
	}

	/**
		Factory function for creating an `MapProperty` subclass suitable for using with new. The generated
		class will have the `type` {@link StateType} descriptor set upon return.

		Example usage:
		```
		class SomeShadow extends MapShadow {
		    // definition here
		}

		export default MapProperty.createClass(SomeShadow, type => {
			// configure type variable
		});
		```

		@param {Object|IsolatedShadow} [shadowType={}] - `MapShadow` subclass or object literal api definition.
			If object literal specified, each property and function is mapped onto a Shadow subclass.
		@param {function(type: StateType)} [typeCallback] - a callback function that will be passed the
			{@link StateType} spec for additional customization, such as setting autoshadow, initial state,
			or readonly.
		@param {Object} [initialState={}] - the initial state for the new property.

		@return {MapProperty} newly defined `MapProperty` subclass.
	*/
	static createClass(shadowType={}, typeCallback, initialState={}) {
		return createPropertyClass(shadowType, initialState, typeCallback, IsolatedProperty);
	}

	/**
		Factory function for setting up the {@link StateType} `type` class variable with an appropriately
		configured intial state.

		Example usage:
		```
        export default class SomeProperty extends MapProperty {
			// implement property here
        }

        class SomeShadow extends MapShadow {
			// implement shadow api here
        }

        IsolatedProperty.defineType(SomeProperty, SomeShadow, type => {
			// configure type variable
        });
		```

		@param {MapProperty} PropClass - MapProperty subclass
		@param {Object|MapShadow} [ShadowType] - `MapShadow` subclass or object literal api definition.
			If object literal specified, each property and function is mapped onto a Shadow subclass.
		@param {function(type: StateType)} [typeCallback] - a callback function that will be passed the
			{@link StateType} spec for additional customization, such as setting autoshadow, initial state, or
			readonly.
		@param {Object} [initialState={}] - the initial state for the new property.
	*/
	static defineType(PropClass, ShadowType, typeCallback, initialState={}) {
		assert( a => a.is(IsolatedProperty.isPrototypeOf(PropClass), "PropClass must subclass IsolatedProperty") );

		StateType.defineTypeEx(PropClass, ShadowType, typeCallback, initialState);
	}


	//------------------------------------------------------------------------------------------------------
	// Life-cycle methods
	//------------------------------------------------------------------------------------------------------

	propertyChildInvalidated(childProperty, sourceProperty) {
		this.store().isolated().invalidated(this);
	}

	propertyWillUnshadow() {
		this.store().isolated().willUnshadow(this);
	}


	//------------------------------------------------------------------------------------------------------
	// Isolated specific methods
	//------------------------------------------------------------------------------------------------------

	/**
		Gets the ID used by the store to track this isolated property.

		@return {string}
	*/
	isolationId() {
		return this[_id];
	}

	key() {
		return this[_key];
	}

	/**
		Gets the property managing this isolated object.

		@return {Property}
	*/
	owner() {
		return this[_owner];
	}

	setKey(key) {
		this[_key] = key;
	}

	/**
		Sets the property that owns/contains this isolated property.

		@param {Property} owner - the object containing/managing this isolated property.
	*/
	setOwner(owner) {
		assert( a => a.not(this[_owner], "Owner already set") );

		this[_owner] = owner;
	}


	//------------------------------------------------------------------------------------------------------
	// Property class overrides - no need to call super
	//
	// These are mostly proxies to the owner property
	//------------------------------------------------------------------------------------------------------

	/**
		Creates the object to be assigned to the shadow.$ property. Subclasses can override this method
		to setup a chain of specialized accessors (`$()`). See {@link Access} for details on setting up
		specialized accessors. This is an advanced feature and rarely required.

		@return {Access} a property accessor instance.
	*/
	create$(impl) {
		return new IsolatedAccess(impl);
	}

	/**
		An isolated property returns owner's `dotPath()` value.

		@return {string} path with each component joined by a `.`

		@see https://lodash.com/docs/4.17.4#result
	*/
	dotPath() {
console.log("ISO path", this[_owner].path())
		return this[_owner].dotPath();
	}

	/**
		An isolated property returns owner's `isActive()` value.

		@return {boolean}
	*/
	isActive() {
		return this[_owner].isActive();
	}

	/**
		Gets if property is an actual isolated property managed by the store.

		@return {boolean}
	*/
	isIsolated() {
		return true;
	}

	/**
		An isolated property is never a root.

		@return {boolean} `false`
	*/
	isRoot() {
		return false;
	}

	/**
		An isolated property returns owner property for the parent.

		@return {[]}
	*/
	parent() {
		return this[_owner];
	}


	//------------------------------------------------------------------------------------------------------
	// Property class overrides - the tricky ones
	//
	// Developer Note: Use great caution when overriding these methods as not their original intent
	//------------------------------------------------------------------------------------------------------

	/**
		An isolated property returns owner's `isReadonly()` value.

		@return {boolean}
	*/
	isReadonly() {
		const explicit = this.readonlyExplicit();
		const readonly = explicit !== undefined ?explicit :this.stateType()._readonly;

		// use readonly flag if explicitly set otherwise use value from owner
		return readonly === undefined
			? this[_owner].isReadonly()
			: readonly;
	}
}


StateType.defineType(IsolatedProperty, spec => {
	spec.initialState({})
		.typeName("IsolatedProperty")
});