No puede seleccionar más de 25 temas Los temas deben comenzar con una letra o número, pueden incluir guiones ('-') y pueden tener hasta 35 caracteres de largo.
 
 
 
 
 
 

452 líneas
13 KiB

  1. /**
  2. * Copyright © Magento, Inc. All rights reserved.
  3. * See COPYING.txt for license details.
  4. */
  5. define([
  6. 'ko',
  7. 'jquery',
  8. 'underscore',
  9. 'mage/utils/strings'
  10. ], function (ko, $, _, stringUtils) {
  11. 'use strict';
  12. var primitives = [
  13. 'undefined',
  14. 'boolean',
  15. 'number',
  16. 'string'
  17. ];
  18. /**
  19. * Sets nested property of a specified object.
  20. * @private
  21. *
  22. * @param {Object} parent - Object to look inside for the properties.
  23. * @param {Array} path - Splitted path the property.
  24. * @param {*} value - Value of the last property in 'path' array.
  25. * returns {*} New value for the property.
  26. */
  27. function setNested(parent, path, value) {
  28. var last = path.pop(),
  29. len = path.length,
  30. pi = 0,
  31. part = path[pi];
  32. for (; pi < len; part = path[++pi]) {
  33. if (!_.isObject(parent[part])) {
  34. parent[part] = {};
  35. }
  36. parent = parent[part];
  37. }
  38. if (typeof parent[last] === 'function') {
  39. parent[last](value);
  40. } else {
  41. parent[last] = value;
  42. }
  43. return value;
  44. }
  45. /**
  46. * Retrieves value of a nested property.
  47. * @private
  48. *
  49. * @param {Object} parent - Object to look inside for the properties.
  50. * @param {Array} path - Splitted path the property.
  51. * @returns {*} Value of the property.
  52. */
  53. function getNested(parent, path) {
  54. var exists = true,
  55. len = path.length,
  56. pi = 0;
  57. for (; pi < len && exists; pi++) {
  58. parent = parent[path[pi]];
  59. if (typeof parent === 'undefined') {
  60. exists = false;
  61. }
  62. }
  63. if (exists) {
  64. if (ko.isObservable(parent)) {
  65. parent = parent();
  66. }
  67. return parent;
  68. }
  69. }
  70. /**
  71. * Removes property from a specified object.
  72. * @private
  73. *
  74. * @param {Object} parent - Object from which to remove property.
  75. * @param {Array} path - Splitted path to the property.
  76. */
  77. function removeNested(parent, path) {
  78. var field = path.pop();
  79. parent = getNested(parent, path);
  80. if (_.isObject(parent)) {
  81. delete parent[field];
  82. }
  83. }
  84. return {
  85. /**
  86. * Retrieves or defines objects' property by a composite path.
  87. *
  88. * @param {Object} data - Container for the properties specified in path.
  89. * @param {String} path - Objects' properties divided by dots.
  90. * @param {*} [value] - New value for the last property.
  91. * @returns {*} Returns value of the last property in chain.
  92. *
  93. * @example
  94. * utils.nested({}, 'one.two', 3);
  95. * => { one: {two: 3} }
  96. */
  97. nested: function (data, path, value) {
  98. var action = arguments.length > 2 ? setNested : getNested;
  99. path = path ? path.split('.') : [];
  100. return action(data, path, value);
  101. },
  102. /**
  103. * Removes nested property from an object.
  104. *
  105. * @param {Object} data - Data source.
  106. * @param {String} path - Path to the property e.g. 'one.two.three'
  107. */
  108. nestedRemove: function (data, path) {
  109. path = path.split('.');
  110. removeNested(data, path);
  111. },
  112. /**
  113. * Flattens objects' nested properties.
  114. *
  115. * @param {Object} data - Object to flatten.
  116. * @param {String} [separator='.'] - Objects' keys separator.
  117. * @returns {Object} Flattened object.
  118. *
  119. * @example Example with a default separator.
  120. * utils.flatten({one: { two: { three: 'value'} }});
  121. * => { 'one.two.three': 'value' };
  122. *
  123. * @example Example with a custom separator.
  124. * utils.flatten({one: { two: { three: 'value'} }}, '=>');
  125. * => {'one=>two=>three': 'value'};
  126. */
  127. flatten: function (data, separator, parent, result) {
  128. separator = separator || '.';
  129. result = result || {};
  130. if (!data) {
  131. return result;
  132. }
  133. // UnderscoreJS each breaks when an object has a length property so we use Object.keys
  134. _.each(Object.keys(data), function (name) {
  135. var node = data[name];
  136. if ({}.toString.call(node) === '[object Function]') {
  137. return;
  138. }
  139. if (parent) {
  140. name = parent + separator + name;
  141. }
  142. typeof node === 'object' ?
  143. this.flatten(node, separator, name, result) :
  144. result[name] = node;
  145. }, this);
  146. return result;
  147. },
  148. /**
  149. * Opposite operation of the 'flatten' method.
  150. *
  151. * @param {Object} data - Previously flattened object.
  152. * @param {String} [separator='.'] - Keys separator.
  153. * @returns {Object} Object with nested properties.
  154. *
  155. * @example Example using custom separator.
  156. * utils.unflatten({'one=>two': 'value'}, '=>');
  157. * => {
  158. * one: { two: 'value' }
  159. * };
  160. */
  161. unflatten: function (data, separator) {
  162. var result = {};
  163. separator = separator || '.';
  164. _.each(data, function (value, nodes) {
  165. nodes = nodes.split(separator);
  166. setNested(result, nodes, value);
  167. });
  168. return result;
  169. },
  170. /**
  171. * Same operation as 'flatten' method,
  172. * but returns objects' keys wrapped in '[]'.
  173. *
  174. * @param {Object} data - Object that should be serialized.
  175. * @returns {Object} Serialized data.
  176. *
  177. * @example
  178. * utils.serialize({one: { two: { three: 'value'} }});
  179. * => { 'one[two][three]': 'value' }
  180. */
  181. serialize: function (data) {
  182. var result = {};
  183. data = this.flatten(data);
  184. _.each(data, function (value, keys) {
  185. keys = stringUtils.serializeName(keys);
  186. value = _.isUndefined(value) ? '' : value;
  187. result[keys] = value;
  188. }, this);
  189. return result;
  190. },
  191. /**
  192. * Performs deep extend of specified objects.
  193. *
  194. * @returns {Object|Array} Extended object.
  195. */
  196. extend: function () {
  197. var args = _.toArray(arguments);
  198. args.unshift(true);
  199. return $.extend.apply($, args);
  200. },
  201. /**
  202. * Performs a deep clone of a specified object.
  203. *
  204. * @param {(Object|Array)} data - Data that should be copied.
  205. * @returns {Object|Array} Cloned object.
  206. */
  207. copy: function (data) {
  208. var result = data,
  209. isArray = Array.isArray(data),
  210. placeholder;
  211. if (this.isObject(data) || isArray) {
  212. placeholder = isArray ? [] : {};
  213. result = this.extend(placeholder, data);
  214. }
  215. return result;
  216. },
  217. /**
  218. * Performs a deep clone of a specified object.
  219. * Doesn't save links to original object.
  220. *
  221. * @param {*} original - Object to clone
  222. * @returns {*}
  223. */
  224. hardCopy: function (original) {
  225. if (original === null || typeof original !== 'object') {
  226. return original;
  227. }
  228. return JSON.parse(JSON.stringify(original));
  229. },
  230. /**
  231. * Removes specified nested properties from the target object.
  232. *
  233. * @param {Object} target - Object whose properties should be removed.
  234. * @param {(...String|Array|Object)} list - List that specifies properties to be removed.
  235. * @returns {Object} Modified object.
  236. *
  237. * @example Basic usage
  238. * var obj = {a: {b: 2}, c: 'a'};
  239. *
  240. * omit(obj, 'a.b');
  241. * => {'a.b': 2};
  242. * obj => {a: {}, c: 'a'};
  243. *
  244. * @example Various syntaxes that would return same result
  245. * omit(obj, ['a.b', 'c']);
  246. * omit(obj, 'a.b', 'c');
  247. * omit(obj, {'a.b': true, 'c': true});
  248. */
  249. omit: function (target, list) {
  250. var removed = {},
  251. ignored = list;
  252. if (this.isObject(list)) {
  253. ignored = [];
  254. _.each(list, function (value, key) {
  255. if (value) {
  256. ignored.push(key);
  257. }
  258. });
  259. } else if (_.isString(list)) {
  260. ignored = _.toArray(arguments).slice(1);
  261. }
  262. _.each(ignored, function (path) {
  263. var value = this.nested(target, path);
  264. if (!_.isUndefined(value)) {
  265. removed[path] = value;
  266. this.nestedRemove(target, path);
  267. }
  268. }, this);
  269. return removed;
  270. },
  271. /**
  272. * Checks if provided value is a plain object.
  273. *
  274. * @param {*} value - Value to be checked.
  275. * @returns {Boolean}
  276. */
  277. isObject: function (value) {
  278. var objProto = Object.prototype;
  279. return typeof value == 'object' ?
  280. objProto.toString.call(value) === '[object Object]' :
  281. false;
  282. },
  283. /**
  284. *
  285. * @param {*} value
  286. * @returns {Boolean}
  287. */
  288. isPrimitive: function (value) {
  289. return value === null || ~primitives.indexOf(typeof value);
  290. },
  291. /**
  292. * Iterates over obj props/array elems recursively, applying action to each one
  293. *
  294. * @param {Object|Array} data - Data to be iterated.
  295. * @param {Function} action - Callback to be called with each item as an argument.
  296. * @param {Number} [maxDepth=7] - Max recursion depth.
  297. */
  298. forEachRecursive: function (data, action, maxDepth) {
  299. maxDepth = typeof maxDepth === 'number' && !isNaN(maxDepth) ? maxDepth - 1 : 7;
  300. if (!_.isFunction(action) || _.isFunction(data) || maxDepth < 0) {
  301. return;
  302. }
  303. if (!_.isObject(data)) {
  304. action(data);
  305. return;
  306. }
  307. _.each(data, function (value) {
  308. this.forEachRecursive(value, action, maxDepth);
  309. }, this);
  310. action(data);
  311. },
  312. /**
  313. * Maps obj props/array elems recursively
  314. *
  315. * @param {Object|Array} data - Data to be iterated.
  316. * @param {Function} action - Callback to transform each item.
  317. * @param {Number} [maxDepth=7] - Max recursion depth.
  318. *
  319. * @returns {Object|Array}
  320. */
  321. mapRecursive: function (data, action, maxDepth) {
  322. var newData;
  323. maxDepth = typeof maxDepth === 'number' && !isNaN(maxDepth) ? maxDepth - 1 : 7;
  324. if (!_.isFunction(action) || _.isFunction(data) || maxDepth < 0) {
  325. return data;
  326. }
  327. if (!_.isObject(data)) {
  328. return action(data);
  329. }
  330. if (_.isArray(data)) {
  331. newData = _.map(data, function (item) {
  332. return this.mapRecursive(item, action, maxDepth);
  333. }, this);
  334. return action(newData);
  335. }
  336. newData = _.mapObject(data, function (val, key) {
  337. if (data.hasOwnProperty(key)) {
  338. return this.mapRecursive(val, action, maxDepth);
  339. }
  340. return val;
  341. }, this);
  342. return action(newData);
  343. },
  344. /**
  345. * Removes empty(in common sence) obj props/array elems
  346. *
  347. * @param {*} data - Data to be cleaned.
  348. * @returns {*}
  349. */
  350. removeEmptyValues: function (data) {
  351. if (!_.isObject(data)) {
  352. return data;
  353. }
  354. if (_.isArray(data)) {
  355. return data.filter(function (item) {
  356. return !this.isEmptyObj(item);
  357. }, this);
  358. }
  359. return _.omit(data, this.isEmptyObj.bind(this));
  360. },
  361. /**
  362. * Checks that argument of any type is empty in common sence:
  363. * empty string, string with spaces only, object without own props, empty array, null or undefined
  364. *
  365. * @param {*} val - Value to be checked.
  366. * @returns {Boolean}
  367. */
  368. isEmptyObj: function (val) {
  369. return _.isObject(val) && _.isEmpty(val) ||
  370. this.isEmpty(val) ||
  371. val && val.trim && this.isEmpty(val.trim());
  372. }
  373. };
  374. });