utils.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412
  1. // Utilities for basic tasks in javascript.
  2. $exists: function $exists(el) {
  3. return typeof el !== "undefined";
  4. };
  5. $fcd: function $fcd(obj, fn) {
  6. return function () { fn.apply(obj, arguments); }
  7. };
  8. $fcb: function $fcb(obj, fn, args) {
  9. return function () {
  10. args = args.concat(arguments);
  11. fn.apply(obj, args);
  12. }
  13. };
  14. $getEl = function $getEl(id) {
  15. return document.getElementById(id);
  16. };
  17. $addCssClass = function $addCssClass(el, cl) {
  18. //TODO: a spurious space sometimes appears at the beginning of className
  19. //avoid using classList for cross-browser support
  20. arr = el.className.split(" ");
  21. if (arr.indexOf(cl) === -1) {
  22. el.className += " " + cl;
  23. }
  24. };
  25. $removeCssClass = function $removeCssClass(el, cl) {
  26. //avoid using classList for cross-browser support
  27. arr = el.className.split(" ");
  28. index = arr.indexOf(cl);
  29. if (arr.indexOf(cl) !== -1) {
  30. arr.splice(index, 1);
  31. el.className = arr.join(" ");
  32. }
  33. };
  34. $addHandler = function $addHandler(el, event, del) {
  35. if (el.addEventListener) {
  36. el.addEventListener(event, del);
  37. }
  38. else if (el.attachEvent) {
  39. el.attachEvent(event, del);
  40. }
  41. else {
  42. throw "Element has no event listeners."
  43. }
  44. };
  45. $removeHandler = function $removeHandler(el, event, del) {
  46. if (el.removeEventListener) {
  47. el.removeEventListener(event, del);
  48. }
  49. else if (el.detachEvent) {
  50. el.detachEvent(event, del);
  51. }
  52. else {
  53. throw "Element has no event listeners."
  54. }
  55. };
  56. $setInheritance = function $setInheritance(base, child) {
  57. for (var key in base.prototype) {
  58. if (!child.prototype.key) {
  59. child.prototype.key = base.prototype.key
  60. }
  61. }
  62. };
  63. $getVerticalScroll = function $getVerticalScroll() {
  64. if (typeof (window.pageYOffset == 'number')) {
  65. return window.pageYOffset;
  66. }
  67. else if (document.body && document.body.scrollTop) { //note: sometimes when it just doesn't work, scrollTop is just permanently 0, so accept 0 as "false".
  68. return document.body.scrollTop;
  69. }
  70. else if (document.documentElement && document.documentElement.scrollTop) {
  71. return document.documentElement.scrollTop;
  72. }
  73. else {
  74. return 0;
  75. }
  76. };
  77. $setVerticalScroll = function $setVerticalScroll(val) {
  78. window.scrollTo(0, val);
  79. };
  80. $enableScrollBars = function $enableScrollBars() {
  81. if (document.documentElement) {
  82. document.documentElement.style.overflow = "auto";
  83. }
  84. if (document.body) {
  85. document.body.scroll = "yes";
  86. }
  87. };
  88. $disableScrollBars = function $disableScrollBars() {
  89. if (document.documentElement) {
  90. document.documentElement.style.overflow = "hidden";
  91. }
  92. if (document.body) {
  93. document.body.scroll = "no";
  94. }
  95. };
  96. $newDiv = function $newDiv(id, cssClasses) {
  97. var div = document.createElement("div");
  98. if ($exists(id)) {
  99. div.setAttribute("id", id);
  100. }
  101. if (Array.isArray(cssClasses)) {
  102. cssClasses.forEach(function (item) {
  103. $addCssClass(div, item);
  104. });
  105. }
  106. return div;
  107. };
  108. $arrayEquality = function $arrayEquality(arr1, arr2) {
  109. if (arr1.length !== arr2.length) { return false; }
  110. for (var i = 0; i < arr1.length; i++) {
  111. if (arr1[i] !== arr2[i]) { return false; }
  112. }
  113. return true;
  114. };
  115. $setCookie: function $setCookie(obj, expiration, path) {
  116. //adds the kvps from the object to the current cookie
  117. var kv;
  118. var cookie = "";
  119. for (var key in obj) { //set fresh values
  120. kv = key + "=" + encodeURIComponent(obj[key]) + ";";
  121. cookie += kv;
  122. }
  123. cookie += "expires=" + expiration + ";"; //need to handle these separately bc we don't want to encode
  124. cookie += "path=" + path + ";";
  125. document.cookie = cookie;
  126. };
  127. $readCookie: function $readCookie() {
  128. if (!document.cookie) { return {}; }
  129. var obj = {};
  130. var cookiePairs = document.cookie.split(";");
  131. var kv;
  132. for (var i = 0; i < cookiePairs.length; i++) {
  133. kv = cookiePairs[i].split("=");
  134. obj[kv[0]] = decodeURIComponent(kv[1]);
  135. }
  136. return obj;
  137. };
  138. $leftPad: function $leftPad(str) {
  139. str = str + "";
  140. return ("0000" + str).substring(str.length);
  141. }
  142. function Timer() {
  143. //handles tasks delayed by a fixed amount of time
  144. this._queue = [];
  145. this.__updateDel = $fcd(this, this._update);
  146. };
  147. Timer.prototype = {
  148. _queue: null,
  149. __timeout: 0,
  150. __timeoutId: null, //also doubles as a flag for whether this is currently running
  151. __updateDel: null,
  152. set: function set(callback, timeout) {
  153. this._queue.push({
  154. 'callback': callback,
  155. 'timeout': timeout
  156. });
  157. if (timeout < this.__timeout || this.__timeoutId === null) {
  158. this.__timeout = timeout;
  159. if (this.__timeoutId) {
  160. clearTimeout(this.__timeoutId);
  161. }
  162. this.__timeoutId = setTimeout(this.__updateDel, timeout);
  163. };
  164. },
  165. isQueued: function isQueued(callback) {
  166. var arr = this._queue.filter(el => el.callback === callback);
  167. return arr.length > 0;
  168. },
  169. clear: function clear() {
  170. this._queue = [];
  171. this.__timeout = 0;
  172. clearTimeout(this.__timeoutId);
  173. this.__timeoutId = null;
  174. },
  175. _update: function __update() {
  176. this.__runQueue();
  177. if (this._queue.length > 0) {
  178. timeout = this.__findTimeout();
  179. this.__timeout = timeout;
  180. this.__timeoutId = setTimeout(this.__updateDel, timeout);
  181. }
  182. else {
  183. this.__timeout = 0;
  184. this.__timeoutId = null;
  185. }
  186. },
  187. __runQueue: function __runQueue() {
  188. for (var i = 0; i < this._queue.length; i++) { //use boring old for fn bc we need "this" in scope
  189. var el = this._queue[i];
  190. el.timeout = el.timeout - this.__timeout;
  191. if (el.timeout <= 0) {
  192. el.callback();
  193. this._queue.splice(i, 1);
  194. }
  195. };
  196. },
  197. __findTimeout: function __findTimeout() {
  198. var timeouts = this._queue.map(el => el.timeout);
  199. return Math.min.apply(null, timeouts);
  200. },
  201. };
  202. function Sequencer() {
  203. //handles tasks that must be done in a certain order
  204. this._queue = [];
  205. this.__updateDel = $fcd(this, this._update);
  206. this.__listeners = [];
  207. };
  208. Sequencer.prototype = {
  209. _queue: null,
  210. __timeoutId: null, //also doubles as a flag for whether this is currently running
  211. __updateDel: null,
  212. __listeners: null,
  213. add: function add(callback, delay) {
  214. this._queue.push({
  215. 'callback': callback,
  216. 'delay': delay //time to wait after previous step is done before executing this one
  217. });
  218. if (this.__timeoutId === null) {
  219. this.__timeoutId = setTimeout(this.__updateDel, delay);
  220. };
  221. },
  222. declareBlock: function declareBlock(blockName) {
  223. this._queue.push(blockName);
  224. },
  225. removeBlock: function removeBlock(blockName) {
  226. // Removes the block of queued callbacks from the first block declaration of the given name, up to the next block declaration.
  227. // If no parameters are passed, removes the first block that isn't currently underway.
  228. // Returns true if a block was removed; false if no such block is queued.
  229. var start = -1;
  230. var end = -1;
  231. var i = 0;
  232. while (i < this._queue.length && start === -1) {
  233. if (blockName) {
  234. if (this._queue[i] === blockName) { start = i; }
  235. }
  236. else {
  237. if (this.__isBlockLabel(this._queue[i])) { start = i; }
  238. }
  239. i++;
  240. }
  241. while (i < this._queue.length && end === -1) {
  242. if (this.__isBlockLabel(this._queue[i])) { end = i; }
  243. i++;
  244. }
  245. if (start !== -1) {
  246. //block found
  247. if (end !== -1) {
  248. //there is a subsequent block declaration
  249. this._queue.splice(start, (end - start));
  250. }
  251. else {
  252. //this block takes up the whole rest of the queue
  253. this._queue = this._queue.slice(0, start);
  254. }
  255. return true;
  256. }
  257. else {
  258. return false;
  259. }
  260. },
  261. removeBlocksOtherThan: function removeBlocksOtherThan(blockName) {
  262. // Removes all queued blocks of callbacks other than the first one with the given name.
  263. // Note that any singletons at the front of the queue will still be processed.
  264. // Returns 1 if the block was the first block to appear; -1 if the block was not the first block to appear; 0 if nothing was removed
  265. debugger;
  266. var preserveStart = -1;
  267. var preserveEnd = -1;
  268. var cutStart = -1;
  269. var i = 0;
  270. while (i < this._queue.length && (preserveStart === -1 || cutStart === -1 || preserveEnd === -1)) {
  271. if (preserveStart === -1 && this._queue[i] === blockName) { preserveStart = i; }
  272. else if (this.__isBlockLabel(this._queue[i])) {
  273. if (preserveStart !== -1 && preserveEnd === -1) {
  274. preserveEnd = i;
  275. }
  276. if (cutStart === -1) {
  277. cutStart = i;
  278. }
  279. }
  280. i++;
  281. }
  282. // there are two distinct cases:
  283. // 1) xxx ______ xxxxx _______ <-- unwanted blocks exist between singletons and target block
  284. // 2) xxx xxxx _______________ <-- target block is right after singletons
  285. var somethingCut = 0;
  286. if (preserveEnd !== -1) { //either way, we can cut off the tail (if it exists)
  287. this._queue = this._queue.slice(0, preserveEnd);
  288. somethingCut = 1;
  289. } //if we're in case 2, we're done
  290. if (cutStart < preserveStart || preserveStart === -1) {
  291. this._queue.splice(cutStart, (preserveStart - cutStart));
  292. somethingCut = -1;
  293. }
  294. return somethingCut;
  295. },
  296. clear: function clear() {
  297. this._queue = [];
  298. clearTimeout(this.__timeoutId);
  299. this.__timeoutId = null;
  300. },
  301. addBlockListener: function addBlockListener(del) {
  302. //todo: theoretically this api should include something to remove the listener as well, but I have no need for that.
  303. this.__listeners.push(del);
  304. },
  305. hasQueuedItems: function hasQueuedItems() {
  306. return !!this.__nextQueueItem();
  307. },
  308. __raiseBlockEvent: function __raiseBlockEvent(blockName) {
  309. this.__listeners.forEach(function (el) {
  310. el.call(null, blockName);
  311. });
  312. },
  313. _update: function __update() {
  314. var timeout = 0;
  315. //handle next step
  316. while (timeout <= 0 && this._queue.length > 0) {
  317. if (this.__isBlockLabel(this._queue[0])) {
  318. this.__raiseBlockEvent(this._queue[0]);
  319. this._queue.splice(0, 1);
  320. //timeout = 0;
  321. continue;
  322. }
  323. else { //this is an actual item in the queue
  324. //execute step
  325. this._queue[0].callback();
  326. this._queue.splice(0, 1);
  327. //check next step's delay
  328. if (this._queue.length > 0) {
  329. var next = this.__nextQueueItem();
  330. if (next) {
  331. timeout = next.delay;
  332. } else {
  333. timeout = 0;
  334. }
  335. }
  336. }
  337. }
  338. if (timeout > 0 && this._queue.length > 0) {
  339. this.__timeoutId = setTimeout(this.__updateDel, timeout);
  340. }
  341. else {
  342. this.__timeoutId = null;
  343. }
  344. },
  345. __isBlockLabel: function __isBlockLabel(queueItem) {
  346. return typeof queueItem === "string";
  347. },
  348. __nextQueueItem: function __nextQueueItem() {
  349. for (var i = 0; i < this._queue.length; i++) {
  350. if (!this.__isBlockLabel(this._queue[i])) {
  351. return this._queue[i];
  352. }
  353. }
  354. return null;
  355. },
  356. };