UNPKG

handsontable

Version:

Handsontable is a JavaScript Data Grid available for React, Angular and Vue.

407 lines (388 loc) • 12.1 kB
"use strict"; exports.__esModule = true; exports.assignObjectDefaults = assignObjectDefaults; exports.clone = clone; exports.createObjectPropListener = createObjectPropListener; exports.deepClone = deepClone; exports.deepExtend = deepExtend; exports.deepObjectSize = deepObjectSize; exports.defineGetter = defineGetter; exports.duckSchema = duckSchema; exports.extend = extend; exports.getProperty = getProperty; exports.hasOwnProperty = hasOwnProperty; exports.inherit = inherit; exports.isObject = isObject; exports.isObjectEqual = isObjectEqual; exports.mixin = mixin; exports.objectEach = objectEach; exports.setProperty = setProperty; require("core-js/modules/es.error.cause.js"); require("core-js/modules/es.array.push.js"); require("core-js/modules/es.json.stringify.js"); require("core-js/modules/esnext.iterator.constructor.js"); require("core-js/modules/esnext.iterator.for-each.js"); var _array = require("./array"); /** * Generate schema for passed object. * * @param {Array|object} object An object to analyze. * @returns {Array|object} */ function duckSchema(object) { let schema; if (Array.isArray(object)) { schema = object.length ? new Array(object.length).fill(null) : []; } 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 The child class. * @param {object} Parent The parent class. * @returns {object} */ 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. * @param {string[]} [writableKeys] An array of keys that are writable to target object. * @returns {object} */ function extend(target, extension, writableKeys) { const hasWritableKeys = Array.isArray(writableKeys); objectEach(extension, (value, key) => { if (hasWritableKeys === false || writableKeys.includes(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. */ 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. * @returns {object} */ function deepClone(obj) { if (typeof obj === 'object') { return JSON.parse(JSON.stringify(obj)); } return obj; } /** * Shallow clone object. * * @param {object} object An object to clone. * @returns {object} */ function clone(object) { const 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} */ function mixin(Base) { if (!Base.MIXINS) { Base.MIXINS = []; } for (var _len = arguments.length, mixins = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { mixins[_key - 1] = arguments[_key]; } (0, _array.arrayEach)(mixins, mixinItem => { Base.MIXINS.push(mixinItem.MIXIN_NAME); objectEach(mixinItem, (value, key) => { if (Base.prototype[key] !== undefined) { throw new Error(`Mixin conflict. Property '${key}' already exist and cannot be overwritten.`); } if (typeof value === 'function') { Base.prototype[key] = value; } else { const getter = function _getter(property, initialValue) { const propertyName = `_${property}`; const initValue = newValue => { let result = newValue; if (Array.isArray(result) || isObject(result)) { result = deepClone(result); } return result; }; return function () { if (this[propertyName] === undefined) { this[propertyName] = initValue(initialValue); } return this[propertyName]; }; }; const setter = function _setter(property) { const propertyName = `_${property}`; return function (newValue) { this[propertyName] = newValue; }; }; 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 The first object to compare. * @param {object|Array} object2 The second object to compare. * @returns {boolean} */ function isObjectEqual(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 {*} object An object to check. * @returns {boolean} */ function isObject(object) { return Object.prototype.toString.call(object) === '[object Object]'; } /** * @param {object} object The object on which to define the property. * @param {string} property The name of the property to be defined or modified. * @param {*} value The value associated with the property. * @param {object} options The descriptor for the property being defined or modified. */ 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`. */ function objectEach(object, iteratee) { // eslint-disable-next-line no-restricted-syntax for (const 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 {*} */ function getProperty(object, name) { const names = name.split('.'); let result = object; objectEach(names, nameItem => { result = result[nameItem]; if (result === undefined) { result = undefined; return false; } }); return result; } /** * Set a property value on the provided object. Works on nested object prop names as well (e.g. `first.name`). * * @param {object} object Object to work on. * @param {string} name Prop name. * @param {*} value Value to be assigned at the provided property. */ function setProperty(object, name, value) { if (typeof name !== 'string') { return; } const names = name.split('.'); let workingObject = object; names.forEach((propName, index) => { if (propName === '__proto__' || propName === 'constructor' || propName === 'prototype') { // Security: prototype-polluting is not allowed return; } if (index !== names.length - 1) { if (!hasOwnProperty(workingObject, propName)) { workingObject[propName] = {}; } workingObject = workingObject[propName]; } else { workingObject[propName] = value; } }); } /** * Return object length (recursively). * * @param {*} object Object for which we want get length. * @returns {number} */ function deepObjectSize(object) { if (!isObject(object)) { return 0; } const recursObjLen = function (obj) { let result = 0; if (isObject(obj)) { objectEach(obj, (value, key) => { if (key === '__children') { return; } result += recursObjLen(value); }); } else { result += 1; } 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} */ function createObjectPropListener(defaultValue) { let propertyToListen = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '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 specific key. * @param {string} key String key to check. * @returns {boolean} */ function hasOwnProperty(object, key) { return Object.prototype.hasOwnProperty.call(object, key); } /** * Assign default values to an object. * * @param {object} target The object to assign defaults to. * @param {object} defaults The default values to assign. * @returns {object} The object with defaults assigned. */ function assignObjectDefaults(target, defaults) { if (typeof target !== 'object' || target === null) { return defaults; } if (typeof defaults !== 'object' || defaults === null) { return target; } const result = {}; // Assign defaults Object.keys(defaults).forEach(key => { if (typeof defaults[key] === 'object' && defaults[key] !== null && !Array.isArray(defaults[key])) { result[key] = assignObjectDefaults(target[key], defaults[key]); } else { result[key] = hasOwnProperty(target, key) && target[key] !== undefined ? target[key] : defaults[key]; } }); // Copy extra keys from target that aren't in defaults Object.keys(target).forEach(key => { if (!hasOwnProperty(result, key)) { result[key] = target[key]; } }); return result; }