hyperflow
Version:
A javascript state flow and mutation management toolkit & library for developing universal app.
1,256 lines (1,182 loc) • 63.4 kB
JavaScript
/**
* Copyright 2015-present Tuan Le.
*
* Licensed under the MIT License.
* You may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://opensource.org/licenses/mit-license.html
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*------------------------------------------------------------------------
*
* @module CommonElement
* @description - Common element module which provides common methods that will be used
* throughout Hf toolkit.
*
* @author Tuan Le (tuan.t.lei@gmail.com)
*
* @flow
*/
'use strict'; // eslint-disable-line
const PRIVATE_PREFIX = `_`;
let DEVELOPMENT = true;
let LOGGING = {
info0: true,
info1: true,
warn0: true,
warn1: true,
historySize: 500
};
if (typeof window === `object` && window.hasOwnProperty(`DEVELOPMENT`)) {
DEVELOPMENT = window.DEVELOPMENT;
} else if (typeof process === `object` && process.hasOwnProperty(`env`)) {
if (typeof process.env === `object` && process.env.hasOwnProperty(`NODE_ENV`)) { // eslint-disable-line
DEVELOPMENT = process.env.NODE_ENV === `development`; // eslint-disable-line
}
}
if (typeof window === `object`) {
LOGGING = {
info0: window.hasOwnProperty(`LOGGING_INFO0`) ? window.LOGGING_INFO0 : LOGGING.info0,
info1: window.hasOwnProperty(`LOGGING_INFO1`) ? window.LOGGING_INFO1 : LOGGING.info1,
warn0: window.hasOwnProperty(`LOGGING_WARN0`) ? window.LOGGING_WARN0 : LOGGING.warn0,
warn1: window.hasOwnProperty(`LOGGING_WARN1`) ? window.LOGGING_WARN1 : LOGGING.warn1,
historySize: window.hasOwnProperty(`LOGGING_HISTORY_SIZE`) ? window.LOGGING_HISTORY_SIZE : LOGGING.historySize
};
} else if (typeof process === `object` && process.hasOwnProperty(`env`)) {
if (typeof process.env === `object`) { // eslint-disable-line
LOGGING = {
info0: process.env.hasOwnProperty(`LOGGING_INFO0`) ? process.env.LOGGING_INFO0 === `true` : LOGGING.info0, // eslint-disable-line
info1: process.env.hasOwnProperty(`LOGGING_INFO1`) ? process.env.LOGGING_INFO1 === `true` : LOGGING.info1, // eslint-disable-line
warn0: process.env.hasOwnProperty(`LOGGING_WARN0`) ? process.env.LOGGING_WARN0 === `true` : LOGGING.warn0, // eslint-disable-line
warn1: process.env.hasOwnProperty(`LOGGING_WARN1`) ? process.env.LOGGING_WARN1 === `true` : LOGGING.warn1, // eslint-disable-line
historySize: process.env.hasOwnProperty(`LOGGING_HISTORY_SIZE`) ? parseInt(process.env.LOGGING_HISTORY_SIZE) : LOGGING.historySize // eslint-disable-line
};
}
}
/**
* @description - Common element prototype object.
*
* CommonElementPrototype
*/
const CommonElementPrototype = Object.create({}).prototype = {
/* ----- Common Prototype Definitions --------------------- */
/**
* @description - Helper function to compare and verify object schema.
*
* @method _deepCompareSchema
* @param {object} schema - Predefined schema.
* @param {object} target - Predefined schema.
* @returns {object}
* @private
*/
_deepCompareSchema (schema, target) {
const Hf = this;
let verified = true;
if (Hf.isObject(schema) && Hf.isObject(target)) {
Hf.forEach(schema, (schemaItem, key) => {
if (verified) {
let itemTypes = [];
if (target.hasOwnProperty(key) || Object.getPrototypeOf(target).hasOwnProperty(key)) {
const targetItem = target[key];
if ((Hf.isObject(targetItem) && Hf.isObject(schemaItem)) || (Hf.isArray(targetItem) && Hf.isArray(schemaItem))) {
verified = Hf._deepCompareSchema(schemaItem, targetItem);
} else if (Hf.isString(schemaItem)) {
itemTypes = Hf.stringToArray(schemaItem, `|`);
verified = itemTypes.some((itemType) => {
if (itemType === `defined`) {
return Hf.isDefined(targetItem);
}
return Hf.typeOf(targetItem) === itemType;
});
} else {
verified = false;
}
} else {
if (Hf.isString(schemaItem)) {
itemTypes = Hf.stringToArray(schemaItem, `|`);
verified = itemTypes.includes(`undefined`);
} else {
verified = false;
}
}
}
});
} else if (Hf.isArray(schema) && Hf.isArray(target)) {
if (schema.length === 1) {
const [ schemaItem ] = schema;
verified = target.reduce((_verified, targetItem) => {
let itemTypes = [];
if ((Hf.isObject(targetItem) && Hf.isObject(schemaItem)) || (Hf.isArray(targetItem) && Hf.isArray(schemaItem))) {
_verified = Hf._deepCompareSchema(schemaItem, targetItem);
} else if (Hf.isString(schemaItem)) {
itemTypes = Hf.stringToArray(schemaItem, `|`);
_verified = itemTypes.some((itemType) => {
if (itemType === `defined`) {
return Hf.isDefined(targetItem);
}
return Hf.typeOf(targetItem) === itemType;
});
} else {
_verified = false;
}
return _verified;
}, verified);
} else {
Hf.log(`warn1`, `CommonElement._deepCompareSchema - Predefined schema test array must have a length of 1.`);
verified = false;
}
} else {
verified = false;
}
return verified;
},
/**
* @description - Helper function to return a new object that was deep mutated from source by reference target mutator object.
*
* @method _deepMutation
* @param {object} source - Target source object.
* @param {object} mutator - Mutator object.
* @param {array} pathId - Mutation path Id.
* @returns {object}
* @private
*/
_deepMutation (source, mutator, pathId = []) {
const Hf = this;
let result;
if (Hf.DEVELOPMENT) {
if (!(Hf.isArray(pathId))) {
Hf.log(`error`, `CommonElement._deepMutation - Input pathId is invalid.`);
}
}
if (Hf.isEmpty(pathId)) {
if (Hf.isObject(source) && Hf.isObject(mutator)) {
result = Object.assign({}, source);
// result = { ...source };
const sourceKeys = Object.keys(source);
const mutatorKeys = Object.keys(mutator);
if (sourceKeys.length >= mutatorKeys.length && mutatorKeys.every((key) => sourceKeys.includes(key))) {
mutatorKeys.forEach((key) => {
const sourceItem = source[key];
const mutatorItem = mutator[key];
if ((Hf.isObject(sourceItem) && !Hf.isObject(mutatorItem) || Hf.isArray(sourceItem) && !Hf.isArray(mutatorItem)) ||
(!Hf.isObject(sourceItem) && Hf.isObject(mutatorItem) || !Hf.isArray(sourceItem) && Hf.isArray(mutatorItem))) {
Hf.log(`warn1`, `CommonElement._deepMutation - Input mutator schema at key:${key} must be a subset of source schema.`);
Hf.log(`debug`, `CommonElement._deepMutation - sourceItem:${JSON.stringify(sourceItem, null, `\t`)}`);
Hf.log(`debug`, `CommonElement._deepMutation - mutatorItem:${JSON.stringify(mutatorItem, null, `\t`)}`);
} else {
if (Hf.isObject(sourceItem) && Hf.isObject(mutatorItem) || Hf.isArray(sourceItem) && Hf.isArray(mutatorItem)) {
result[key] = Hf._deepMutation(sourceItem, mutatorItem);
} else {
result[key] = mutatorItem;
}
}
});
} else {
Hf.log(`warn1`, `CommonElement._deepMutation - Input mutator object schema is not a subset of the source schema.`);
Hf.log(`debug`, `CommonElement._deepMutation - source:${JSON.stringify(source, null, `\t`)}`);
Hf.log(`debug`, `CommonElement._deepMutation - mutator:${JSON.stringify(mutator, null, `\t`)}`);
}
} else if (Hf.isArray(source) && Hf.isArray(mutator)) {
result = source.slice(0);
if (source.length === mutator.length) {
source.forEach((sourceItem, key) => {
const mutatorItem = mutator[key];
if ((Hf.isObject(sourceItem) && !Hf.isObject(mutatorItem) || Hf.isArray(sourceItem) && !Hf.isArray(mutatorItem)) ||
(!Hf.isObject(sourceItem) && Hf.isObject(mutatorItem) || !Hf.isArray(sourceItem) && Hf.isArray(mutatorItem))) {
Hf.log(`warn1`, `CommonElement._deepMutation - Input mutator schema at key:${key} must be a subset of source schema.`);
Hf.log(`debug`, `CommonElement._deepMutation - sourceItem:${JSON.stringify(sourceItem, null, `\t`)}`);
Hf.log(`debug`, `CommonElement._deepMutation - mutatorItem:${JSON.stringify(mutatorItem, null, `\t`)}`);
} else {
if (Hf.isObject(sourceItem) && Hf.isObject(mutatorItem) || Hf.isArray(sourceItem) && Hf.isArray(mutatorItem)) {
result[key] = Hf._deepMutation(sourceItem, mutatorItem);
} else {
result[key] = mutatorItem;
}
}
});
} else {
Hf.log(`warn1`, `CommonElement._deepMutation - Input mutator array must be the same size as the source array.`);
Hf.log(`debug`, `CommonElement._deepMutation - source:${JSON.stringify(source, null, `\t`)}`);
Hf.log(`debug`, `CommonElement._deepMutation - mutator:${JSON.stringify(mutator, null, `\t`)}`);
}
} else {
Hf.log(`error`, `CommonElement._deepMutation - Input source or target mutator is invalid.`);
}
} else {
const key = pathId.shift();
if (Hf.isObject(source) && source.hasOwnProperty(key)) {
result = Object.assign({}, source);
// result = { ...source };
if (Hf.isEmpty(pathId)) {
if (Hf.isObject(mutator) && mutator.hasOwnProperty(key)) {
result[key] = Hf._deepMutation(source[key], mutator[key], pathId.slice(0));
} else {
Hf.log(`warn1`, `CommonElement._deepMutation - Key:${key} of path Id:${pathId} is not defined in mutator.`);
Hf.log(`debug`, `CommonElement._deepMutation - source:${JSON.stringify(source, null, `\t`)}`);
Hf.log(`debug`, `CommonElement._deepMutation - mutator:${JSON.stringify(mutator, null, `\t`)}`);
}
} else {
result[key] = Hf._deepMutation(source[key], mutator, pathId.slice(0));
}
} else if (Hf.isArray(source) && Hf.isInteger(key) && key < source.length) {
result = source.slice(0);
if (Hf.isEmpty(pathId)) {
if (Hf.isArray(mutator) && key < mutator.length) {
result[key] = Hf._deepMutation(source[key], mutator[key], pathId.slice(0));
} else {
Hf.log(`warn1`, `CommonElement._deepMutation - Array index:${key} is greater than mutator array size.`);
Hf.log(`debug`, `CommonElement._deepMutation - source:${JSON.stringify(source, null, `\t`)}`);
Hf.log(`debug`, `CommonElement._deepMutation - mutator:${JSON.stringify(mutator, null, `\t`)}`);
}
} else {
result[key] = Hf._deepMutation(source[key], mutator, pathId.slice(0));
}
} else {
Hf.log(`error`, `CommonElement._deepMutation - Path ends at property key:${key}.`);
}
}
return result;
},
/**
* @description - Helper function to deep merge source with target and return result.
*
* @method _deepMerge
* @param {object} source - Source object.
* @param {object} target - Target object.
* @param {array} pathId - Merge at path Id.
* @returns {object}
* @private
*/
_deepMerge (source, target, pathId = []) {
const Hf = this;
let result;
if (Hf.DEVELOPMENT) {
if (!(Hf.isObject(source) || Hf.isArray(source)) &&
!(Hf.isObject(target) || Hf.isArray(target))) {
Hf.log(`error`, `CommonElement._deepMerge - Input source or mutation is invalid.`);
}
}
pathId = Hf.isArray(pathId) ? pathId : [];
if (Hf.isEmpty(pathId)) {
if (Hf.isArray(source) && Hf.isArray(target)) {
result = source.slice(0);
target.forEach((item, key) => {
if (!Hf.isDefined(result[key])) {
result[key] = item;
} else if (Hf.isObject(item)) {
result[key] = Hf._deepMerge(source[key], item);
} else {
if (!source.includes(item)) {
result.push(item);
}
}
});
} else {
if (Hf.isObject(source)) {
result = Object.assign({}, source);
// result = { ...source };
}
Object.entries(target).forEach(([ key, targetValue ]) => {
if (Hf.isObject(targetValue) || Hf.isArray(targetValue)) {
if (!Hf.isDefined(source[key])) {
result[key] = targetValue;
} else {
result[key] = Hf._deepMerge(source[key], targetValue);
}
} else {
result[key] = targetValue;
}
});
}
} else {
const key = pathId.shift();
if (Hf.isObject(source) && source.hasOwnProperty(key)) {
result = Object.assign({}, source);
// result = { ...source };
if (Hf.isEmpty(pathId)) {
if (Hf.isObject(target) && target.hasOwnProperty(key)) {
result[key] = Hf._deepMerge(source[key], target[key], pathId.slice(0));
}
} else {
result[key] = Hf._deepMerge(source[key], target, pathId.slice(0));
}
} else if (Hf.isArray(source) && Hf.isInteger(key) && key < source.length) {
result = source.slice(0);
if (Hf.isEmpty(pathId)) {
if (Hf.isArray(target) && key < target.length) {
result[key] = Hf._deepMerge(source[key], target[key], pathId.slice(0));
}
} else {
result[key] = Hf._deepMerge(source[key], target, pathId.slice(0));
}
} else {
Hf.log(`error`, `CommonElement._deepMerge - Path ends at property key:${key}.`);
}
}
return result;
},
/**
* @description - Helper function to do compare and fallback if mismatched.
*
* @method _deepCompareAndFallback
* @param {object} source - Source object.
* @param {object} target - Target object.
* @param {function} notify - Optional notification callback when a fallback occurs.
* @returns {object}
* @private
*/
_deepCompareAndFallback (source, target, notify) {
const Hf = this;
let result;
if (Hf.isObject(source) && Hf.isObject(target)) {
result = Hf.clone(target);
Hf.forEach(source, (sourceItem, key) => {
if (target.hasOwnProperty(key)) {
const targetItem = target[key];
if ((Hf.isObject(targetItem) && Hf.isObject(sourceItem)) || (Hf.isArray(targetItem) && Hf.isArray(sourceItem))) {
result[key] = Hf._deepCompareAndFallback(sourceItem, targetItem, notify);
} else {
if (Hf.typeOf(targetItem) !== Hf.typeOf(sourceItem)) {
result[key] = sourceItem;
if (Hf.isFunction(notify)) {
notify(key);
}
}
}
} else {
result[key] = sourceItem;
if (Hf.isFunction(notify)) {
notify(key);
}
}
});
} else if (Hf.isArray(source) && Hf.isArray(target)) {
result = Hf.clone(target);
Hf.forEach(source, (sourceItem, key) => {
if (key >= 0 && key < target.length) {
const targetItem = target[key];
if ((Hf.isObject(targetItem) && Hf.isObject(sourceItem)) || (Hf.isArray(targetItem) && Hf.isArray(sourceItem))) {
result[key] = Hf._deepCompareAndFallback(sourceItem, targetItem, notify);
} else {
if (Hf.typeOf(targetItem) !== Hf.typeOf(sourceItem)) {
result[key] = sourceItem;
if (Hf.isFunction(notify)) {
notify(key);
}
}
}
} else {
result.push(sourceItem);
if (Hf.isFunction(notify)) {
notify(key);
}
}
});
} else {
Hf.log(`error`, `CommonElement._deepCompareAndFallback - Input source or target object is invalid.`);
}
return result;
},
/**
* @description - Helper function to recursively parsing through and retrieve an object property at pathId.
*
* @method _deepRetrieval
* @param {object} target - Target object to retrieve property.
* @param {array} pathId - Retrival path Id.
* @param {boolean} asNestedObject
* @returns {object}
* @private
*/
_deepRetrieval (target, pathId, asNestedObject) {
const Hf = this;
if (Hf.DEVELOPMENT) {
if (!(Hf.isObject(target) || Hf.isArray(target))) {
Hf.log(`error`, `CommonElement._deepRetrieval - Input target object or array is invalid.`);
} else if (!(Hf.isArray(pathId))) {
Hf.log(`error`, `CommonElement._deepRetrieval - Input pathId is invalid.`);
} else if (Hf.isArray(pathId) && Hf.isEmpty(pathId)) {
Hf.log(`error`, `CommonElement._deepRetrieval - No property is defined.`);
}
}
const key = pathId.shift();
let resultAtPath = Hf.isObject(target) ? {} : Array(key).fill(null);
let propertyAtPath;
if (Hf.isObject(target) && target.hasOwnProperty(key)) {
if (!Hf.isEmpty(pathId)) {
propertyAtPath = Hf._deepRetrieval(target[key], pathId.slice(0), asNestedObject);
resultAtPath[key] = propertyAtPath;
} else {
propertyAtPath = target[key];
resultAtPath[key] = propertyAtPath;
}
} else if (Hf.isArray(target) && Hf.isInteger(key) && key < target.length) {
if (!Hf.isEmpty(pathId)) {
propertyAtPath = Hf._deepRetrieval(target[key], pathId.slice(0), asNestedObject);
resultAtPath.push(propertyAtPath);
} else {
propertyAtPath = target[key];
resultAtPath.push(propertyAtPath);
}
}
if (Hf.DEVELOPMENT) {
if (!Hf.isDefined(propertyAtPath) && !Hf.isEmpty(pathId)) {
Hf.log(`error`, `CommonElement._deepRetrieval - Path ends at property key:${key}.`);
}
}
return asNestedObject ? resultAtPath : propertyAtPath;
},
/**
* @description - Check if value is an integer.
*
* @method isInteger
* @param {number} value - To be checked if it is an integer.
* @returns {boolean}
*/
isInteger (value) {
return (/^-?\d+$/.test(String(value)));
},
/**
* @description - Check if value is a float.
*
* @method isFloat
* @param {number} value - To be checked if it is a float.
* @returns {boolean}
*/
isFloat (value) {
return (/^[+-]?\d+(\.\d+)?$/.test(String(value)));
},
/**
* @description - Check if value is a number.
*
* @method isNumeric
* @param {number} value - To be checked if it is a number.
* @returns {boolean}
*/
isNumeric (value) {
return !isNaN(parseFloat(value)) && isFinite(value);
},
/**
* @description - Check if an object, array, or string is empty.
*
* @method isEmpty
* @param {object|array|string} value - To be checked if it is an empty object, array, or string.
* @returns {boolean}
*/
isEmpty (value) {
const Hf = this;
if (Hf.isObject(value)) {
return Object.getOwnPropertyNames(value).length === 0;
} else if (Hf.isArray(value) || Hf.isString(value)) {
return value.length === 0;
}
return true;
},
/**
* @description - Check for a string type.
*
* @method isString
* @param {string} str - To be checked if it is a string.
* @returns {boolean}
*/
isString (str) {
const Hf = this;
return Hf.typeOf(str) === `string` || (Hf.typeOf(str) === `object` && str.constructor === String);
},
/**
* @description - Check for a string type and is not empty.
*
* @method isNonEmptyString
* @param {string} str - To be checked if it is a string and not empty.
* @returns {boolean}
*/
isNonEmptyString (str) {
const Hf = this;
return Hf.isString(str) && !Hf.isEmpty(str);
},
/**
* @description - Check if value is a boolean.
*
* @method isBoolean
* @param value - To be checked if it is a boolean.
* @returns {boolean}
*/
isBoolean (value) {
const Hf = this;
return Hf.typeOf(value) === `boolean` || (Hf.isString(value) && (value.toLowerCase() === `true` || value.toLowerCase() === `false`));
},
/**
* @description - Check for defined type.
*
* @method isDefined
* @param {*} value - To be checked if value is defined.
* @returns {boolean}
*/
isDefined (value) {
const Hf = this;
return Hf.typeOf(value) !== `undefined`;
},
/**
* @description - Check for function type.
*
* @method isFunction
* @param {function} fn - To be checked if it is a function.
* @returns {boolean}
*/
isFunction (fn) {
return Object.prototype.toString.call(fn) === `[object Function]` || Object.prototype.toString.call(fn) === `[object AsyncFunction]`;
},
/**
* @description - Check for regex type.
*
* @method isRegEx
* @param {*} regex
* @returns {boolean}
*/
isRegEx (regex) {
return Object.prototype.toString.call(regex) === `[object RegEx]`;
},
/**
* @description - Check for date type.
*
* @method isDate
* @param {*} date
* @returns {boolean}
*/
isDate (date) {
return Object.prototype.toString.call(date) === `[object Date]`;
},
/**
* @description - Check for array type.
*
* @method isArray
* @param {array} array - To be checked if it is an array.
* @returns {boolean}
*/
isArray (array) {
return Object.prototype.toString.call(array) === `[object Array]` || Array.isArray(array) && array !== null;
},
/**
* @description - Check for an array type and is not empty.
*
* @method isNonEmptyArray
* @param {array} array - To be checked if it is an array and not empty.
* @returns {boolean}
*/
isNonEmptyArray (array) {
const Hf = this;
return Hf.isArray(array) && !Hf.isEmpty(array);
},
/**
* @description - Check for object type.
*
* @method isObject
* @param {object} obj - To be checked if it is an object.
* @returns {boolean}
*/
isObject (obj) {
const Hf = this;
return Hf.typeOf(obj) === `object` && obj === Object(obj) && !Hf.isArray(obj) && obj !== null;
},
/**
* @description - Check for an object type and is not empty.
*
* @method isNonEmptyObject
* @param {array} array - To be checked if it is an object and not empty.
* @returns {boolean}
*/
isNonEmptyObject (array) {
const Hf = this;
return Hf.isObject(array) && !Hf.isEmpty(array);
},
/**
* @description - Check object by comparing it to a predefined schema.
*
* @usage TODO: Write usage for CommonElement.isSchema method.
*
* @method isSchema
* @param {object} schema - Predefined schema.
* @returns {object}
*/
isSchema (schema) {
const Hf = this;
return {
/**
* @description - Compare schema of the target object...
*
* @prototype isSchema.of
* @param {object} target - Target object be compared with.
* @returns {boolean}
*/
of (target) {
return Hf._deepCompareSchema(schema, target);
}
};
},
/**
* @description - Get the type string of input value.
*
* @method typeOf
* @param {*} value
* @returns {string}
*/
typeOf (value) {
// FIXME: Crash occurs when value is an object with circular reference.
return ({}).toString.call(value).match(/\s([a-zA-Z]+)/)[1].toLowerCase();
},
/**
* @description - Forge and return a composed function of two or more functions.
*
* @method compose
* @param {array} fns
* @return {function}
*/
compose (...fns) {
const Hf = this;
if (Hf.DEVELOPMENT) {
if (fns.length < 2) {
Hf.log(`error`, `CommonElement.compose - Input function array must have more than two functions.`);
} else if (!fns.every((fn) => Hf.isFunction(fn))) {
Hf.log(`error`, `CommonElement.compose - Input function is invalid.`);
}
}
/**
* @description - A composed function of two or more functions.
*
* @method composed
* @param {*} value
* @returns {function}
*/
return function composed (value) {
return fns.reduce((result, fn) => {
if (Hf.isDefined(result)) {
return fn(result);
}
return fn();
}, value);
};
},
/**
* @description - Collect propteries from an object or array and return those propteries as an array.
*
* @method collect
* @param {array} pathIds
* @return {object}
*/
collect (...pathIds) {
const Hf = this;
if (Hf.DEVELOPMENT) {
if (!pathIds.every((pathId) => Hf.isString(pathId) || Hf.isArray(pathId))) {
Hf.log(`error`, `CommonElement.collect - Input pathId is invalid.`);
}
}
/**
* @description - Collect from a target object.
*
* @method collect.from
* @param {object|array} target
* @return {array}
*/
return {
from (target) {
if (Hf.DEVELOPMENT) {
if (!(Hf.isObject(target) || Hf.isArray(target))) {
Hf.log(`error`, `CommonElement.collect.from - Input target is invalid.`);
}
}
return Hf.isEmpty(pathIds) ? [] : pathIds.map((pathId) => Hf.retrieve(pathId, `.`).from(target));
}
};
},
/**
* @description - Clear all object or array.
*
* @method clear
* @param {object|array} value
* @return void
*/
clear (value) {
const Hf = this;
if (Hf.isObject(value)) {
Object.getOwnPropertyNames(value).forEach((key) => {
delete value[key];
// value[key] = undefined;
});
} else if (Hf.isArray(value)) {
value.length = 0;
} else {
Hf.log(`error`, `CommonElement.clear - Input is not an object or array type.`);
}
},
/**
* @description - Create an exact clone of an object or array.
*
* @method clone
* @param {object|array} source - Source object or array to be cloned.
* @returns {object}
*/
clone (source) {
const Hf = this;
if (Hf.DEVELOPMENT) {
if (!(Hf.isObject(source) || Hf.isArray(source))) {
Hf.log(`error`, `CommonElement.clone - Input is not an object or array type.`);
}
}
let result;
if (Hf.isObject(source)) {
result = Object.assign({}, source);
// result = { ...source };
}
if (Hf.isArray(source)) {
result = source.map((value) => {
return Hf.isObject(value) || Hf.isArray(value) ? Hf.clone(value) : value;
}).slice(0);
}
// return Object.isFrozen(source) ? Object.freeze(result) : result;
return result;
},
/**
* @description - Deep free a source object or function.
*
* @method freeze
* @param {object|function} source
* @return {object}
*/
freeze (source) {
const Hf = this;
if ((Hf.isObject(source) || Hf.isFunction(source)) && !Object.isFrozen(source)) {
Object.freeze(source);
Object.getOwnPropertyNames(source).forEach((key) => Hf.freeze(source[key]));
}
return source;
},
/**
* @description - Mutate and return an object of source that was mutated by the reference target mutator object.
* Only mutate matching property keys.
*
* @method mutate
* @param {object} source - Source object to be mutated from.
* @returns {object}
*/
mutate (source) {
const Hf = this;
if (Hf.DEVELOPMENT) {
if (!Hf.isObject(source)) {
Hf.log(`error`, `CommonElement.mutate - Input source is invalid.`);
}
}
return {
/**
* @description - Return a new mutating of source at pathId from reference mutate object...
*
* @method mutate.atPathBy
* @param {object} mutator - Target reference mutator object.
* @param {string|array} pathId - Path of the property to retrieve.
* @returns {object}
*/
atPathBy (mutator, pathId) {
pathId = Hf.isString(pathId) ? Hf.stringToArray(pathId, `.`) : pathId;
if (Hf.DEVELOPMENT) {
if (!Hf.isObject(mutator)) {
Hf.log(`error`, `CommonElement.mutate.atPathBy - Input mutator is invalid.`);
} else if (!(Hf.isArray(pathId) && !Hf.isEmpty(pathId))) {
Hf.log(`error`, `CommonElement.mutate.atPathBy - Input pathId is invalid.`);
}
}
return Hf._deepMutation(source, mutator, pathId.slice(0));
},
/**
* @description - Mutating the source from reference target mutator object...
*
* @method mutate.by
* @param {object} mutator - Target reference mutator object.
* @returns {object}
*/
by (mutator) {
if (Hf.DEVELOPMENT) {
if (!Hf.isObject(mutator)) {
Hf.log(`error`, `CommonElement.mutate.by - Input mutator is invalid.`);
}
}
return Hf._deepMutation(source, mutator);
}
};
},
/**
* @description - Deep merging source to target object.
*
* @method merge
* @param {object} source - Source object be merged from.
* @returns {object}
*/
merge (source) {
const Hf = this;
if (Hf.DEVELOPMENT) {
if (!Hf.isObject(source)) {
Hf.log(`error`, `CommonElement.merge - Input source is invalid.`);
}
}
return {
/**
* @description - Merging with the target object at pathId...
*
* @method merge.atPathWith
* @param {object} target - Target object be merged to.
* @param {string|array} pathId - Path of the property to retrieve.
* @returns {object}
*/
atPathWith (target, pathId) {
pathId = Hf.isString(pathId) ? Hf.stringToArray(pathId, `.`) : pathId;
if (Hf.DEVELOPMENT) {
if (!Hf.isObject(target)) {
Hf.log(`error`, `CommonElement.merge.atPathWith - Input target is invalid.`);
} else if (!(Hf.isArray(pathId) && !Hf.isEmpty(pathId))) {
Hf.log(`error`, `CommonElement.merge.atPathWith - Input pathId is invalid.`);
}
}
return Hf._deepMerge(source, target, pathId.slice(0));
},
/**
* @description - Merging with the target object...
*
* @method merge.with
* @param {object} target - Target object be merged to.
* @returns {object}
*/
with (target) {
if (Hf.DEVELOPMENT) {
if (!Hf.isObject(target)) {
Hf.log(`error`, `CommonElement.merge.with - Input target is invalid.`);
}
}
return Hf._deepMerge(source, target);
}
};
},
/**
* @description - Fallback to source if target does not have the same properties.
* Fallback occurs if target does not have the same property/index (name and type or index) of source.
*
* @usage TODO: Write usage for CommonElement.fallback method.
*
* @method fallback
* @param {object|array} source - Default source object or array to fallback to.
* @param {function} notify - Notify when a fallback has occured.
* @return {object}
*/
fallback (source, notify) {
const Hf = this;
if (Hf.DEVELOPMENT) {
if (!(Hf.isObject(source) || Hf.isArray(source))) {
Hf.log(`error`, `CommonElement.fallback - Input source object is invalid.`);
}
}
return {
/**
* @description - Fallback from the target object/array...
*
* @method fallback.of
* @param {object|array} target - Target object or array.
* @return {object}
*/
of (target) {
if (Hf.DEVELOPMENT) {
if ((Hf.isObject(source) && !Hf.isObject(target)) || Hf.isArray(source) && !Hf.isArray(target)) {
Hf.log(`error`, `CommonElement.fallback.of - Input target object is invalid.`);
}
}
return Hf._deepCompareAndFallback(source, target, notify);
}
};
},
/**
* @description - Mixing function that do shallow mixing and binding of source and target object or fuction to a mixed object or function.
*
* @usage TODO: Write usage for CommonElement.mix method.
*
* @method mix
* @param {object|fuction} source - Source object or function that is being extended from.
* @param {object} option - Exclusion, a list of functions or properties that should not be mixed.
* @return {object}
*/
mix (source, option = {
fnOverrided: true,
exclusion: {
prototypes: false,
properties: false,
enumerablePropertiesOnly: false,
prefixes: [ PRIVATE_PREFIX ],
postfixes: [],
keys: [],
exception: {
prefixes: [],
postfixes: [],
keys: []
}
}
}) {
const Hf = this;
if (!DEVELOPMENT) {
if (!(Hf.isObject(source) || Hf.isFunction(source))) {
Hf.log(`error`, `CommonElement.mix - Input source object or function is invalid.`);
}
}
const {
fnOverrided,
exclusion
} = Hf.fallback({
fnOverrided: true,
exclusion: {
prototypes: false,
properties: false,
enumerablePropertiesOnly: false,
prefixes: [ PRIVATE_PREFIX ],
postfixes: [],
keys: [],
exception: {
prefixes: [],
postfixes: [],
keys: []
}
}
}).of(option);
let result;
/* helper function to filter out key in the exclusion list. */
const isIncluded = (key) => {
let included = false;
if (!DEVELOPMENT) {
exclusion.prefixes.push(`DEBUG_`);
}
if (Hf.isString(key) && key !== `prototype`) {
const prefixExcepted = Hf.isNonEmptyArray(exclusion.exception.prefixes) ? exclusion.exception.prefixes.some((prefix) => {
return key.substr(0, prefix.length) === prefix;
}) : false;
const postfixExcepted = Hf.isNonEmptyArray(exclusion.exception.postfixes) ? exclusion.exception.postfixes.some((postfix) => {
return key.substr(0, postfix.length) === postfix;
}) : false;
const keyExcepted = Hf.isNonEmptyArray(exclusion.exception.keys) ? exclusion.exception.keys.includes(key) : false;
included = true;
if (included && Hf.isNonEmptyArray(exclusion.prefixes)) {
included = exclusion.prefixes.every((prefix) => key.substr(0, prefix.length) !== prefix);
}
if (included && Hf.isNonEmptyArray(exclusion.postfixes)) {
included = exclusion.postfixes.every((postfix) => key.substr(0, postfix.length) !== postfix);
}
if (included && Hf.isNonEmptyArray(exclusion.keys)) {
if (exclusion.keys.length === 1 && exclusion.keys[0] === `*`) {
included = false;
} else {
if (included) {
included = !exclusion.keys.includes(key);
}
}
}
included = included || (prefixExcepted || postfixExcepted || keyExcepted);
}
return included;
};
if (Hf.isObject(source)) {
result = {};
} else if (Hf.isFunction(source)) {
result = () => {};
}
if (!exclusion.prototypes) {
/* copy source object prototypes to new mixed result object */
result = Object.entries(Object.getPrototypeOf(source)).filter(([ fnName, fn ]) => {
return Hf.isFunction(fn) && isIncluded(fnName);
}).reduce((_result, [ fnName, fn ]) => {
/* bind the prototype to source object */
_result[fnName] = fn.bind(source);
return _result;
}, result);
/* copy source object functions to new mixed result object */
result = Object.entries(source).filter(([ fnName, fn ]) => {
return Hf.isFunction(fn) && isIncluded(fnName);
}).reduce((_result, [ fnName, fn ]) => {
/* bind the prototype to source object */
_result[fnName] = fn;
return _result;
}, result);
}
if (!exclusion.properties) {
result = Object.keys(Object.getPrototypeOf(source)).concat(
exclusion.enumerablePropertiesOnly ? Object.keys(source) : Object.getOwnPropertyNames(source)
).filter((key) => {
return !Hf.isFunction(source[key]) && isIncluded(key);
}).reduce((_result, key) => {
const sourceObjDesc = Object.getOwnPropertyDescriptor(source, key);
if (Hf.isObject(sourceObjDesc)) {
Object.defineProperty(_result, key, {
get () {
return source[key];
},
set (value) {
source[key] = value;
},
configurable: sourceObjDesc.configurable,
enumerable: sourceObjDesc.enumerable
});
} else {
Object.defineProperty(_result, key, {
get () {
return source[key];
},
set (value) {
source[key] = value;
},
configurable: false,
enumerable: true
});
}
return _result;
}, result);
}
return {
/**
* @description - Mixing with the target object or function...
*
* @method mix.with
* @param {object|fuction} target - Target object or function that is being extended to.
* @return {object}
*/
with (target) {
if (Hf.DEVELOPMENT) {
if (!(Hf.isObject(target) || Hf.isFunction(target))) {
Hf.log(`error`, `CommonElement.mix.with - Input target object or function is invalid.`);
}
// if (Hf.isObject(source) && !Hf.isObject(target)) {
// Hf.log(`error`, `CommonElement.mix.with - Input target object is invalid.`);
// } else if (Hf.isFunction(source) && !Hf.isFunction(target)) {
// Hf.log(`error`, `CommonElement.mix.with - Input target function is invalid.`);
// }
}
if (!exclusion.prototypes) {
/* copy target object prototypes to new mixed result object */
result = Object.entries(Object.getPrototypeOf(target)).filter(([ fnName, fn ]) => {
if (Hf.isFunction(fn) && isIncluded(fnName)) {
if (!fnOverrided) {
if (!result.hasOwnProperty(fnName)) {
return true;
}
return false;
}
return true;
}
return false;
}).reduce((_result, [ fnName, fn ]) => {
/* bind the prototype to target object */
_result[fnName] = fn.bind(target);
return _result;
}, result);
/* copy target object functions to new mixed result object */
result = Object.entries(target).filter(([ fnName, fn ]) => {
if (Hf.isFunction(fn) && isIncluded(fnName)) {
/* mix prototypes only */
if (!fnOverrided) {
if (!result.hasOwnProperty(fnName)) {
return true;
}
return false;
}
return true;
}
return false;
}).reduce((_result, [ fnName, fn ]) => {
_result[fnName] = fn;
return _result;
}, result);
}
if (!exclusion.properties) {
result = Object.keys(Object.getPrototypeOf(target)).concat(
exclusion.enumerablePropertiesOnly ? Object.keys(target) : Object.getOwnPropertyNames(target)
).filter((key) => {
return !Hf.isFunction(target[key]) && target.hasOwnProperty(key) && !result.hasOwnProperty(key) && isIncluded(key);
}).reduce((_result, key) => {
const targetObjDesc = Object.getOwnPropertyDescriptor(target, key);
if (Hf.isObject(targetObjDesc)) {
Object.defineProperty(_result, key, {
get () {
return target[key];
},
set (value) {
target[key] = value;
},
configurable: targetObjDesc.configurable,
enumerable: targetObjDesc.enumerable
});
} else {
Object.defineProperty(_result, key, {
get () {
return target[key];
},
set (value) {
target[key] = value;
},
configurable: false,
enumerable: true
});
}
return _result;
}, result);
}
return result;
}
};
},
/**
* @description - Reveal the closure as a public object.
*
* @method reveal
* @param {object} closure - A closure function or object.
* @param {object} option
* @return {object}
*/
reveal (closure, option = {}) {
const Hf = this;
option = Hf.isObject(option) ? option : {};
if (Hf.DEVELOPMENT) {
if (!(Hf.isObject(closure) || Hf.isFunction(closure))) {
Hf.log(`error`, `CommonElement.reveal - Input closure is invalid.`);
}
}
if (Hf.isObject(closure)) {
return Hf.mix(closure, option).with({});
}
if (Hf.isFunction(closure)) {
let enclosedObj = {};
closure.call(enclosedObj);
return Hf.mix(enclosedObj, option).with({});
}
},
/**
* @description - Retrieve an object property at pathId.
*
* @usage TODO: Write usage for CommonElement.retrieve method.
*
* @method retrieve
* @param {string|array} pathId - Path of the property to retrieve.
* @param {string} delimiter
* @param {object} asNestedObject - Flag to indicate the return value is a nested object of pathId.
* @return {object}
*/
retrieve (pathId, delimiter, asNestedObject = false) {
const Hf = this;
asNestedObject = Hf.isBoolean(asNestedObject) ? asNestedObject : false;
if (Hf.DEVELOPMENT) {
if (!(Hf.isString(pathId) || Hf.isArray(pathId))) {
Hf.log(`error`, `CommonElement.retrieve - Input pathId is invalid.`);
} else if (!(Hf.isString(delimiter) && delimiter.length === 1)) {
Hf.log(`error`, `CommonElement.retrieve - Input delimiter is invalid.`);
}
}
pathId = Hf.isString(pathId) ? Hf.stringToArray(pathId, delimiter) : pathId;
return {
/**
* @description - Target object to retrive property from...
*
* @method retrieve.from