cba7740269c988c8f809a62501c576047f71048d75c9df2f29f8a7590762b03b9ff08148241afbe8fcb723b1af991a1da0130eccd0c5e5c0eebc820bdf9200 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  1. import {arrayEach} from './array';
  2. /**
  3. * Generate schema for passed object.
  4. *
  5. * @param {Array|Object} object
  6. * @returns {Array|Object}
  7. */
  8. export function duckSchema(object) {
  9. var schema;
  10. if (Array.isArray(object)) {
  11. schema = [];
  12. } else {
  13. schema = {};
  14. objectEach(object, (value, key) => {
  15. if (key === '__children') {
  16. return;
  17. }
  18. if (value && typeof value === 'object' && !Array.isArray(value)) {
  19. schema[key] = duckSchema(value);
  20. } else if (Array.isArray(value)) {
  21. if (value.length && typeof value[0] === 'object' && !Array.isArray(value[0])) {
  22. schema[key] = [duckSchema(value[0])];
  23. } else {
  24. schema[key] = [];
  25. }
  26. } else {
  27. schema[key] = null;
  28. }
  29. });
  30. }
  31. return schema;
  32. }
  33. /**
  34. * Inherit without without calling parent constructor, and setting `Child.prototype.constructor` to `Child` instead of `Parent`.
  35. * Creates temporary dummy function to call it as constructor.
  36. * Described in ticket: https://github.com/handsontable/handsontable/pull/516
  37. *
  38. * @param {Object} Child child class
  39. * @param {Object} Parent parent class
  40. * @return {Object} extended Child
  41. */
  42. export function inherit(Child, Parent) {
  43. Parent.prototype.constructor = Parent;
  44. Child.prototype = new Parent();
  45. Child.prototype.constructor = Child;
  46. return Child;
  47. }
  48. /**
  49. * Perform shallow extend of a target object with extension's own properties.
  50. *
  51. * @param {Object} target An object that will receive the new properties.
  52. * @param {Object} extension An object containing additional properties to merge into the target.
  53. */
  54. export function extend(target, extension) {
  55. objectEach(extension, (value, key) => {
  56. target[key] = value;
  57. });
  58. return target;
  59. }
  60. /**
  61. * Perform deep extend of a target object with extension's own properties.
  62. *
  63. * @param {Object} target An object that will receive the new properties.
  64. * @param {Object} extension An object containing additional properties to merge into the target.
  65. */
  66. export function deepExtend(target, extension) {
  67. objectEach(extension, (value, key) => {
  68. if (extension[key] && typeof extension[key] === 'object') {
  69. if (!target[key]) {
  70. if (Array.isArray(extension[key])) {
  71. target[key] = [];
  72. } else if (Object.prototype.toString.call(extension[key]) === '[object Date]') {
  73. target[key] = extension[key];
  74. } else {
  75. target[key] = {};
  76. }
  77. }
  78. deepExtend(target[key], extension[key]);
  79. } else {
  80. target[key] = extension[key];
  81. }
  82. });
  83. }
  84. /**
  85. * Perform deep clone of an object.
  86. * WARNING! Only clones JSON properties. Will cause error when `obj` contains a function, Date, etc.
  87. *
  88. * @param {Object} obj An object that will be cloned
  89. * @return {Object}
  90. */
  91. export function deepClone(obj) {
  92. if (typeof obj === 'object') {
  93. return JSON.parse(JSON.stringify(obj));
  94. }
  95. return obj;
  96. }
  97. /**
  98. * Shallow clone object.
  99. *
  100. * @param {Object} object
  101. * @returns {Object}
  102. */
  103. export function clone(object) {
  104. let result = {};
  105. objectEach(object, (value, key) => {
  106. result[key] = value;
  107. });
  108. return result;
  109. }
  110. /**
  111. * Extend the Base object (usually prototype) of the functionality the `mixins` objects.
  112. *
  113. * @param {Object} Base Base object which will be extended.
  114. * @param {Object} mixins The object of the functionality will be "copied".
  115. * @returns {Object}
  116. */
  117. export function mixin(Base, ...mixins) {
  118. if (!Base.MIXINS) {
  119. Base.MIXINS = [];
  120. }
  121. arrayEach(mixins, (mixin) => {
  122. Base.MIXINS.push(mixin.MIXIN_NAME);
  123. objectEach(mixin, (value, key) => {
  124. if (Base.prototype[key] !== void 0) {
  125. throw new Error(`Mixin conflict. Property '${key}' already exist and cannot be overwritten.`);
  126. }
  127. if (typeof value === 'function') {
  128. Base.prototype[key] = value;
  129. } else {
  130. let getter = function _getter(propertyName, initialValue) {
  131. propertyName = `_${propertyName}`;
  132. let initValue = (value) => {
  133. if (Array.isArray(value) || isObject(value)) {
  134. value = deepClone(value);
  135. }
  136. return value;
  137. };
  138. return function() {
  139. if (this[propertyName] === void 0) {
  140. this[propertyName] = initValue(initialValue);
  141. }
  142. return this[propertyName];
  143. };
  144. };
  145. let setter = function _setter(propertyName) {
  146. propertyName = `_${propertyName}`;
  147. return function(value) {
  148. this[propertyName] = value;
  149. };
  150. };
  151. Object.defineProperty(Base.prototype, key, {
  152. get: getter(key, value),
  153. set: setter(key),
  154. configurable: true,
  155. });
  156. }
  157. });
  158. });
  159. return Base;
  160. }
  161. /**
  162. * Checks if two objects or arrays are (deep) equal
  163. *
  164. * @param {Object|Array} object1
  165. * @param {Object|Array} object2
  166. * @returns {Boolean}
  167. */
  168. export function isObjectEquals(object1, object2) {
  169. return JSON.stringify(object1) === JSON.stringify(object2);
  170. }
  171. /**
  172. * Determines whether given object is a plain Object.
  173. * Note: String and Array are not plain Objects
  174. * @param {*} obj
  175. * @returns {boolean}
  176. */
  177. export function isObject(obj) {
  178. return Object.prototype.toString.call(obj) == '[object Object]';
  179. }
  180. export function defineGetter(object, property, value, options) {
  181. options.value = value;
  182. options.writable = options.writable !== false;
  183. options.enumerable = options.enumerable !== false;
  184. options.configurable = options.configurable !== false;
  185. Object.defineProperty(object, property, options);
  186. }
  187. /**
  188. * A specialized version of `.forEach` for objects.
  189. *
  190. * @param {Object} object The object to iterate over.
  191. * @param {Function} iteratee The function invoked per iteration.
  192. * @returns {Object} Returns `object`.
  193. */
  194. export function objectEach(object, iteratee) {
  195. for (let key in object) {
  196. if (!object.hasOwnProperty || (object.hasOwnProperty && Object.prototype.hasOwnProperty.call(object, key))) {
  197. if (iteratee(object[key], key, object) === false) {
  198. break;
  199. }
  200. }
  201. }
  202. return object;
  203. }
  204. /**
  205. * Get object property by its name. Access to sub properties can be achieved by dot notation (e.q. `'foo.bar.baz'`).
  206. *
  207. * @param {Object} object Object which value will be exported.
  208. * @param {String} name Object property name.
  209. * @returns {*}
  210. */
  211. export function getProperty(object, name) {
  212. let names = name.split('.');
  213. let result = object;
  214. objectEach(names, (name) => {
  215. result = result[name];
  216. if (result === void 0) {
  217. result = void 0;
  218. return false;
  219. }
  220. });
  221. return result;
  222. }
  223. /**
  224. * Return object length (recursively).
  225. *
  226. * @param {*} object Object for which we want get length.
  227. * @returns {Number}
  228. */
  229. export function deepObjectSize(object) {
  230. if (!isObject(object)) {
  231. return 0;
  232. }
  233. let recursObjLen = function(obj) {
  234. let result = 0;
  235. if (isObject(obj)) {
  236. objectEach(obj, (key) => {
  237. result += recursObjLen(key);
  238. });
  239. } else {
  240. result++;
  241. }
  242. return result;
  243. };
  244. return recursObjLen(object);
  245. }
  246. /**
  247. * Create object with property where its value change will be observed.
  248. *
  249. * @param {*} [defaultValue=undefined] Default value.
  250. * @param {String} [propertyToListen='value'] Property to listen.
  251. * @returns {Object}
  252. */
  253. export function createObjectPropListener(defaultValue, propertyToListen = 'value') {
  254. const privateProperty = `_${propertyToListen}`;
  255. const holder = {
  256. _touched: false,
  257. [privateProperty]: defaultValue,
  258. isTouched() {
  259. return this._touched;
  260. }
  261. };
  262. Object.defineProperty(holder, propertyToListen, {
  263. get() {
  264. return this[privateProperty];
  265. },
  266. set(value) {
  267. this._touched = true;
  268. this[privateProperty] = value;
  269. },
  270. enumerable: true,
  271. configurable: true
  272. });
  273. return holder;
  274. }
  275. /**
  276. * Check if at specified `key` there is any value for `object`.
  277. *
  278. * @param {Object} object Object to search value at specyfic key.
  279. * @param {String} key String key to check.
  280. */
  281. export function hasOwnProperty(object, key) {
  282. return Object.prototype.hasOwnProperty.call(object, key);
  283. }