const negate = require('lodash/negate'); // Coerces an a parameter into a callback for matching elements. // This accepts an element name, an element type and returns a // callback to match for those elements. function coerceElementMatchingCallback(value) { // Element Name if (typeof value === 'string') { return element => element.element === value; } // Element Type if (value.constructor && value.extend) { return element => element instanceof value; } return value; } /** * @class * * @param {Element[]} elements * * @property {Element[]} elements */ class ArraySlice { constructor(elements) { this.elements = elements || []; } /** * @returns {Array} */ toValue() { return this.elements.map(element => element.toValue()); } // High Order Functions /** * @param callback - Function to execute for each element * @param thisArg - Value to use as this (i.e the reference Object) when executing callback * @returns {array} A new array with each element being the result of the callback function */ map(callback, thisArg) { return this.elements.map(callback, thisArg); } /** * Maps and then flattens the results. * @param callback - Function to execute for each element. * @param thisArg - Value to use as this (i.e the reference Object) when executing callback * @returns {array} */ flatMap(callback, thisArg) { return this .map(callback, thisArg) .reduce((a, b) => a.concat(b), []); } /** * Returns an array containing the truthy results of calling the given transformation with each element of this sequence * @param transform - A closure that accepts an element of this array as its argument and returns an optional value. * @param thisArg - Value to use as this (i.e the reference Object) when executing callback * @memberof ArraySlice.prototype * @returns An array of the non-undefined results of calling transform with each element of the array */ compactMap(transform, thisArg) { const results = []; this.forEach((element) => { const result = transform.bind(thisArg)(element); if (result) { results.push(result); } }); return results; } /** * @param callback - Function to execute for each element. This may be a callback, an element name or an element class. * @param thisArg - Value to use as this (i.e the reference Object) when executing callback * @returns {ArraySlice} * @memberof ArraySlice.prototype */ filter(callback, thisArg) { callback = coerceElementMatchingCallback(callback); return new ArraySlice(this.elements.filter(callback, thisArg)); } /** * @param callback - Function to execute for each element. This may be a callback, an element name or an element class. * @param thisArg - Value to use as this (i.e the reference Object) when executing callback * @returns {ArraySlice} * @memberof ArraySlice.prototype */ reject(callback, thisArg) { callback = coerceElementMatchingCallback(callback); return new ArraySlice(this.elements.filter(negate(callback), thisArg)); } /** * Returns the first element in the array that satisfies the given value * @param callback - Function to execute for each element. This may be a callback, an element name or an element class. * @param thisArg - Value to use as this (i.e the reference Object) when executing callback * @returns {Element} * @memberof ArraySlice.prototype */ find(callback, thisArg) { callback = coerceElementMatchingCallback(callback); return this.elements.find(callback, thisArg); } /** * @param callback - Function to execute for each element * @param thisArg - Value to use as this (i.e the reference Object) when executing callback * @memberof ArraySlice.prototype */ forEach(callback, thisArg) { this.elements.forEach(callback, thisArg); } /** * @param callback - Function to execute for each element * @param initialValue * @memberof ArraySlice.prototype */ reduce(callback, initialValue) { return this.elements.reduce(callback, initialValue); } /** * @param value * @returns {boolean} * @memberof ArraySlice.prototype */ includes(value) { return this.elements.some(element => element.equals(value)); } // Mutation /** * Removes the first element from the slice * @returns {Element} The removed element or undefined if the slice is empty * @memberof ArraySlice.prototype */ shift() { return this.elements.shift(); } /** * Adds the given element to the begining of the slice * @parameter {Element} value * @memberof ArraySlice.prototype */ unshift(value) { this.elements.unshift(this.refract(value)); } /** * Adds the given element to the end of the slice * @parameter {Element} value * @memberof ArraySlice.prototype */ push(value) { this.elements.push(this.refract(value)); return this; } /** * @parameter {Element} value * @memberof ArraySlice.prototype */ add(value) { this.push(value); } // Accessors /** * @parameter {number} index * @returns {Element} * @memberof ArraySlice.prototype */ get(index) { return this.elements[index]; } /** * @parameter {number} index * @memberof ArraySlice.prototype */ getValue(index) { const element = this.elements[index]; if (element) { return element.toValue(); } return undefined; } /** * Returns the number of elements in the slice * @type number */ get length() { return this.elements.length; } /** * Returns whether the slice is empty * @type boolean */ get isEmpty() { return this.elements.length === 0; } /** * Returns the first element in the slice or undefined if the slice is empty * @type Element */ get first() { return this.elements[0]; } } if (typeof Symbol !== 'undefined') { ArraySlice.prototype[Symbol.iterator] = function symbol() { return this.elements[Symbol.iterator](); }; } module.exports = ArraySlice;