Home Reference Source Repository

src/IndexedShadow.js

import has from "lodash.has";
import sortBy from "lodash.sortby";

import Shadow from "./Shadow";


const _cache = Symbol("_cache");
const _valuesArray = Symbol('_valuesArray');


/**
	Default shadow api for the `IndexedProperty`. Exposes the non-mutating methods of the
	{@link Array} interface.

	@see {@link IndexedProperty}
*/
export default class IndexedShadow extends Shadow {
	constructor(impl) {
		super(impl);

		this[_cache] = {};
	}

	get length() {
		return this.__().length;
	}

	//------------------------------------------------------------------------------------------------------
	//	Read-only array methods
	//------------------------------------------------------------------------------------------------------

	/*
		Alias for valuesArray().
	*/
	all() {
		return this.valuesArray();
	}

	concat(...values) {
		return this.valuesArray().concat(...values);
	}

	every(pred, context) {
		for (let i=0, len=this.length; i<len; i++) {
			if (!pred.call(context, this[i], i, this)) {
				return false;
			}
		}

		return true;
	}

	filter(callback, context) {
		const acc = [];

		for (let i=0, len=this.length; i<len; i++) {
			if (callback.call(context, this[i], i, this)) {
				acc.push(this[i]);
			}
		}

		return acc;
	}

	find(pred, context) {
		for (let i=0, len=this.length; i<len; i++) {
			if (pred.call(context, this[i], i, this)) {
				return this[i];
			}
		}
	}

	findIndex(pred, context) {
		for (let i=0, len=this.length; i<len; i++) {
			if (pred.call(context, this[i], i, this)) {
				return i;
			}
		}

		return -1;
	}

	forEach(callback, context) {
		for (let i=0, len=this.length; i<len; i++) {
			callback.call(context, this[i], i, this);
		}
	}

	groupBy(callback, context) {
		var result = { };

		for (let i=0, len=this.length; i<len; i++) {
			const prop = this[i];
			const key = callback.call(context, prop);

			if (has(result, key)) {
				result[key].push(prop);
			} else {
				result[key] = [ prop ];
			}
		}

		return result;
	}

	includes(searchElement, fromIndex=0) {
		return this.indexOf(searchElement, fromIndex) != -1;
	}

	indexOf(value, fromIndex=0) {
		const fromIdx = Math.min(fromIndex, this.length);

		for (let i=fromIdx, len=this.length; i<len; i++) {
			if (this[i] === value) {
				return i;
			}
		}

		return -1;
	}

	join(separator=',') {
		return this.__().state().join(separator);
	}

	lastIndexOf(value, fromIndex=this.length-1) {
		if (!this.length) { return -1; }

		for (let i=Math.min(fromIndex, this.length-1); i>=0; i--) {
			if (this[i] === value) {
				return i;
			}
		}

		return -1;
	}

	map(callback, context) {
		const acc = [];

		for (let i=0, len=this.length; i<len; i++) {
			acc.push(callback.call(context, this[i], i, this));
		}

		return acc;
	}

	reduce(callback, acc, context) {
		for (let i=0, len=this.length; i<len; i++) {
			acc = callback.call(context, acc, this[i], i, this);
		}

		return acc;
	}

	some(pred, context) {
		for (let i=0, len=this.length; i<len; i++) {
			if (pred.call(context, this[i], i, this)) {
				return true;
			}
		}

		return false;
	}

	sort(callback) {
		const values = this.valuesArray();

		return values.sort(callback);
	}

	sortBy(...iteratee) {
		const values = this.valuesArray();

		return sortBy.apply(null, [values, ...iteratee]);
	}

	/*
		The values() method returns a new Array Iterator object that contains the values for each index in the array.
	*/
	values() {
		var i = 0;
		const length = this.length;
		const next = () => i<length ?{ value: this[i++], done: false } :{ done: true };

		return {
			next: next,
			[Symbol.iterator]() { return { next: next } }
		}
	}

	valuesArray() {
		if (!this[_cache][_valuesArray]) {
			var values = [];

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

			this[_cache][_valuesArray] = values;
		}

		return this[_cache][_valuesArray];
	}

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