vis.js is a dynamic, browser-based visualization library
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1523 lines
39 KiB

11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
9 years ago
9 years ago
9 years ago
11 years ago
11 years ago
11 years ago
10 years ago
10 years ago
11 years ago
11 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
9 years ago
9 years ago
  1. // utility functions
  2. // first check if moment.js is already loaded in the browser window, if so,
  3. // use this instance. Else, load via commonjs.
  4. var moment = require('./module/moment');
  5. var uuid = require('./module/uuid');
  6. /**
  7. * Test whether given object is a number
  8. * @param {*} object
  9. * @return {Boolean} isNumber
  10. */
  11. exports.isNumber = function (object) {
  12. return (object instanceof Number || typeof object == 'number');
  13. };
  14. /**
  15. * Censors object elements containing dom elements
  16. * @param {*} object
  17. * @return {Object} object without elements
  18. */
  19. exports.elementsCensor = function (object) {
  20. if (!object) return;
  21. var replacer = function(key, value) {
  22. if (value instanceof Element) {
  23. return "DOM Element";
  24. } else {
  25. return value;
  26. }
  27. }
  28. return JSON.parse(JSON.stringify(object, replacer))
  29. }
  30. /**
  31. * Remove everything in the DOM object
  32. * @param DOMobject
  33. */
  34. exports.recursiveDOMDelete = function (DOMobject) {
  35. if (DOMobject) {
  36. while (DOMobject.hasChildNodes() === true) {
  37. exports.recursiveDOMDelete(DOMobject.firstChild);
  38. DOMobject.removeChild(DOMobject.firstChild);
  39. }
  40. }
  41. };
  42. /**
  43. * this function gives you a range between 0 and 1 based on the min and max values in the set, the total sum of all values and the current value.
  44. *
  45. * @param min
  46. * @param max
  47. * @param total
  48. * @param value
  49. * @returns {number}
  50. */
  51. exports.giveRange = function (min, max, total, value) {
  52. if (max == min) {
  53. return 0.5;
  54. }
  55. else {
  56. var scale = 1 / (max - min);
  57. return Math.max(0, (value - min) * scale);
  58. }
  59. }
  60. /**
  61. * Test whether given object is a string
  62. * @param {*} object
  63. * @return {Boolean} isString
  64. */
  65. exports.isString = function (object) {
  66. return (object instanceof String || typeof object == 'string');
  67. };
  68. /**
  69. * Test whether given object is a Date, or a String containing a Date
  70. * @param {Date | String} object
  71. * @return {Boolean} isDate
  72. */
  73. exports.isDate = function (object) {
  74. if (object instanceof Date) {
  75. return true;
  76. }
  77. else if (exports.isString(object)) {
  78. // test whether this string contains a date
  79. var match = ASPDateRegex.exec(object);
  80. if (match) {
  81. return true;
  82. }
  83. else if (!isNaN(Date.parse(object))) {
  84. return true;
  85. }
  86. }
  87. return false;
  88. };
  89. /**
  90. * Create a semi UUID
  91. * source: http://stackoverflow.com/a/105074/1262753
  92. * @return {String} uuid
  93. */
  94. exports.randomUUID = function () {
  95. return uuid.v4();
  96. };
  97. /**
  98. * assign all keys of an object that are not nested objects to a certain value (used for color objects).
  99. * @param obj
  100. * @param value
  101. */
  102. exports.assignAllKeys = function (obj, value) {
  103. for (var prop in obj) {
  104. if (obj.hasOwnProperty(prop)) {
  105. if (typeof obj[prop] !== 'object') {
  106. obj[prop] = value;
  107. }
  108. }
  109. }
  110. }
  111. /**
  112. * Fill an object with a possibly partially defined other object. Only copies values if the a object has an object requiring values.
  113. * That means an object is not created on a property if only the b object has it.
  114. * @param obj
  115. * @param value
  116. */
  117. exports.fillIfDefined = function (a, b, allowDeletion = false) {
  118. for (var prop in a) {
  119. if (b[prop] !== undefined) {
  120. if (typeof b[prop] !== 'object') {
  121. if ((b[prop] === undefined || b[prop] === null) && a[prop] !== undefined && allowDeletion === true) {
  122. delete a[prop];
  123. }
  124. else {
  125. a[prop] = b[prop];
  126. }
  127. }
  128. else {
  129. if (typeof a[prop] === 'object') {
  130. exports.fillIfDefined(a[prop], b[prop], allowDeletion);
  131. }
  132. }
  133. }
  134. }
  135. }
  136. /**
  137. * Extend object a with the properties of object b or a series of objects
  138. * Only properties with defined values are copied
  139. * @param {Object} a
  140. * @param {... Object} b
  141. * @return {Object} a
  142. */
  143. exports.protoExtend = function (a, b) {
  144. for (var i = 1; i < arguments.length; i++) {
  145. var other = arguments[i];
  146. for (var prop in other) {
  147. a[prop] = other[prop];
  148. }
  149. }
  150. return a;
  151. };
  152. /**
  153. * Extend object a with the properties of object b or a series of objects
  154. * Only properties with defined values are copied
  155. * @param {Object} a
  156. * @param {... Object} b
  157. * @return {Object} a
  158. */
  159. exports.extend = function (a, b) {
  160. for (var i = 1; i < arguments.length; i++) {
  161. var other = arguments[i];
  162. for (var prop in other) {
  163. if (other.hasOwnProperty(prop)) {
  164. a[prop] = other[prop];
  165. }
  166. }
  167. }
  168. return a;
  169. };
  170. /**
  171. * Extend object a with selected properties of object b or a series of objects
  172. * Only properties with defined values are copied
  173. * @param {Array.<String>} props
  174. * @param {Object} a
  175. * @param {Object} b
  176. * @return {Object} a
  177. */
  178. exports.selectiveExtend = function (props, a, b) {
  179. if (!Array.isArray(props)) {
  180. throw new Error('Array with property names expected as first argument');
  181. }
  182. for (var i = 2; i < arguments.length; i++) {
  183. var other = arguments[i];
  184. for (var p = 0; p < props.length; p++) {
  185. var prop = props[p];
  186. if (other.hasOwnProperty(prop)) {
  187. a[prop] = other[prop];
  188. }
  189. }
  190. }
  191. return a;
  192. };
  193. /**
  194. * Extend object a with selected properties of object b or a series of objects
  195. * Only properties with defined values are copied
  196. * @param {Array.<String>} props
  197. * @param {Object} a
  198. * @param {Object} b
  199. * @return {Object} a
  200. */
  201. exports.selectiveDeepExtend = function (props, a, b, allowDeletion = false) {
  202. // TODO: add support for Arrays to deepExtend
  203. if (Array.isArray(b)) {
  204. throw new TypeError('Arrays are not supported by deepExtend');
  205. }
  206. for (var i = 2; i < arguments.length; i++) {
  207. var other = arguments[i];
  208. for (var p = 0; p < props.length; p++) {
  209. var prop = props[p];
  210. if (other.hasOwnProperty(prop)) {
  211. if (b[prop] && b[prop].constructor === Object) {
  212. if (a[prop] === undefined) {
  213. a[prop] = {};
  214. }
  215. if (a[prop].constructor === Object) {
  216. exports.deepExtend(a[prop], b[prop], false, allowDeletion);
  217. }
  218. else {
  219. if ((b[prop] === null) && a[prop] !== undefined && allowDeletion === true) {
  220. delete a[prop];
  221. }
  222. else {
  223. a[prop] = b[prop];
  224. }
  225. }
  226. } else if (Array.isArray(b[prop])) {
  227. throw new TypeError('Arrays are not supported by deepExtend');
  228. } else {
  229. if ((b[prop] === null) && a[prop] !== undefined && allowDeletion === true) {
  230. delete a[prop];
  231. }
  232. else {
  233. a[prop] = b[prop];
  234. }
  235. }
  236. }
  237. }
  238. }
  239. return a;
  240. };
  241. /**
  242. * Extend object a with selected properties of object b or a series of objects
  243. * Only properties with defined values are copied
  244. * @param {Array.<String>} props
  245. * @param {Object} a
  246. * @param {Object} b
  247. * @return {Object} a
  248. */
  249. exports.selectiveNotDeepExtend = function (props, a, b, allowDeletion = false) {
  250. // TODO: add support for Arrays to deepExtend
  251. if (Array.isArray(b)) {
  252. throw new TypeError('Arrays are not supported by deepExtend');
  253. }
  254. for (var prop in b) {
  255. if (b.hasOwnProperty(prop)) {
  256. if (props.indexOf(prop) == -1) {
  257. if (b[prop] && b[prop].constructor === Object) {
  258. if (a[prop] === undefined) {
  259. a[prop] = {};
  260. }
  261. if (a[prop].constructor === Object) {
  262. exports.deepExtend(a[prop], b[prop]);
  263. }
  264. else {
  265. if ((b[prop] === null) && a[prop] !== undefined && allowDeletion === true) {
  266. delete a[prop];
  267. }
  268. else {
  269. a[prop] = b[prop];
  270. }
  271. }
  272. } else if (Array.isArray(b[prop])) {
  273. a[prop] = [];
  274. for (let i = 0; i < b[prop].length; i++) {
  275. a[prop].push(b[prop][i]);
  276. }
  277. } else {
  278. if ((b[prop] === null) && a[prop] !== undefined && allowDeletion === true) {
  279. delete a[prop];
  280. }
  281. else {
  282. a[prop] = b[prop];
  283. }
  284. }
  285. }
  286. }
  287. }
  288. return a;
  289. };
  290. /**
  291. * Deep extend an object a with the properties of object b
  292. * @param {Object} a
  293. * @param {Object} b
  294. * @param [Boolean] protoExtend --> optional parameter. If true, the prototype values will also be extended.
  295. * (ie. the options objects that inherit from others will also get the inherited options)
  296. * @param [Boolean] global --> optional parameter. If true, the values of fields that are null will not deleted
  297. * @returns {Object}
  298. */
  299. exports.deepExtend = function (a, b, protoExtend, allowDeletion) {
  300. for (var prop in b) {
  301. if (b.hasOwnProperty(prop) || protoExtend === true) {
  302. if (b[prop] && b[prop].constructor === Object) {
  303. if (a[prop] === undefined) {
  304. a[prop] = {};
  305. }
  306. if (a[prop].constructor === Object) {
  307. exports.deepExtend(a[prop], b[prop], protoExtend);
  308. }
  309. else {
  310. if ((b[prop] === null) && a[prop] !== undefined && allowDeletion === true) {
  311. delete a[prop];
  312. }
  313. else {
  314. a[prop] = b[prop];
  315. }
  316. }
  317. } else if (Array.isArray(b[prop])) {
  318. a[prop] = [];
  319. for (let i = 0; i < b[prop].length; i++) {
  320. a[prop].push(b[prop][i]);
  321. }
  322. } else {
  323. if ((b[prop] === null) && a[prop] !== undefined && allowDeletion === true) {
  324. delete a[prop];
  325. }
  326. else {
  327. a[prop] = b[prop];
  328. }
  329. }
  330. }
  331. }
  332. return a;
  333. };
  334. /**
  335. * Test whether all elements in two arrays are equal.
  336. * @param {Array} a
  337. * @param {Array} b
  338. * @return {boolean} Returns true if both arrays have the same length and same
  339. * elements.
  340. */
  341. exports.equalArray = function (a, b) {
  342. if (a.length != b.length) return false;
  343. for (var i = 0, len = a.length; i < len; i++) {
  344. if (a[i] != b[i]) return false;
  345. }
  346. return true;
  347. };
  348. /**
  349. * Convert an object to another type
  350. * @param {Boolean | Number | String | Date | Moment | Null | undefined} object
  351. * @param {String | undefined} type Name of the type. Available types:
  352. * 'Boolean', 'Number', 'String',
  353. * 'Date', 'Moment', ISODate', 'ASPDate'.
  354. * @return {*} object
  355. * @throws Error
  356. */
  357. exports.convert = function (object, type) {
  358. var match;
  359. if (object === undefined) {
  360. return undefined;
  361. }
  362. if (object === null) {
  363. return null;
  364. }
  365. if (!type) {
  366. return object;
  367. }
  368. if (!(typeof type === 'string') && !(type instanceof String)) {
  369. throw new Error('Type must be a string');
  370. }
  371. //noinspection FallthroughInSwitchStatementJS
  372. switch (type) {
  373. case 'boolean':
  374. case 'Boolean':
  375. return Boolean(object);
  376. case 'number':
  377. case 'Number':
  378. if (exports.isString(object) && !isNaN(Date.parse(object))) {
  379. return moment(object).valueOf();
  380. } else {
  381. return Number(object.valueOf());
  382. }
  383. case 'string':
  384. case 'String':
  385. return String(object);
  386. case 'Date':
  387. if (exports.isNumber(object)) {
  388. return new Date(object);
  389. }
  390. if (object instanceof Date) {
  391. return new Date(object.valueOf());
  392. }
  393. else if (moment.isMoment(object)) {
  394. return new Date(object.valueOf());
  395. }
  396. if (exports.isString(object)) {
  397. match = ASPDateRegex.exec(object);
  398. if (match) {
  399. // object is an ASP date
  400. return new Date(Number(match[1])); // parse number
  401. }
  402. else {
  403. return moment(new Date(object)).toDate(); // parse string
  404. }
  405. }
  406. else {
  407. throw new Error(
  408. 'Cannot convert object of type ' + exports.getType(object) +
  409. ' to type Date');
  410. }
  411. case 'Moment':
  412. if (exports.isNumber(object)) {
  413. return moment(object);
  414. }
  415. if (object instanceof Date) {
  416. return moment(object.valueOf());
  417. }
  418. else if (moment.isMoment(object)) {
  419. return moment(object);
  420. }
  421. if (exports.isString(object)) {
  422. match = ASPDateRegex.exec(object);
  423. if (match) {
  424. // object is an ASP date
  425. return moment(Number(match[1])); // parse number
  426. }
  427. else {
  428. return moment(object); // parse string
  429. }
  430. }
  431. else {
  432. throw new Error(
  433. 'Cannot convert object of type ' + exports.getType(object) +
  434. ' to type Date');
  435. }
  436. case 'ISODate':
  437. if (exports.isNumber(object)) {
  438. return new Date(object);
  439. }
  440. else if (object instanceof Date) {
  441. return object.toISOString();
  442. }
  443. else if (moment.isMoment(object)) {
  444. return object.toDate().toISOString();
  445. }
  446. else if (exports.isString(object)) {
  447. match = ASPDateRegex.exec(object);
  448. if (match) {
  449. // object is an ASP date
  450. return new Date(Number(match[1])).toISOString(); // parse number
  451. }
  452. else {
  453. return moment(object).format(); // ISO 8601
  454. }
  455. }
  456. else {
  457. throw new Error(
  458. 'Cannot convert object of type ' + exports.getType(object) +
  459. ' to type ISODate');
  460. }
  461. case 'ASPDate':
  462. if (exports.isNumber(object)) {
  463. return '/Date(' + object + ')/';
  464. }
  465. else if (object instanceof Date) {
  466. return '/Date(' + object.valueOf() + ')/';
  467. }
  468. else if (exports.isString(object)) {
  469. match = ASPDateRegex.exec(object);
  470. var value;
  471. if (match) {
  472. // object is an ASP date
  473. value = new Date(Number(match[1])).valueOf(); // parse number
  474. }
  475. else {
  476. value = new Date(object).valueOf(); // parse string
  477. }
  478. return '/Date(' + value + ')/';
  479. }
  480. else {
  481. throw new Error(
  482. 'Cannot convert object of type ' + exports.getType(object) +
  483. ' to type ASPDate');
  484. }
  485. default:
  486. throw new Error('Unknown type "' + type + '"');
  487. }
  488. };
  489. // parse ASP.Net Date pattern,
  490. // for example '/Date(1198908717056)/' or '/Date(1198908717056-0700)/'
  491. // code from http://momentjs.com/
  492. var ASPDateRegex = /^\/?Date\((\-?\d+)/i;
  493. /**
  494. * Get the type of an object, for example exports.getType([]) returns 'Array'
  495. * @param {*} object
  496. * @return {String} type
  497. */
  498. exports.getType = function (object) {
  499. var type = typeof object;
  500. if (type == 'object') {
  501. if (object === null) {
  502. return 'null';
  503. }
  504. if (object instanceof Boolean) {
  505. return 'Boolean';
  506. }
  507. if (object instanceof Number) {
  508. return 'Number';
  509. }
  510. if (object instanceof String) {
  511. return 'String';
  512. }
  513. if (Array.isArray(object)) {
  514. return 'Array';
  515. }
  516. if (object instanceof Date) {
  517. return 'Date';
  518. }
  519. return 'Object';
  520. }
  521. else if (type == 'number') {
  522. return 'Number';
  523. }
  524. else if (type == 'boolean') {
  525. return 'Boolean';
  526. }
  527. else if (type == 'string') {
  528. return 'String';
  529. }
  530. else if (type === undefined) {
  531. return 'undefined';
  532. }
  533. return type;
  534. };
  535. /**
  536. * Used to extend an array and copy it. This is used to propagate paths recursively.
  537. *
  538. * @param arr
  539. * @param newValue
  540. * @returns {Array}
  541. */
  542. exports.copyAndExtendArray = function (arr, newValue) {
  543. let newArr = [];
  544. for (let i = 0; i < arr.length; i++) {
  545. newArr.push(arr[i]);
  546. }
  547. newArr.push(newValue);
  548. return newArr;
  549. }
  550. /**
  551. * Used to extend an array and copy it. This is used to propagate paths recursively.
  552. *
  553. * @param arr
  554. * @param newValue
  555. * @returns {Array}
  556. */
  557. exports.copyArray = function (arr) {
  558. let newArr = [];
  559. for (let i = 0; i < arr.length; i++) {
  560. newArr.push(arr[i]);
  561. }
  562. return newArr;
  563. }
  564. /**
  565. * Retrieve the absolute left value of a DOM element
  566. * @param {Element} elem A dom element, for example a div
  567. * @return {number} left The absolute left position of this element
  568. * in the browser page.
  569. */
  570. exports.getAbsoluteLeft = function (elem) {
  571. return elem.getBoundingClientRect().left;
  572. };
  573. exports.getAbsoluteRight = function (elem) {
  574. return elem.getBoundingClientRect().right;
  575. };
  576. /**
  577. * Retrieve the absolute top value of a DOM element
  578. * @param {Element} elem A dom element, for example a div
  579. * @return {number} top The absolute top position of this element
  580. * in the browser page.
  581. */
  582. exports.getAbsoluteTop = function (elem) {
  583. return elem.getBoundingClientRect().top;
  584. };
  585. /**
  586. * add a className to the given elements style
  587. * @param {Element} elem
  588. * @param {String} className
  589. */
  590. exports.addClassName = function (elem, className) {
  591. var classes = elem.className.split(' ');
  592. if (classes.indexOf(className) == -1) {
  593. classes.push(className); // add the class to the array
  594. elem.className = classes.join(' ');
  595. }
  596. };
  597. /**
  598. * add a className to the given elements style
  599. * @param {Element} elem
  600. * @param {String} className
  601. */
  602. exports.removeClassName = function (elem, className) {
  603. var classes = elem.className.split(' ');
  604. var index = classes.indexOf(className);
  605. if (index != -1) {
  606. classes.splice(index, 1); // remove the class from the array
  607. elem.className = classes.join(' ');
  608. }
  609. };
  610. /**
  611. * For each method for both arrays and objects.
  612. * In case of an array, the built-in Array.forEach() is applied.
  613. * In case of an Object, the method loops over all properties of the object.
  614. * @param {Object | Array} object An Object or Array
  615. * @param {function} callback Callback method, called for each item in
  616. * the object or array with three parameters:
  617. * callback(value, index, object)
  618. */
  619. exports.forEach = function (object, callback) {
  620. var i,
  621. len;
  622. if (Array.isArray(object)) {
  623. // array
  624. for (i = 0, len = object.length; i < len; i++) {
  625. callback(object[i], i, object);
  626. }
  627. }
  628. else {
  629. // object
  630. for (i in object) {
  631. if (object.hasOwnProperty(i)) {
  632. callback(object[i], i, object);
  633. }
  634. }
  635. }
  636. };
  637. /**
  638. * Convert an object into an array: all objects properties are put into the
  639. * array. The resulting array is unordered.
  640. * @param {Object} object
  641. * @param {Array} array
  642. */
  643. exports.toArray = function (object) {
  644. var array = [];
  645. for (var prop in object) {
  646. if (object.hasOwnProperty(prop)) array.push(object[prop]);
  647. }
  648. return array;
  649. };
  650. /**
  651. * Update a property in an object
  652. * @param {Object} object
  653. * @param {String} key
  654. * @param {*} value
  655. * @return {Boolean} changed
  656. */
  657. exports.updateProperty = function (object, key, value) {
  658. if (object[key] !== value) {
  659. object[key] = value;
  660. return true;
  661. }
  662. else {
  663. return false;
  664. }
  665. };
  666. /**
  667. * Throttle the given function to be only executed once per animation frame
  668. * @param {function} fn
  669. * @returns {function} Returns the throttled function
  670. */
  671. exports.throttle = function (fn) {
  672. var scheduled = false;
  673. return function throttled () {
  674. if (!scheduled) {
  675. scheduled = true;
  676. requestAnimationFrame(function () {
  677. scheduled = false;
  678. fn();
  679. });
  680. }
  681. }
  682. };
  683. /**
  684. * Add and event listener. Works for all browsers
  685. * @param {Element} element An html element
  686. * @param {string} action The action, for example "click",
  687. * without the prefix "on"
  688. * @param {function} listener The callback function to be executed
  689. * @param {boolean} [useCapture]
  690. */
  691. exports.addEventListener = function (element, action, listener, useCapture) {
  692. if (element.addEventListener) {
  693. if (useCapture === undefined)
  694. useCapture = false;
  695. if (action === "mousewheel" && navigator.userAgent.indexOf("Firefox") >= 0) {
  696. action = "DOMMouseScroll"; // For Firefox
  697. }
  698. element.addEventListener(action, listener, useCapture);
  699. } else {
  700. element.attachEvent("on" + action, listener); // IE browsers
  701. }
  702. };
  703. /**
  704. * Remove an event listener from an element
  705. * @param {Element} element An html dom element
  706. * @param {string} action The name of the event, for example "mousedown"
  707. * @param {function} listener The listener function
  708. * @param {boolean} [useCapture]
  709. */
  710. exports.removeEventListener = function (element, action, listener, useCapture) {
  711. if (element.removeEventListener) {
  712. // non-IE browsers
  713. if (useCapture === undefined)
  714. useCapture = false;
  715. if (action === "mousewheel" && navigator.userAgent.indexOf("Firefox") >= 0) {
  716. action = "DOMMouseScroll"; // For Firefox
  717. }
  718. element.removeEventListener(action, listener, useCapture);
  719. } else {
  720. // IE browsers
  721. element.detachEvent("on" + action, listener);
  722. }
  723. };
  724. /**
  725. * Cancels the event if it is cancelable, without stopping further propagation of the event.
  726. */
  727. exports.preventDefault = function (event) {
  728. if (!event)
  729. event = window.event;
  730. if (event.preventDefault) {
  731. event.preventDefault(); // non-IE browsers
  732. }
  733. else {
  734. event.returnValue = false; // IE browsers
  735. }
  736. };
  737. /**
  738. * Get HTML element which is the target of the event
  739. * @param {Event} event
  740. * @return {Element} target element
  741. */
  742. exports.getTarget = function (event) {
  743. // code from http://www.quirksmode.org/js/events_properties.html
  744. if (!event) {
  745. event = window.event;
  746. }
  747. var target;
  748. if (event.target) {
  749. target = event.target;
  750. }
  751. else if (event.srcElement) {
  752. target = event.srcElement;
  753. }
  754. if (target.nodeType != undefined && target.nodeType == 3) {
  755. // defeat Safari bug
  756. target = target.parentNode;
  757. }
  758. return target;
  759. };
  760. /**
  761. * Check if given element contains given parent somewhere in the DOM tree
  762. * @param {Element} element
  763. * @param {Element} parent
  764. */
  765. exports.hasParent = function (element, parent) {
  766. var e = element;
  767. while (e) {
  768. if (e === parent) {
  769. return true;
  770. }
  771. e = e.parentNode;
  772. }
  773. return false;
  774. };
  775. exports.option = {};
  776. /**
  777. * Convert a value into a boolean
  778. * @param {Boolean | function | undefined} value
  779. * @param {Boolean} [defaultValue]
  780. * @returns {Boolean} bool
  781. */
  782. exports.option.asBoolean = function (value, defaultValue) {
  783. if (typeof value == 'function') {
  784. value = value();
  785. }
  786. if (value != null) {
  787. return (value != false);
  788. }
  789. return defaultValue || null;
  790. };
  791. /**
  792. * Convert a value into a number
  793. * @param {Boolean | function | undefined} value
  794. * @param {Number} [defaultValue]
  795. * @returns {Number} number
  796. */
  797. exports.option.asNumber = function (value, defaultValue) {
  798. if (typeof value == 'function') {
  799. value = value();
  800. }
  801. if (value != null) {
  802. return Number(value) || defaultValue || null;
  803. }
  804. return defaultValue || null;
  805. };
  806. /**
  807. * Convert a value into a string
  808. * @param {String | function | undefined} value
  809. * @param {String} [defaultValue]
  810. * @returns {String} str
  811. */
  812. exports.option.asString = function (value, defaultValue) {
  813. if (typeof value == 'function') {
  814. value = value();
  815. }
  816. if (value != null) {
  817. return String(value);
  818. }
  819. return defaultValue || null;
  820. };
  821. /**
  822. * Convert a size or location into a string with pixels or a percentage
  823. * @param {String | Number | function | undefined} value
  824. * @param {String} [defaultValue]
  825. * @returns {String} size
  826. */
  827. exports.option.asSize = function (value, defaultValue) {
  828. if (typeof value == 'function') {
  829. value = value();
  830. }
  831. if (exports.isString(value)) {
  832. return value;
  833. }
  834. else if (exports.isNumber(value)) {
  835. return value + 'px';
  836. }
  837. else {
  838. return defaultValue || null;
  839. }
  840. };
  841. /**
  842. * Convert a value into a DOM element
  843. * @param {HTMLElement | function | undefined} value
  844. * @param {HTMLElement} [defaultValue]
  845. * @returns {HTMLElement | null} dom
  846. */
  847. exports.option.asElement = function (value, defaultValue) {
  848. if (typeof value == 'function') {
  849. value = value();
  850. }
  851. return value || defaultValue || null;
  852. };
  853. /**
  854. * http://stackoverflow.com/questions/5623838/rgb-to-hex-and-hex-to-rgb
  855. *
  856. * @param {String} hex
  857. * @returns {{r: *, g: *, b: *}} | 255 range
  858. */
  859. exports.hexToRGB = function (hex) {
  860. // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
  861. var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
  862. hex = hex.replace(shorthandRegex, function (m, r, g, b) {
  863. return r + r + g + g + b + b;
  864. });
  865. var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
  866. return result ? {
  867. r: parseInt(result[1], 16),
  868. g: parseInt(result[2], 16),
  869. b: parseInt(result[3], 16)
  870. } : null;
  871. };
  872. /**
  873. * This function takes color in hex format or rgb() or rgba() format and overrides the opacity. Returns rgba() string.
  874. * @param color
  875. * @param opacity
  876. * @returns {*}
  877. */
  878. exports.overrideOpacity = function (color, opacity) {
  879. if (color.indexOf("rgba") != -1) {
  880. return color;
  881. }
  882. else if (color.indexOf("rgb") != -1) {
  883. var rgb = color.substr(color.indexOf("(") + 1).replace(")", "").split(",");
  884. return "rgba(" + rgb[0] + "," + rgb[1] + "," + rgb[2] + "," + opacity + ")"
  885. }
  886. else {
  887. var rgb = exports.hexToRGB(color);
  888. if (rgb == null) {
  889. return color;
  890. }
  891. else {
  892. return "rgba(" + rgb.r + "," + rgb.g + "," + rgb.b + "," + opacity + ")"
  893. }
  894. }
  895. }
  896. /**
  897. *
  898. * @param red 0 -- 255
  899. * @param green 0 -- 255
  900. * @param blue 0 -- 255
  901. * @returns {string}
  902. * @constructor
  903. */
  904. exports.RGBToHex = function (red, green, blue) {
  905. return "#" + ((1 << 24) + (red << 16) + (green << 8) + blue).toString(16).slice(1);
  906. };
  907. /**
  908. * Parse a color property into an object with border, background, and
  909. * highlight colors
  910. * @param {Object | String} color
  911. * @return {Object} colorObject
  912. */
  913. exports.parseColor = function (color) {
  914. var c;
  915. if (exports.isString(color) === true) {
  916. if (exports.isValidRGB(color) === true) {
  917. var rgb = color.substr(4).substr(0, color.length - 5).split(',').map(function (value) { return parseInt(value) });
  918. color = exports.RGBToHex(rgb[0], rgb[1], rgb[2]);
  919. }
  920. if (exports.isValidHex(color) === true) {
  921. var hsv = exports.hexToHSV(color);
  922. var lighterColorHSV = { h: hsv.h, s: hsv.s * 0.8, v: Math.min(1, hsv.v * 1.02) };
  923. var darkerColorHSV = { h: hsv.h, s: Math.min(1, hsv.s * 1.25), v: hsv.v * 0.8 };
  924. var darkerColorHex = exports.HSVToHex(darkerColorHSV.h, darkerColorHSV.s, darkerColorHSV.v);
  925. var lighterColorHex = exports.HSVToHex(lighterColorHSV.h, lighterColorHSV.s, lighterColorHSV.v);
  926. c = {
  927. background: color,
  928. border: darkerColorHex,
  929. highlight: {
  930. background: lighterColorHex,
  931. border: darkerColorHex
  932. },
  933. hover: {
  934. background: lighterColorHex,
  935. border: darkerColorHex
  936. }
  937. };
  938. }
  939. else {
  940. c = {
  941. background: color,
  942. border: color,
  943. highlight: {
  944. background: color,
  945. border: color
  946. },
  947. hover: {
  948. background: color,
  949. border: color
  950. }
  951. };
  952. }
  953. }
  954. else {
  955. c = {};
  956. c.background = color.background || undefined;
  957. c.border = color.border || undefined;
  958. if (exports.isString(color.highlight)) {
  959. c.highlight = {
  960. border: color.highlight,
  961. background: color.highlight
  962. }
  963. }
  964. else {
  965. c.highlight = {};
  966. c.highlight.background = color.highlight && color.highlight.background || undefined;
  967. c.highlight.border = color.highlight && color.highlight.border || undefined;
  968. }
  969. if (exports.isString(color.hover)) {
  970. c.hover = {
  971. border: color.hover,
  972. background: color.hover
  973. }
  974. }
  975. else {
  976. c.hover = {};
  977. c.hover.background = color.hover && color.hover.background || undefined;
  978. c.hover.border = color.hover && color.hover.border || undefined;
  979. }
  980. }
  981. return c;
  982. };
  983. /**
  984. * http://www.javascripter.net/faq/rgb2hsv.htm
  985. *
  986. * @param red
  987. * @param green
  988. * @param blue
  989. * @returns {*}
  990. * @constructor
  991. */
  992. exports.RGBToHSV = function (red, green, blue) {
  993. red = red / 255; green = green / 255; blue = blue / 255;
  994. var minRGB = Math.min(red, Math.min(green, blue));
  995. var maxRGB = Math.max(red, Math.max(green, blue));
  996. // Black-gray-white
  997. if (minRGB == maxRGB) {
  998. return { h: 0, s: 0, v: minRGB };
  999. }
  1000. // Colors other than black-gray-white:
  1001. var d = (red == minRGB) ? green - blue : ((blue == minRGB) ? red - green : blue - red);
  1002. var h = (red == minRGB) ? 3 : ((blue == minRGB) ? 1 : 5);
  1003. var hue = 60 * (h - d / (maxRGB - minRGB)) / 360;
  1004. var saturation = (maxRGB - minRGB) / maxRGB;
  1005. var value = maxRGB;
  1006. return { h: hue, s: saturation, v: value };
  1007. };
  1008. var cssUtil = {
  1009. // split a string with css styles into an object with key/values
  1010. split: function (cssText) {
  1011. var styles = {};
  1012. cssText.split(';').forEach(function (style) {
  1013. if (style.trim() != '') {
  1014. var parts = style.split(':');
  1015. var key = parts[0].trim();
  1016. var value = parts[1].trim();
  1017. styles[key] = value;
  1018. }
  1019. });
  1020. return styles;
  1021. },
  1022. // build a css text string from an object with key/values
  1023. join: function (styles) {
  1024. return Object.keys(styles)
  1025. .map(function (key) {
  1026. return key + ': ' + styles[key];
  1027. })
  1028. .join('; ');
  1029. }
  1030. };
  1031. /**
  1032. * Append a string with css styles to an element
  1033. * @param {Element} element
  1034. * @param {String} cssText
  1035. */
  1036. exports.addCssText = function (element, cssText) {
  1037. var currentStyles = cssUtil.split(element.style.cssText);
  1038. var newStyles = cssUtil.split(cssText);
  1039. var styles = exports.extend(currentStyles, newStyles);
  1040. element.style.cssText = cssUtil.join(styles);
  1041. };
  1042. /**
  1043. * Remove a string with css styles from an element
  1044. * @param {Element} element
  1045. * @param {String} cssText
  1046. */
  1047. exports.removeCssText = function (element, cssText) {
  1048. var styles = cssUtil.split(element.style.cssText);
  1049. var removeStyles = cssUtil.split(cssText);
  1050. for (var key in removeStyles) {
  1051. if (removeStyles.hasOwnProperty(key)) {
  1052. delete styles[key];
  1053. }
  1054. }
  1055. element.style.cssText = cssUtil.join(styles);
  1056. };
  1057. /**
  1058. * https://gist.github.com/mjijackson/5311256
  1059. * @param h
  1060. * @param s
  1061. * @param v
  1062. * @returns {{r: number, g: number, b: number}}
  1063. * @constructor
  1064. */
  1065. exports.HSVToRGB = function (h, s, v) {
  1066. var r, g, b;
  1067. var i = Math.floor(h * 6);
  1068. var f = h * 6 - i;
  1069. var p = v * (1 - s);
  1070. var q = v * (1 - f * s);
  1071. var t = v * (1 - (1 - f) * s);
  1072. switch (i % 6) {
  1073. case 0: r = v, g = t, b = p; break;
  1074. case 1: r = q, g = v, b = p; break;
  1075. case 2: r = p, g = v, b = t; break;
  1076. case 3: r = p, g = q, b = v; break;
  1077. case 4: r = t, g = p, b = v; break;
  1078. case 5: r = v, g = p, b = q; break;
  1079. }
  1080. return { r: Math.floor(r * 255), g: Math.floor(g * 255), b: Math.floor(b * 255) };
  1081. };
  1082. exports.HSVToHex = function (h, s, v) {
  1083. var rgb = exports.HSVToRGB(h, s, v);
  1084. return exports.RGBToHex(rgb.r, rgb.g, rgb.b);
  1085. };
  1086. exports.hexToHSV = function (hex) {
  1087. var rgb = exports.hexToRGB(hex);
  1088. return exports.RGBToHSV(rgb.r, rgb.g, rgb.b);
  1089. };
  1090. exports.isValidHex = function (hex) {
  1091. var isOk = /(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(hex);
  1092. return isOk;
  1093. };
  1094. exports.isValidRGB = function (rgb) {
  1095. rgb = rgb.replace(" ", "");
  1096. var isOk = /rgb\((\d{1,3}),(\d{1,3}),(\d{1,3})\)/i.test(rgb);
  1097. return isOk;
  1098. }
  1099. exports.isValidRGBA = function (rgba) {
  1100. rgba = rgba.replace(" ", "");
  1101. var isOk = /rgba\((\d{1,3}),(\d{1,3}),(\d{1,3}),(.{1,3})\)/i.test(rgba);
  1102. return isOk;
  1103. }
  1104. /**
  1105. * This recursively redirects the prototype of JSON objects to the referenceObject
  1106. * This is used for default options.
  1107. *
  1108. * @param referenceObject
  1109. * @returns {*}
  1110. */
  1111. exports.selectiveBridgeObject = function (fields, referenceObject) {
  1112. if (typeof referenceObject == "object") {
  1113. var objectTo = Object.create(referenceObject);
  1114. for (var i = 0; i < fields.length; i++) {
  1115. if (referenceObject.hasOwnProperty(fields[i])) {
  1116. if (typeof referenceObject[fields[i]] == "object") {
  1117. objectTo[fields[i]] = exports.bridgeObject(referenceObject[fields[i]]);
  1118. }
  1119. }
  1120. }
  1121. return objectTo;
  1122. }
  1123. else {
  1124. return null;
  1125. }
  1126. };
  1127. /**
  1128. * This recursively redirects the prototype of JSON objects to the referenceObject
  1129. * This is used for default options.
  1130. *
  1131. * @param referenceObject
  1132. * @returns {*}
  1133. */
  1134. exports.bridgeObject = function (referenceObject) {
  1135. if (typeof referenceObject == "object") {
  1136. var objectTo = Object.create(referenceObject);
  1137. for (var i in referenceObject) {
  1138. if (referenceObject.hasOwnProperty(i)) {
  1139. if (typeof referenceObject[i] == "object") {
  1140. objectTo[i] = exports.bridgeObject(referenceObject[i]);
  1141. }
  1142. }
  1143. }
  1144. return objectTo;
  1145. }
  1146. else {
  1147. return null;
  1148. }
  1149. };
  1150. /**
  1151. * This method provides a stable sort implementation, very fast for presorted data
  1152. *
  1153. * @param a the array
  1154. * @param a order comparator
  1155. * @returns {the array}
  1156. */
  1157. exports.insertSort = function (a,compare) {
  1158. for (var i = 0; i < a.length; i++) {
  1159. var k = a[i];
  1160. for (var j = i; j > 0 && compare(k,a[j - 1])<0; j--) {
  1161. a[j] = a[j - 1];
  1162. }
  1163. a[j] = k;
  1164. }
  1165. return a;
  1166. }
  1167. /**
  1168. * this is used to set the options of subobjects in the options object. A requirement of these subobjects
  1169. * is that they have an 'enabled' element which is optional for the user but mandatory for the program.
  1170. *
  1171. * @param [object] mergeTarget | this is either this.options or the options used for the groups.
  1172. * @param [object] options | options
  1173. * @param [String] option | this is the option key in the options argument
  1174. */
  1175. exports.mergeOptions = function (mergeTarget, options, option, allowDeletion = false, globalOptions = {}) {
  1176. if (options[option] === null) {
  1177. mergeTarget[option] = Object.create(globalOptions[option]);
  1178. }
  1179. else {
  1180. if (options[option] !== undefined) {
  1181. if (typeof options[option] === 'boolean') {
  1182. mergeTarget[option].enabled = options[option];
  1183. }
  1184. else {
  1185. if (options[option].enabled === undefined) {
  1186. mergeTarget[option].enabled = true;
  1187. }
  1188. for (var prop in options[option]) {
  1189. if (options[option].hasOwnProperty(prop)) {
  1190. mergeTarget[option][prop] = options[option][prop];
  1191. }
  1192. }
  1193. }
  1194. }
  1195. }
  1196. }
  1197. /**
  1198. * This function does a binary search for a visible item in a sorted list. If we find a visible item, the code that uses
  1199. * this function will then iterate in both directions over this sorted list to find all visible items.
  1200. *
  1201. * @param {Item[]} orderedItems | Items ordered by start
  1202. * @param {function} comparator | -1 is lower, 0 is equal, 1 is higher
  1203. * @param {String} field
  1204. * @param {String} field2
  1205. * @returns {number}
  1206. * @private
  1207. */
  1208. exports.binarySearchCustom = function (orderedItems, comparator, field, field2) {
  1209. var maxIterations = 10000;
  1210. var iteration = 0;
  1211. var low = 0;
  1212. var high = orderedItems.length - 1;
  1213. while (low <= high && iteration < maxIterations) {
  1214. var middle = Math.floor((low + high) / 2);
  1215. var item = orderedItems[middle];
  1216. var value = (field2 === undefined) ? item[field] : item[field][field2];
  1217. var searchResult = comparator(value);
  1218. if (searchResult == 0) { // jihaa, found a visible item!
  1219. return middle;
  1220. }
  1221. else if (searchResult == -1) { // it is too small --> increase low
  1222. low = middle + 1;
  1223. }
  1224. else { // it is too big --> decrease high
  1225. high = middle - 1;
  1226. }
  1227. iteration++;
  1228. }
  1229. return -1;
  1230. };
  1231. /**
  1232. * This function does a binary search for a specific value in a sorted array. If it does not exist but is in between of
  1233. * two values, we return either the one before or the one after, depending on user input
  1234. * If it is found, we return the index, else -1.
  1235. *
  1236. * @param {Array} orderedItems
  1237. * @param {{start: number, end: number}} target
  1238. * @param {String} field
  1239. * @param {String} sidePreference 'before' or 'after'
  1240. * @param {function} comparator an optional comparator, returning -1,0,1 for <,==,>.
  1241. * @returns {number}
  1242. * @private
  1243. */
  1244. exports.binarySearchValue = function (orderedItems, target, field, sidePreference, comparator) {
  1245. var maxIterations = 10000;
  1246. var iteration = 0;
  1247. var low = 0;
  1248. var high = orderedItems.length - 1;
  1249. var prevValue, value, nextValue, middle;
  1250. var comparator = comparator != undefined ? comparator : function (a, b) {
  1251. return a == b ? 0 : a < b ? -1 : 1
  1252. };
  1253. while (low <= high && iteration < maxIterations) {
  1254. // get a new guess
  1255. middle = Math.floor(0.5 * (high + low));
  1256. prevValue = orderedItems[Math.max(0, middle - 1)][field];
  1257. value = orderedItems[middle][field];
  1258. nextValue = orderedItems[Math.min(orderedItems.length - 1, middle + 1)][field];
  1259. if (comparator(value, target) == 0) { // we found the target
  1260. return middle;
  1261. }
  1262. else if (comparator(prevValue, target) < 0 && comparator(value, target) > 0) { // target is in between of the previous and the current
  1263. return sidePreference == 'before' ? Math.max(0, middle - 1) : middle;
  1264. }
  1265. else if (comparator(value, target) < 0 && comparator(nextValue, target) > 0) { // target is in between of the current and the next
  1266. return sidePreference == 'before' ? middle : Math.min(orderedItems.length - 1, middle + 1);
  1267. }
  1268. else { // didnt find the target, we need to change our boundaries.
  1269. if (comparator(value, target) < 0) { // it is too small --> increase low
  1270. low = middle + 1;
  1271. }
  1272. else { // it is too big --> decrease high
  1273. high = middle - 1;
  1274. }
  1275. }
  1276. iteration++;
  1277. }
  1278. // didnt find anything. Return -1.
  1279. return -1;
  1280. };
  1281. /*
  1282. * Easing Functions - inspired from http://gizma.com/easing/
  1283. * only considering the t value for the range [0, 1] => [0, 1]
  1284. * https://gist.github.com/gre/1650294
  1285. */
  1286. exports.easingFunctions = {
  1287. // no easing, no acceleration
  1288. linear: function (t) {
  1289. return t
  1290. },
  1291. // accelerating from zero velocity
  1292. easeInQuad: function (t) {
  1293. return t * t
  1294. },
  1295. // decelerating to zero velocity
  1296. easeOutQuad: function (t) {
  1297. return t * (2 - t)
  1298. },
  1299. // acceleration until halfway, then deceleration
  1300. easeInOutQuad: function (t) {
  1301. return t < .5 ? 2 * t * t : -1 + (4 - 2 * t) * t
  1302. },
  1303. // accelerating from zero velocity
  1304. easeInCubic: function (t) {
  1305. return t * t * t
  1306. },
  1307. // decelerating to zero velocity
  1308. easeOutCubic: function (t) {
  1309. return (--t) * t * t + 1
  1310. },
  1311. // acceleration until halfway, then deceleration
  1312. easeInOutCubic: function (t) {
  1313. return t < .5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1
  1314. },
  1315. // accelerating from zero velocity
  1316. easeInQuart: function (t) {
  1317. return t * t * t * t
  1318. },
  1319. // decelerating to zero velocity
  1320. easeOutQuart: function (t) {
  1321. return 1 - (--t) * t * t * t
  1322. },
  1323. // acceleration until halfway, then deceleration
  1324. easeInOutQuart: function (t) {
  1325. return t < .5 ? 8 * t * t * t * t : 1 - 8 * (--t) * t * t * t
  1326. },
  1327. // accelerating from zero velocity
  1328. easeInQuint: function (t) {
  1329. return t * t * t * t * t
  1330. },
  1331. // decelerating to zero velocity
  1332. easeOutQuint: function (t) {
  1333. return 1 + (--t) * t * t * t * t
  1334. },
  1335. // acceleration until halfway, then deceleration
  1336. easeInOutQuint: function (t) {
  1337. return t < .5 ? 16 * t * t * t * t * t : 1 + 16 * (--t) * t * t * t * t
  1338. }
  1339. };
  1340. exports.getScrollBarWidth = function () {
  1341. var inner = document.createElement('p');
  1342. inner.style.width = "100%";
  1343. inner.style.height = "200px";
  1344. var outer = document.createElement('div');
  1345. outer.style.position = "absolute";
  1346. outer.style.top = "0px";
  1347. outer.style.left = "0px";
  1348. outer.style.visibility = "hidden";
  1349. outer.style.width = "200px";
  1350. outer.style.height = "150px";
  1351. outer.style.overflow = "hidden";
  1352. outer.appendChild (inner);
  1353. document.body.appendChild (outer);
  1354. var w1 = inner.offsetWidth;
  1355. outer.style.overflow = 'scroll';
  1356. var w2 = inner.offsetWidth;
  1357. if (w1 == w2) w2 = outer.clientWidth;
  1358. document.body.removeChild (outer);
  1359. return (w1 - w2);
  1360. };
  1361. exports.topMost = function (pile, accessors) {
  1362. let candidate;
  1363. if (!Array.isArray(accessors)) {
  1364. accessors = [accessors];
  1365. }
  1366. for (const member of pile) {
  1367. if (member) {
  1368. candidate = member[accessors[0]];
  1369. for (let i = 1; i < accessors.length; i++){
  1370. if (candidate) {
  1371. candidate = candidate[accessors[i]]
  1372. } else {
  1373. continue;
  1374. }
  1375. }
  1376. if (candidate) {
  1377. break;
  1378. }
  1379. }
  1380. }
  1381. return candidate;
  1382. };