Home Reference Source Repository

src/KeyedApi.js

import has from "lodash.has";
import {
	assert,
	doneIterator,
	iteratorFor,
	iterateOver,
} from "akutils";


/**
	API implementation for properties that support keyed access to child properties.

	@see {@link MapProperty}
	@see {@link ObjectProperty}
*/
export default class KeyedApi {
	constructor(property) {
		this._property = property;
	}

	impl() {
		return this._property.__();
	}

	isActive() {
		return this._property.isActive();
	}

	shadow() {
		return this._property._();
	}

	addProperty(name, property, automount) {
		property.setParent(this._property);

		return this.addPropertyShader(name, property.shader(), property.getInitialState(), automount);
	}

	addPropertyType(name, stateType) {
		return this.addPropertyShader(name, stateType.factory(this._property), stateType._initialState);
	}

	addPropertyShader(name, shader, initialState, automount) {
		const property = this._property;
		const propInitialState = property.initialState();

		// initial state has two sources: parameter and the shader
		const iState = initialState !== undefined
				?initialState
				:shader.initialState ?shader.initialState :propInitialState && propInitialState[name];

		property.shader().add(name, shader, automount);

		if (this.isActive()) {
			if (iState !== undefined) {
				this.set(name, iState);
			}

			property.touch();
		} else if (propInitialState) {
			propInitialState[name] = iState;
		}

		// return shader so can be further customized
		return shader;
	}

	removeProperty(name) {
		this._property.shader().remove(name);
		this.delete(name);
	}


	//------------------------------------------------------------------------------------------------------
	// Map and other useful data structure functions
	//------------------------------------------------------------------------------------------------------

	/*
		Converting map size property to a function in keeping with current design philosophy of minimizing
		internal object properties in favor of methods.
	*/
	size() {
		return this.impl().size();
	}

	clear() {
		if (this.isActive()) {
			this.impl().clear();
		}
	}

	delete(key) {
		if (this.isActive()) {
			const value = this.shadow()[key];

			// need to perform action even if value is undefined because could be a queued action
			// to add it.
			this.impl().delete(key);

			return value;
		}
	}

	entries() {
		if (!this.isActive()) { return doneIterator; }

		return iterateOver(this.keysArray(), key => [key, this.get(key)] );
	}

	filter(callback, context) {
		const keys = this.keysArray();
		const shadow = this.shadow();
		const acc = [];
		var key, value;

		for (let i=0, len=keys.length; i<len; i++) {
			key = keys[i];
			value = shadow[key];

			if (callback.call(context, value, key, shadow)) {
				acc.push(value);
			}
		}

		return acc;
	}

	get(key) {
		if (this.isActive()) {
			return this.shadow()[key];
		}
	}

	has(key) {
		if (this.isActive()) {
			return has(this.shadow(), key);
		}
	}

	keys() {
		if (!this.isActive()) { return doneIterator; }

		return iteratorFor(this.keysArray());
	}

	keysArray() {
		if (!this.isActive()) { return doneIterator }

		// use Object.keys() so do not get non-enumerable properties
		return Object.keys(this.shadow());
	}

	set(key, value) {
		if (this.isActive()) {
			this.impl().set(key, value);
		}
	}

	valuesArray() {
		if (!this.isActive()) { return doneIterator; }

		const keys = this.keysArray();
		const values = [];

		for (let i=0, len=keys.length; i<len; i++) {
			values.push( this[keys[i]] );
		}

		return values;
	}

	values() {
		if (!this.isActive()) { return doneIterator; }

		return iterateOver(this.keysArray(), key => this.get(key));
	}

	[Symbol.iterator]() { return this.entries() }
}