util.js 43 KB

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