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());
 | 
						|
        }
 | 
						|
    };
 | 
						|
});
 | 
						|
 |