452 lines
13 KiB
JavaScript
Executable File
452 lines
13 KiB
JavaScript
Executable File
/**
|
|
* Copyright © Magento, Inc. All rights reserved.
|
|
* See COPYING.txt for license details.
|
|
*/
|
|
define([
|
|
'ko',
|
|
'jquery',
|
|
'underscore',
|
|
'mage/utils/strings'
|
|
], function (ko, $, _, stringUtils) {
|
|
'use strict';
|
|
|
|
var primitives = [
|
|
'undefined',
|
|
'boolean',
|
|
'number',
|
|
'string'
|
|
];
|
|
|
|
/**
|
|
* Sets nested property of a specified object.
|
|
* @private
|
|
*
|
|
* @param {Object} parent - Object to look inside for the properties.
|
|
* @param {Array} path - Splitted path the property.
|
|
* @param {*} value - Value of the last property in 'path' array.
|
|
* returns {*} New value for the property.
|
|
*/
|
|
function setNested(parent, path, value) {
|
|
var last = path.pop(),
|
|
len = path.length,
|
|
pi = 0,
|
|
part = path[pi];
|
|
|
|
for (; pi < len; part = path[++pi]) {
|
|
if (!_.isObject(parent[part])) {
|
|
parent[part] = {};
|
|
}
|
|
|
|
parent = parent[part];
|
|
}
|
|
|
|
if (typeof parent[last] === 'function') {
|
|
parent[last](value);
|
|
} else {
|
|
parent[last] = value;
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
/**
|
|
* Retrieves value of a nested property.
|
|
* @private
|
|
*
|
|
* @param {Object} parent - Object to look inside for the properties.
|
|
* @param {Array} path - Splitted path the property.
|
|
* @returns {*} Value of the property.
|
|
*/
|
|
function getNested(parent, path) {
|
|
var exists = true,
|
|
len = path.length,
|
|
pi = 0;
|
|
|
|
for (; pi < len && exists; pi++) {
|
|
parent = parent[path[pi]];
|
|
|
|
if (typeof parent === 'undefined') {
|
|
exists = false;
|
|
}
|
|
}
|
|
|
|
if (exists) {
|
|
if (ko.isObservable(parent)) {
|
|
parent = parent();
|
|
}
|
|
|
|
return parent;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Removes property from a specified object.
|
|
* @private
|
|
*
|
|
* @param {Object} parent - Object from which to remove property.
|
|
* @param {Array} path - Splitted path to the property.
|
|
*/
|
|
function removeNested(parent, path) {
|
|
var field = path.pop();
|
|
|
|
parent = getNested(parent, path);
|
|
|
|
if (_.isObject(parent)) {
|
|
delete parent[field];
|
|
}
|
|
}
|
|
|
|
return {
|
|
|
|
/**
|
|
* Retrieves or defines objects' property by a composite path.
|
|
*
|
|
* @param {Object} data - Container for the properties specified in path.
|
|
* @param {String} path - Objects' properties divided by dots.
|
|
* @param {*} [value] - New value for the last property.
|
|
* @returns {*} Returns value of the last property in chain.
|
|
*
|
|
* @example
|
|
* utils.nested({}, 'one.two', 3);
|
|
* => { one: {two: 3} }
|
|
*/
|
|
nested: function (data, path, value) {
|
|
var action = arguments.length > 2 ? setNested : getNested;
|
|
|
|
path = path ? path.split('.') : [];
|
|
|
|
return action(data, path, value);
|
|
},
|
|
|
|
/**
|
|
* Removes nested property from an object.
|
|
*
|
|
* @param {Object} data - Data source.
|
|
* @param {String} path - Path to the property e.g. 'one.two.three'
|
|
*/
|
|
nestedRemove: function (data, path) {
|
|
path = path.split('.');
|
|
|
|
removeNested(data, path);
|
|
},
|
|
|
|
/**
|
|
* Flattens objects' nested properties.
|
|
*
|
|
* @param {Object} data - Object to flatten.
|
|
* @param {String} [separator='.'] - Objects' keys separator.
|
|
* @returns {Object} Flattened object.
|
|
*
|
|
* @example Example with a default separator.
|
|
* utils.flatten({one: { two: { three: 'value'} }});
|
|
* => { 'one.two.three': 'value' };
|
|
*
|
|
* @example Example with a custom separator.
|
|
* utils.flatten({one: { two: { three: 'value'} }}, '=>');
|
|
* => {'one=>two=>three': 'value'};
|
|
*/
|
|
flatten: function (data, separator, parent, result) {
|
|
separator = separator || '.';
|
|
result = result || {};
|
|
|
|
if (!data) {
|
|
return result;
|
|
}
|
|
|
|
// UnderscoreJS each breaks when an object has a length property so we use Object.keys
|
|
_.each(Object.keys(data), function (name) {
|
|
var node = data[name];
|
|
|
|
if ({}.toString.call(node) === '[object Function]') {
|
|
return;
|
|
}
|
|
|
|
if (parent) {
|
|
name = parent + separator + name;
|
|
}
|
|
|
|
typeof node === 'object' ?
|
|
this.flatten(node, separator, name, result) :
|
|
result[name] = node;
|
|
|
|
}, this);
|
|
|
|
return result;
|
|
},
|
|
|
|
/**
|
|
* Opposite operation of the 'flatten' method.
|
|
*
|
|
* @param {Object} data - Previously flattened object.
|
|
* @param {String} [separator='.'] - Keys separator.
|
|
* @returns {Object} Object with nested properties.
|
|
*
|
|
* @example Example using custom separator.
|
|
* utils.unflatten({'one=>two': 'value'}, '=>');
|
|
* => {
|
|
* one: { two: 'value' }
|
|
* };
|
|
*/
|
|
unflatten: function (data, separator) {
|
|
var result = {};
|
|
|
|
separator = separator || '.';
|
|
|
|
_.each(data, function (value, nodes) {
|
|
nodes = nodes.split(separator);
|
|
|
|
setNested(result, nodes, value);
|
|
});
|
|
|
|
return result;
|
|
},
|
|
|
|
/**
|
|
* Same operation as 'flatten' method,
|
|
* but returns objects' keys wrapped in '[]'.
|
|
*
|
|
* @param {Object} data - Object that should be serialized.
|
|
* @returns {Object} Serialized data.
|
|
*
|
|
* @example
|
|
* utils.serialize({one: { two: { three: 'value'} }});
|
|
* => { 'one[two][three]': 'value' }
|
|
*/
|
|
serialize: function (data) {
|
|
var result = {};
|
|
|
|
data = this.flatten(data);
|
|
|
|
_.each(data, function (value, keys) {
|
|
keys = stringUtils.serializeName(keys);
|
|
value = _.isUndefined(value) ? '' : value;
|
|
|
|
result[keys] = value;
|
|
}, this);
|
|
|
|
return result;
|
|
},
|
|
|
|
/**
|
|
* Performs deep extend of specified objects.
|
|
*
|
|
* @returns {Object|Array} Extended object.
|
|
*/
|
|
extend: function () {
|
|
var args = _.toArray(arguments);
|
|
|
|
args.unshift(true);
|
|
|
|
return $.extend.apply($, args);
|
|
},
|
|
|
|
/**
|
|
* Performs a deep clone of a specified object.
|
|
*
|
|
* @param {(Object|Array)} data - Data that should be copied.
|
|
* @returns {Object|Array} Cloned object.
|
|
*/
|
|
copy: function (data) {
|
|
var result = data,
|
|
isArray = Array.isArray(data),
|
|
placeholder;
|
|
|
|
if (this.isObject(data) || isArray) {
|
|
placeholder = isArray ? [] : {};
|
|
result = this.extend(placeholder, data);
|
|
}
|
|
|
|
return result;
|
|
},
|
|
|
|
/**
|
|
* Performs a deep clone of a specified object.
|
|
* Doesn't save links to original object.
|
|
*
|
|
* @param {*} original - Object to clone
|
|
* @returns {*}
|
|
*/
|
|
hardCopy: function (original) {
|
|
if (original === null || typeof original !== 'object') {
|
|
return original;
|
|
}
|
|
|
|
return JSON.parse(JSON.stringify(original));
|
|
},
|
|
|
|
/**
|
|
* Removes specified nested properties from the target object.
|
|
*
|
|
* @param {Object} target - Object whose properties should be removed.
|
|
* @param {(...String|Array|Object)} list - List that specifies properties to be removed.
|
|
* @returns {Object} Modified object.
|
|
*
|
|
* @example Basic usage
|
|
* var obj = {a: {b: 2}, c: 'a'};
|
|
*
|
|
* omit(obj, 'a.b');
|
|
* => {'a.b': 2};
|
|
* obj => {a: {}, c: 'a'};
|
|
*
|
|
* @example Various syntaxes that would return same result
|
|
* omit(obj, ['a.b', 'c']);
|
|
* omit(obj, 'a.b', 'c');
|
|
* omit(obj, {'a.b': true, 'c': true});
|
|
*/
|
|
omit: function (target, list) {
|
|
var removed = {},
|
|
ignored = list;
|
|
|
|
if (this.isObject(list)) {
|
|
ignored = [];
|
|
|
|
_.each(list, function (value, key) {
|
|
if (value) {
|
|
ignored.push(key);
|
|
}
|
|
});
|
|
} else if (_.isString(list)) {
|
|
ignored = _.toArray(arguments).slice(1);
|
|
}
|
|
|
|
_.each(ignored, function (path) {
|
|
var value = this.nested(target, path);
|
|
|
|
if (!_.isUndefined(value)) {
|
|
removed[path] = value;
|
|
|
|
this.nestedRemove(target, path);
|
|
}
|
|
}, this);
|
|
|
|
return removed;
|
|
},
|
|
|
|
/**
|
|
* Checks if provided value is a plain object.
|
|
*
|
|
* @param {*} value - Value to be checked.
|
|
* @returns {Boolean}
|
|
*/
|
|
isObject: function (value) {
|
|
var objProto = Object.prototype;
|
|
|
|
return typeof value == 'object' ?
|
|
objProto.toString.call(value) === '[object Object]' :
|
|
false;
|
|
},
|
|
|
|
/**
|
|
*
|
|
* @param {*} value
|
|
* @returns {Boolean}
|
|
*/
|
|
isPrimitive: function (value) {
|
|
return value === null || ~primitives.indexOf(typeof value);
|
|
},
|
|
|
|
/**
|
|
* Iterates over obj props/array elems recursively, applying action to each one
|
|
*
|
|
* @param {Object|Array} data - Data to be iterated.
|
|
* @param {Function} action - Callback to be called with each item as an argument.
|
|
* @param {Number} [maxDepth=7] - Max recursion depth.
|
|
*/
|
|
forEachRecursive: function (data, action, maxDepth) {
|
|
maxDepth = typeof maxDepth === 'number' && !isNaN(maxDepth) ? maxDepth - 1 : 7;
|
|
|
|
if (!_.isFunction(action) || _.isFunction(data) || maxDepth < 0) {
|
|
return;
|
|
}
|
|
|
|
if (!_.isObject(data)) {
|
|
action(data);
|
|
|
|
return;
|
|
}
|
|
|
|
_.each(data, function (value) {
|
|
this.forEachRecursive(value, action, maxDepth);
|
|
}, this);
|
|
|
|
action(data);
|
|
},
|
|
|
|
/**
|
|
* Maps obj props/array elems recursively
|
|
*
|
|
* @param {Object|Array} data - Data to be iterated.
|
|
* @param {Function} action - Callback to transform each item.
|
|
* @param {Number} [maxDepth=7] - Max recursion depth.
|
|
*
|
|
* @returns {Object|Array}
|
|
*/
|
|
mapRecursive: function (data, action, maxDepth) {
|
|
var newData;
|
|
|
|
maxDepth = typeof maxDepth === 'number' && !isNaN(maxDepth) ? maxDepth - 1 : 7;
|
|
|
|
if (!_.isFunction(action) || _.isFunction(data) || maxDepth < 0) {
|
|
return data;
|
|
}
|
|
|
|
if (!_.isObject(data)) {
|
|
return action(data);
|
|
}
|
|
|
|
if (_.isArray(data)) {
|
|
newData = _.map(data, function (item) {
|
|
return this.mapRecursive(item, action, maxDepth);
|
|
}, this);
|
|
|
|
return action(newData);
|
|
}
|
|
|
|
newData = _.mapObject(data, function (val, key) {
|
|
if (data.hasOwnProperty(key)) {
|
|
return this.mapRecursive(val, action, maxDepth);
|
|
}
|
|
|
|
return val;
|
|
}, this);
|
|
|
|
return action(newData);
|
|
},
|
|
|
|
/**
|
|
* Removes empty(in common sence) obj props/array elems
|
|
*
|
|
* @param {*} data - Data to be cleaned.
|
|
* @returns {*}
|
|
*/
|
|
removeEmptyValues: function (data) {
|
|
if (!_.isObject(data)) {
|
|
return data;
|
|
}
|
|
|
|
if (_.isArray(data)) {
|
|
return data.filter(function (item) {
|
|
return !this.isEmptyObj(item);
|
|
}, this);
|
|
}
|
|
|
|
return _.omit(data, this.isEmptyObj.bind(this));
|
|
},
|
|
|
|
/**
|
|
* Checks that argument of any type is empty in common sence:
|
|
* empty string, string with spaces only, object without own props, empty array, null or undefined
|
|
*
|
|
* @param {*} val - Value to be checked.
|
|
* @returns {Boolean}
|
|
*/
|
|
isEmptyObj: function (val) {
|
|
|
|
return _.isObject(val) && _.isEmpty(val) ||
|
|
this.isEmpty(val) ||
|
|
val && val.trim && this.isEmpty(val.trim());
|
|
}
|
|
};
|
|
});
|
|
|