@quantlab/handsontable
Version:
Spreadsheet-like data grid editor that provides copy/paste functionality compatible with Excel/Google Docs
330 lines (283 loc) • 8.53 kB
JavaScript
import {arrayEach} from './array';
/**
* Generate schema for passed object.
*
* @param {Array|Object} object
* @returns {Array|Object}
*/
export function duckSchema(object) {
var schema;
if (Array.isArray(object)) {
schema = [];
} else {
schema = {};
objectEach(object, (value, key) => {
if (key === '__children') {
return;
}
if (value && typeof value === 'object' && !Array.isArray(value)) {
schema[key] = duckSchema(value);
} else if (Array.isArray(value)) {
if (value.length && typeof value[0] === 'object' && !Array.isArray(value[0])) {
schema[key] = [duckSchema(value[0])];
} else {
schema[key] = [];
}
} else {
schema[key] = null;
}
});
}
return schema;
}
/**
* Inherit without without calling parent constructor, and setting `Child.prototype.constructor` to `Child` instead of `Parent`.
* Creates temporary dummy function to call it as constructor.
* Described in ticket: https://github.com/handsontable/handsontable/pull/516
*
* @param {Object} Child child class
* @param {Object} Parent parent class
* @return {Object} extended Child
*/
export function inherit(Child, Parent) {
Parent.prototype.constructor = Parent;
Child.prototype = new Parent();
Child.prototype.constructor = Child;
return Child;
}
/**
* Perform shallow extend of a target object with extension's own properties.
*
* @param {Object} target An object that will receive the new properties.
* @param {Object} extension An object containing additional properties to merge into the target.
*/
export function extend(target, extension) {
objectEach(extension, (value, key) => {
target[key] = value;
});
return target;
}
/**
* Perform deep extend of a target object with extension's own properties.
*
* @param {Object} target An object that will receive the new properties.
* @param {Object} extension An object containing additional properties to merge into the target.
*/
export function deepExtend(target, extension) {
objectEach(extension, (value, key) => {
if (extension[key] && typeof extension[key] === 'object') {
if (!target[key]) {
if (Array.isArray(extension[key])) {
target[key] = [];
} else if (Object.prototype.toString.call(extension[key]) === '[object Date]') {
target[key] = extension[key];
} else {
target[key] = {};
}
}
deepExtend(target[key], extension[key]);
} else {
target[key] = extension[key];
}
});
}
/**
* Perform deep clone of an object.
* WARNING! Only clones JSON properties. Will cause error when `obj` contains a function, Date, etc.
*
* @param {Object} obj An object that will be cloned
* @return {Object}
*/
export function deepClone(obj) {
if (typeof obj === 'object') {
return JSON.parse(JSON.stringify(obj));
}
return obj;
}
/**
* Shallow clone object.
*
* @param {Object} object
* @returns {Object}
*/
export function clone(object) {
let result = {};
objectEach(object, (value, key) => {
result[key] = value;
});
return result;
}
/**
* Extend the Base object (usually prototype) of the functionality the `mixins` objects.
*
* @param {Object} Base Base object which will be extended.
* @param {Object} mixins The object of the functionality will be "copied".
* @returns {Object}
*/
export function mixin(Base, ...mixins) {
if (!Base.MIXINS) {
Base.MIXINS = [];
}
arrayEach(mixins, (mixin) => {
Base.MIXINS.push(mixin.MIXIN_NAME);
objectEach(mixin, (value, key) => {
if (Base.prototype[key] !== void 0) {
throw new Error(`Mixin conflict. Property '${key}' already exist and cannot be overwritten.`);
}
if (typeof value === 'function') {
Base.prototype[key] = value;
} else {
let getter = function _getter(propertyName, initialValue) {
propertyName = `_${propertyName}`;
let initValue = (value) => {
if (Array.isArray(value) || isObject(value)) {
value = deepClone(value);
}
return value;
};
return function() {
if (this[propertyName] === void 0) {
this[propertyName] = initValue(initialValue);
}
return this[propertyName];
};
};
let setter = function _setter(propertyName) {
propertyName = `_${propertyName}`;
return function(value) {
this[propertyName] = value;
};
};
Object.defineProperty(Base.prototype, key, {
get: getter(key, value),
set: setter(key),
configurable: true,
});
}
});
});
return Base;
}
/**
* Checks if two objects or arrays are (deep) equal
*
* @param {Object|Array} object1
* @param {Object|Array} object2
* @returns {Boolean}
*/
export function isObjectEquals(object1, object2) {
return JSON.stringify(object1) === JSON.stringify(object2);
}
/**
* Determines whether given object is a plain Object.
* Note: String and Array are not plain Objects
* @param {*} obj
* @returns {boolean}
*/
export function isObject(obj) {
return Object.prototype.toString.call(obj) == '[object Object]';
}
export function defineGetter(object, property, value, options) {
options.value = value;
options.writable = options.writable !== false;
options.enumerable = options.enumerable !== false;
options.configurable = options.configurable !== false;
Object.defineProperty(object, property, options);
}
/**
* A specialized version of `.forEach` for objects.
*
* @param {Object} object The object to iterate over.
* @param {Function} iteratee The function invoked per iteration.
* @returns {Object} Returns `object`.
*/
export function objectEach(object, iteratee) {
for (let key in object) {
if (!object.hasOwnProperty || (object.hasOwnProperty && Object.prototype.hasOwnProperty.call(object, key))) {
if (iteratee(object[key], key, object) === false) {
break;
}
}
}
return object;
}
/**
* Get object property by its name. Access to sub properties can be achieved by dot notation (e.q. `'foo.bar.baz'`).
*
* @param {Object} object Object which value will be exported.
* @param {String} name Object property name.
* @returns {*}
*/
export function getProperty(object, name) {
let names = name.split('.');
let result = object;
objectEach(names, (name) => {
result = result[name];
if (result === void 0) {
result = void 0;
return false;
}
});
return result;
}
/**
* Return object length (recursively).
*
* @param {*} object Object for which we want get length.
* @returns {Number}
*/
export function deepObjectSize(object) {
if (!isObject(object)) {
return 0;
}
let recursObjLen = function(obj) {
let result = 0;
if (isObject(obj)) {
objectEach(obj, (key) => {
result += recursObjLen(key);
});
} else {
result++;
}
return result;
};
return recursObjLen(object);
}
/**
* Create object with property where its value change will be observed.
*
* @param {*} [defaultValue=undefined] Default value.
* @param {String} [propertyToListen='value'] Property to listen.
* @returns {Object}
*/
export function createObjectPropListener(defaultValue, propertyToListen = 'value') {
const privateProperty = `_${propertyToListen}`;
const holder = {
_touched: false,
[privateProperty]: defaultValue,
isTouched() {
return this._touched;
}
};
Object.defineProperty(holder, propertyToListen, {
get() {
return this[privateProperty];
},
set(value) {
this._touched = true;
this[privateProperty] = value;
},
enumerable: true,
configurable: true
});
return holder;
}
/**
* Check if at specified `key` there is any value for `object`.
*
* @param {Object} object Object to search value at specyfic key.
* @param {String} key String key to check.
*/
export function hasOwnProperty(object, key) {
return Object.prototype.hasOwnProperty.call(object, key);
}