|
1 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
2 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
4 |
|
5 "use strict"; |
|
6 |
|
7 // ********** |
|
8 // Title: utils.js |
|
9 |
|
10 this.EXPORTED_SYMBOLS = ["Point", "Rect", "Range", "Subscribable", "Utils", "MRUList"]; |
|
11 |
|
12 // ######### |
|
13 const Ci = Components.interfaces; |
|
14 const Cu = Components.utils; |
|
15 |
|
16 Cu.import("resource://gre/modules/Services.jsm"); |
|
17 |
|
18 // ########## |
|
19 // Class: Point |
|
20 // A simple point. |
|
21 // |
|
22 // Constructor: Point |
|
23 // If a is a Point, creates a copy of it. Otherwise, expects a to be x, |
|
24 // and creates a Point with it along with y. If either a or y are omitted, |
|
25 // 0 is used in their place. |
|
26 this.Point = function Point(a, y) { |
|
27 if (Utils.isPoint(a)) { |
|
28 this.x = a.x; |
|
29 this.y = a.y; |
|
30 } else { |
|
31 this.x = (Utils.isNumber(a) ? a : 0); |
|
32 this.y = (Utils.isNumber(y) ? y : 0); |
|
33 } |
|
34 }; |
|
35 |
|
36 Point.prototype = { |
|
37 // ---------- |
|
38 // Function: toString |
|
39 // Prints [Point (x,y)] for debug use |
|
40 toString: function Point_toString() { |
|
41 return "[Point (" + this.x + "," + this.y + ")]"; |
|
42 }, |
|
43 |
|
44 // ---------- |
|
45 // Function: distance |
|
46 // Returns the distance from this point to the given <Point>. |
|
47 distance: function Point_distance(point) { |
|
48 var ax = this.x - point.x; |
|
49 var ay = this.y - point.y; |
|
50 return Math.sqrt((ax * ax) + (ay * ay)); |
|
51 } |
|
52 }; |
|
53 |
|
54 // ########## |
|
55 // Class: Rect |
|
56 // A simple rectangle. Note that in addition to the left and width, it also has |
|
57 // a right property; changing one affects the others appropriately. Same for the |
|
58 // vertical properties. |
|
59 // |
|
60 // Constructor: Rect |
|
61 // If a is a Rect, creates a copy of it. Otherwise, expects a to be left, |
|
62 // and creates a Rect with it along with top, width, and height. |
|
63 this.Rect = function Rect(a, top, width, height) { |
|
64 // Note: perhaps 'a' should really be called 'rectOrLeft' |
|
65 if (Utils.isRect(a)) { |
|
66 this.left = a.left; |
|
67 this.top = a.top; |
|
68 this.width = a.width; |
|
69 this.height = a.height; |
|
70 } else { |
|
71 this.left = a; |
|
72 this.top = top; |
|
73 this.width = width; |
|
74 this.height = height; |
|
75 } |
|
76 }; |
|
77 |
|
78 Rect.prototype = { |
|
79 // ---------- |
|
80 // Function: toString |
|
81 // Prints [Rect (left,top,width,height)] for debug use |
|
82 toString: function Rect_toString() { |
|
83 return "[Rect (" + this.left + "," + this.top + "," + |
|
84 this.width + "," + this.height + ")]"; |
|
85 }, |
|
86 |
|
87 get right() this.left + this.width, |
|
88 set right(value) { |
|
89 this.width = value - this.left; |
|
90 }, |
|
91 |
|
92 get bottom() this.top + this.height, |
|
93 set bottom(value) { |
|
94 this.height = value - this.top; |
|
95 }, |
|
96 |
|
97 // ---------- |
|
98 // Variable: xRange |
|
99 // Gives you a new <Range> for the horizontal dimension. |
|
100 get xRange() new Range(this.left, this.right), |
|
101 |
|
102 // ---------- |
|
103 // Variable: yRange |
|
104 // Gives you a new <Range> for the vertical dimension. |
|
105 get yRange() new Range(this.top, this.bottom), |
|
106 |
|
107 // ---------- |
|
108 // Function: intersects |
|
109 // Returns true if this rectangle intersects the given <Rect>. |
|
110 intersects: function Rect_intersects(rect) { |
|
111 return (rect.right > this.left && |
|
112 rect.left < this.right && |
|
113 rect.bottom > this.top && |
|
114 rect.top < this.bottom); |
|
115 }, |
|
116 |
|
117 // ---------- |
|
118 // Function: intersection |
|
119 // Returns a new <Rect> with the intersection of this rectangle and the give <Rect>, |
|
120 // or null if they don't intersect. |
|
121 intersection: function Rect_intersection(rect) { |
|
122 var box = new Rect(Math.max(rect.left, this.left), Math.max(rect.top, this.top), 0, 0); |
|
123 box.right = Math.min(rect.right, this.right); |
|
124 box.bottom = Math.min(rect.bottom, this.bottom); |
|
125 if (box.width > 0 && box.height > 0) |
|
126 return box; |
|
127 |
|
128 return null; |
|
129 }, |
|
130 |
|
131 // ---------- |
|
132 // Function: contains |
|
133 // Returns a boolean denoting if the <Rect> or <Point> is contained inside |
|
134 // this rectangle. |
|
135 // |
|
136 // Parameters |
|
137 // - A <Rect> or a <Point> |
|
138 contains: function Rect_contains(a) { |
|
139 if (Utils.isPoint(a)) |
|
140 return (a.x > this.left && |
|
141 a.x < this.right && |
|
142 a.y > this.top && |
|
143 a.y < this.bottom); |
|
144 |
|
145 return (a.left >= this.left && |
|
146 a.right <= this.right && |
|
147 a.top >= this.top && |
|
148 a.bottom <= this.bottom); |
|
149 }, |
|
150 |
|
151 // ---------- |
|
152 // Function: center |
|
153 // Returns a new <Point> with the center location of this rectangle. |
|
154 center: function Rect_center() { |
|
155 return new Point(this.left + (this.width / 2), this.top + (this.height / 2)); |
|
156 }, |
|
157 |
|
158 // ---------- |
|
159 // Function: size |
|
160 // Returns a new <Point> with the dimensions of this rectangle. |
|
161 size: function Rect_size() { |
|
162 return new Point(this.width, this.height); |
|
163 }, |
|
164 |
|
165 // ---------- |
|
166 // Function: position |
|
167 // Returns a new <Point> with the top left of this rectangle. |
|
168 position: function Rect_position() { |
|
169 return new Point(this.left, this.top); |
|
170 }, |
|
171 |
|
172 // ---------- |
|
173 // Function: area |
|
174 // Returns the area of this rectangle. |
|
175 area: function Rect_area() { |
|
176 return this.width * this.height; |
|
177 }, |
|
178 |
|
179 // ---------- |
|
180 // Function: inset |
|
181 // Makes the rect smaller (if the arguments are positive) as if a margin is added all around |
|
182 // the initial rect, with the margin widths (symmetric) being specified by the arguments. |
|
183 // |
|
184 // Paramaters |
|
185 // - A <Point> or two arguments: x and y |
|
186 inset: function Rect_inset(a, b) { |
|
187 if (Utils.isPoint(a)) { |
|
188 b = a.y; |
|
189 a = a.x; |
|
190 } |
|
191 |
|
192 this.left += a; |
|
193 this.width -= a * 2; |
|
194 this.top += b; |
|
195 this.height -= b * 2; |
|
196 }, |
|
197 |
|
198 // ---------- |
|
199 // Function: offset |
|
200 // Moves (translates) the rect by the given vector. |
|
201 // |
|
202 // Paramaters |
|
203 // - A <Point> or two arguments: x and y |
|
204 offset: function Rect_offset(a, b) { |
|
205 if (Utils.isPoint(a)) { |
|
206 this.left += a.x; |
|
207 this.top += a.y; |
|
208 } else { |
|
209 this.left += a; |
|
210 this.top += b; |
|
211 } |
|
212 }, |
|
213 |
|
214 // ---------- |
|
215 // Function: equals |
|
216 // Returns true if this rectangle is identical to the given <Rect>. |
|
217 equals: function Rect_equals(rect) { |
|
218 return (rect.left == this.left && |
|
219 rect.top == this.top && |
|
220 rect.width == this.width && |
|
221 rect.height == this.height); |
|
222 }, |
|
223 |
|
224 // ---------- |
|
225 // Function: union |
|
226 // Returns a new <Rect> with the union of this rectangle and the given <Rect>. |
|
227 union: function Rect_union(a) { |
|
228 var newLeft = Math.min(a.left, this.left); |
|
229 var newTop = Math.min(a.top, this.top); |
|
230 var newWidth = Math.max(a.right, this.right) - newLeft; |
|
231 var newHeight = Math.max(a.bottom, this.bottom) - newTop; |
|
232 var newRect = new Rect(newLeft, newTop, newWidth, newHeight); |
|
233 |
|
234 return newRect; |
|
235 }, |
|
236 |
|
237 // ---------- |
|
238 // Function: copy |
|
239 // Copies the values of the given <Rect> into this rectangle. |
|
240 copy: function Rect_copy(a) { |
|
241 this.left = a.left; |
|
242 this.top = a.top; |
|
243 this.width = a.width; |
|
244 this.height = a.height; |
|
245 } |
|
246 }; |
|
247 |
|
248 // ########## |
|
249 // Class: Range |
|
250 // A physical interval, with a min and max. |
|
251 // |
|
252 // Constructor: Range |
|
253 // Creates a Range with the given min and max |
|
254 this.Range = function Range(min, max) { |
|
255 if (Utils.isRange(min) && !max) { // if the one variable given is a range, copy it. |
|
256 this.min = min.min; |
|
257 this.max = min.max; |
|
258 } else { |
|
259 this.min = min || 0; |
|
260 this.max = max || 0; |
|
261 } |
|
262 }; |
|
263 |
|
264 Range.prototype = { |
|
265 // ---------- |
|
266 // Function: toString |
|
267 // Prints [Range (min,max)] for debug use |
|
268 toString: function Range_toString() { |
|
269 return "[Range (" + this.min + "," + this.max + ")]"; |
|
270 }, |
|
271 |
|
272 // Variable: extent |
|
273 // Equivalent to max-min |
|
274 get extent() { |
|
275 return (this.max - this.min); |
|
276 }, |
|
277 |
|
278 set extent(extent) { |
|
279 this.max = extent - this.min; |
|
280 }, |
|
281 |
|
282 // ---------- |
|
283 // Function: contains |
|
284 // Whether the <Range> contains the given <Range> or value or not. |
|
285 // |
|
286 // Parameters |
|
287 // - a number or <Range> |
|
288 contains: function Range_contains(value) { |
|
289 if (Utils.isNumber(value)) |
|
290 return value >= this.min && value <= this.max; |
|
291 if (Utils.isRange(value)) |
|
292 return value.min >= this.min && value.max <= this.max; |
|
293 return false; |
|
294 }, |
|
295 |
|
296 // ---------- |
|
297 // Function: overlaps |
|
298 // Whether the <Range> overlaps with the given <Range> value or not. |
|
299 // |
|
300 // Parameters |
|
301 // - a number or <Range> |
|
302 overlaps: function Range_overlaps(value) { |
|
303 if (Utils.isNumber(value)) |
|
304 return this.contains(value); |
|
305 if (Utils.isRange(value)) |
|
306 return !(value.max < this.min || this.max < value.min); |
|
307 return false; |
|
308 }, |
|
309 |
|
310 // ---------- |
|
311 // Function: proportion |
|
312 // Maps the given value to the range [0,1], so that it returns 0 if the value is <= the min, |
|
313 // returns 1 if the value >= the max, and returns an interpolated "proportion" in (min, max). |
|
314 // |
|
315 // Parameters |
|
316 // - a number |
|
317 // - (bool) smooth? If true, a smooth tanh-based function will be used instead of the linear. |
|
318 proportion: function Range_proportion(value, smooth) { |
|
319 if (value <= this.min) |
|
320 return 0; |
|
321 if (this.max <= value) |
|
322 return 1; |
|
323 |
|
324 var proportion = (value - this.min) / this.extent; |
|
325 |
|
326 if (smooth) { |
|
327 // The ease function ".5+.5*Math.tanh(4*x-2)" is a pretty |
|
328 // little graph. It goes from near 0 at x=0 to near 1 at x=1 |
|
329 // smoothly and beautifully. |
|
330 // http://www.wolframalpha.com/input/?i=.5+%2B+.5+*+tanh%28%284+*+x%29+-+2%29 |
|
331 let tanh = function tanh(x) { |
|
332 var e = Math.exp(x); |
|
333 return (e - 1/e) / (e + 1/e); |
|
334 }; |
|
335 |
|
336 return .5 - .5 * tanh(2 - 4 * proportion); |
|
337 } |
|
338 |
|
339 return proportion; |
|
340 }, |
|
341 |
|
342 // ---------- |
|
343 // Function: scale |
|
344 // Takes the given value in [0,1] and maps it to the associated value on the Range. |
|
345 // |
|
346 // Parameters |
|
347 // - a number in [0,1] |
|
348 scale: function Range_scale(value) { |
|
349 if (value > 1) |
|
350 value = 1; |
|
351 if (value < 0) |
|
352 value = 0; |
|
353 return this.min + this.extent * value; |
|
354 } |
|
355 }; |
|
356 |
|
357 // ########## |
|
358 // Class: Subscribable |
|
359 // A mix-in for allowing objects to collect subscribers for custom events. |
|
360 this.Subscribable = function Subscribable() { |
|
361 this.subscribers = null; |
|
362 }; |
|
363 |
|
364 Subscribable.prototype = { |
|
365 // ---------- |
|
366 // Function: addSubscriber |
|
367 // The given callback will be called when the Subscribable fires the given event. |
|
368 addSubscriber: function Subscribable_addSubscriber(eventName, callback) { |
|
369 try { |
|
370 Utils.assertThrow(typeof callback == "function", "callback must be a function"); |
|
371 Utils.assertThrow(eventName && typeof eventName == "string", |
|
372 "eventName must be a non-empty string"); |
|
373 } catch(e) { |
|
374 Utils.log(e); |
|
375 return; |
|
376 } |
|
377 |
|
378 if (!this.subscribers) |
|
379 this.subscribers = {}; |
|
380 |
|
381 if (!this.subscribers[eventName]) |
|
382 this.subscribers[eventName] = []; |
|
383 |
|
384 let subscribers = this.subscribers[eventName]; |
|
385 if (subscribers.indexOf(callback) == -1) |
|
386 subscribers.push(callback); |
|
387 }, |
|
388 |
|
389 // ---------- |
|
390 // Function: removeSubscriber |
|
391 // Removes the subscriber associated with the event for the given callback. |
|
392 removeSubscriber: function Subscribable_removeSubscriber(eventName, callback) { |
|
393 try { |
|
394 Utils.assertThrow(typeof callback == "function", "callback must be a function"); |
|
395 Utils.assertThrow(eventName && typeof eventName == "string", |
|
396 "eventName must be a non-empty string"); |
|
397 } catch(e) { |
|
398 Utils.log(e); |
|
399 return; |
|
400 } |
|
401 |
|
402 if (!this.subscribers || !this.subscribers[eventName]) |
|
403 return; |
|
404 |
|
405 let subscribers = this.subscribers[eventName]; |
|
406 let index = subscribers.indexOf(callback); |
|
407 |
|
408 if (index > -1) |
|
409 subscribers.splice(index, 1); |
|
410 }, |
|
411 |
|
412 // ---------- |
|
413 // Function: _sendToSubscribers |
|
414 // Internal routine. Used by the Subscribable to fire events. |
|
415 _sendToSubscribers: function Subscribable__sendToSubscribers(eventName, eventInfo) { |
|
416 try { |
|
417 Utils.assertThrow(eventName && typeof eventName == "string", |
|
418 "eventName must be a non-empty string"); |
|
419 } catch(e) { |
|
420 Utils.log(e); |
|
421 return; |
|
422 } |
|
423 |
|
424 if (!this.subscribers || !this.subscribers[eventName]) |
|
425 return; |
|
426 |
|
427 let subsCopy = this.subscribers[eventName].concat(); |
|
428 subsCopy.forEach(function (callback) { |
|
429 try { |
|
430 callback(this, eventInfo); |
|
431 } catch(e) { |
|
432 Utils.log(e); |
|
433 } |
|
434 }, this); |
|
435 } |
|
436 }; |
|
437 |
|
438 // ########## |
|
439 // Class: Utils |
|
440 // Singelton with common utility functions. |
|
441 this.Utils = { |
|
442 // ---------- |
|
443 // Function: toString |
|
444 // Prints [Utils] for debug use |
|
445 toString: function Utils_toString() { |
|
446 return "[Utils]"; |
|
447 }, |
|
448 |
|
449 // ___ Logging |
|
450 useConsole: true, // as opposed to dump |
|
451 showTime: false, |
|
452 |
|
453 // ---------- |
|
454 // Function: log |
|
455 // Prints the given arguments to the JavaScript error console as a message. |
|
456 // Pass as many arguments as you want, it'll print them all. |
|
457 log: function Utils_log() { |
|
458 var text = this.expandArgumentsForLog(arguments); |
|
459 var prefix = this.showTime ? Date.now() + ': ' : ''; |
|
460 if (this.useConsole) |
|
461 Services.console.logStringMessage(prefix + text); |
|
462 else |
|
463 dump(prefix + text + '\n'); |
|
464 }, |
|
465 |
|
466 // ---------- |
|
467 // Function: error |
|
468 // Prints the given arguments to the JavaScript error console as an error. |
|
469 // Pass as many arguments as you want, it'll print them all. |
|
470 error: function Utils_error() { |
|
471 var text = this.expandArgumentsForLog(arguments); |
|
472 var prefix = this.showTime ? Date.now() + ': ' : ''; |
|
473 if (this.useConsole) |
|
474 Cu.reportError(prefix + "tabview error: " + text); |
|
475 else |
|
476 dump(prefix + "TABVIEW ERROR: " + text + '\n'); |
|
477 }, |
|
478 |
|
479 // ---------- |
|
480 // Function: trace |
|
481 // Prints the given arguments to the JavaScript error console as a message, |
|
482 // along with a full stack trace. |
|
483 // Pass as many arguments as you want, it'll print them all. |
|
484 trace: function Utils_trace() { |
|
485 var text = this.expandArgumentsForLog(arguments); |
|
486 |
|
487 // cut off the first line of the stack trace, because that's just this function. |
|
488 let stack = Error().stack.split("\n").slice(1); |
|
489 |
|
490 // if the caller was assert, cut out the line for the assert function as well. |
|
491 if (stack[0].indexOf("Utils_assert(") == 0) |
|
492 stack.splice(0, 1); |
|
493 |
|
494 this.log('trace: ' + text + '\n' + stack.join("\n")); |
|
495 }, |
|
496 |
|
497 // ---------- |
|
498 // Function: assert |
|
499 // Prints a stack trace along with label (as a console message) if condition is false. |
|
500 assert: function Utils_assert(condition, label) { |
|
501 if (!condition) { |
|
502 let text; |
|
503 if (typeof label != 'string') |
|
504 text = 'badly formed assert'; |
|
505 else |
|
506 text = "tabview assert: " + label; |
|
507 |
|
508 this.trace(text); |
|
509 } |
|
510 }, |
|
511 |
|
512 // ---------- |
|
513 // Function: assertThrow |
|
514 // Throws label as an exception if condition is false. |
|
515 assertThrow: function Utils_assertThrow(condition, label) { |
|
516 if (!condition) { |
|
517 let text; |
|
518 if (typeof label != 'string') |
|
519 text = 'badly formed assert'; |
|
520 else |
|
521 text = "tabview assert: " + label; |
|
522 |
|
523 // cut off the first line of the stack trace, because that's just this function. |
|
524 let stack = Error().stack.split("\n").slice(1); |
|
525 |
|
526 throw text + "\n" + stack.join("\n"); |
|
527 } |
|
528 }, |
|
529 |
|
530 // ---------- |
|
531 // Function: expandObject |
|
532 // Prints the given object to a string, including all of its properties. |
|
533 expandObject: function Utils_expandObject(obj) { |
|
534 var s = obj + ' = {'; |
|
535 for (let prop in obj) { |
|
536 let value; |
|
537 try { |
|
538 value = obj[prop]; |
|
539 } catch(e) { |
|
540 value = '[!!error retrieving property]'; |
|
541 } |
|
542 |
|
543 s += prop + ': '; |
|
544 if (typeof value == 'string') |
|
545 s += '\'' + value + '\''; |
|
546 else if (typeof value == 'function') |
|
547 s += 'function'; |
|
548 else |
|
549 s += value; |
|
550 |
|
551 s += ', '; |
|
552 } |
|
553 return s + '}'; |
|
554 }, |
|
555 |
|
556 // ---------- |
|
557 // Function: expandArgumentsForLog |
|
558 // Expands all of the given args (an array) into a single string. |
|
559 expandArgumentsForLog: function Utils_expandArgumentsForLog(args) { |
|
560 var that = this; |
|
561 return Array.map(args, function(arg) { |
|
562 return typeof arg == 'object' ? that.expandObject(arg) : arg; |
|
563 }).join('; '); |
|
564 }, |
|
565 |
|
566 // ___ Misc |
|
567 |
|
568 // ---------- |
|
569 // Function: isLeftClick |
|
570 // Given a DOM mouse event, returns true if it was for the left mouse button. |
|
571 isLeftClick: function Utils_isLeftClick(event) { |
|
572 return event.button == 0; |
|
573 }, |
|
574 |
|
575 // ---------- |
|
576 // Function: isMiddleClick |
|
577 // Given a DOM mouse event, returns true if it was for the middle mouse button. |
|
578 isMiddleClick: function Utils_isMiddleClick(event) { |
|
579 return event.button == 1; |
|
580 }, |
|
581 |
|
582 // ---------- |
|
583 // Function: isRightClick |
|
584 // Given a DOM mouse event, returns true if it was for the right mouse button. |
|
585 isRightClick: function Utils_isRightClick(event) { |
|
586 return event.button == 2; |
|
587 }, |
|
588 |
|
589 // ---------- |
|
590 // Function: isDOMElement |
|
591 // Returns true if the given object is a DOM element. |
|
592 isDOMElement: function Utils_isDOMElement(object) { |
|
593 return object instanceof Ci.nsIDOMElement; |
|
594 }, |
|
595 |
|
596 // ---------- |
|
597 // Function: isValidXULTab |
|
598 // A xulTab is valid if it has not been closed, |
|
599 // and it has not been removed from the DOM |
|
600 // Returns true if the tab is valid. |
|
601 isValidXULTab: function Utils_isValidXULTab(xulTab) { |
|
602 return !xulTab.closing && xulTab.parentNode; |
|
603 }, |
|
604 |
|
605 // ---------- |
|
606 // Function: isNumber |
|
607 // Returns true if the argument is a valid number. |
|
608 isNumber: function Utils_isNumber(n) { |
|
609 return typeof n == 'number' && !isNaN(n); |
|
610 }, |
|
611 |
|
612 // ---------- |
|
613 // Function: isRect |
|
614 // Returns true if the given object (r) looks like a <Rect>. |
|
615 isRect: function Utils_isRect(r) { |
|
616 return (r && |
|
617 this.isNumber(r.left) && |
|
618 this.isNumber(r.top) && |
|
619 this.isNumber(r.width) && |
|
620 this.isNumber(r.height)); |
|
621 }, |
|
622 |
|
623 // ---------- |
|
624 // Function: isRange |
|
625 // Returns true if the given object (r) looks like a <Range>. |
|
626 isRange: function Utils_isRange(r) { |
|
627 return (r && |
|
628 this.isNumber(r.min) && |
|
629 this.isNumber(r.max)); |
|
630 }, |
|
631 |
|
632 // ---------- |
|
633 // Function: isPoint |
|
634 // Returns true if the given object (p) looks like a <Point>. |
|
635 isPoint: function Utils_isPoint(p) { |
|
636 return (p && this.isNumber(p.x) && this.isNumber(p.y)); |
|
637 }, |
|
638 |
|
639 // ---------- |
|
640 // Function: isPlainObject |
|
641 // Check to see if an object is a plain object (created using "{}" or "new Object"). |
|
642 isPlainObject: function Utils_isPlainObject(obj) { |
|
643 // Must be an Object. |
|
644 // Make sure that DOM nodes and window objects don't pass through, as well |
|
645 if (!obj || Object.prototype.toString.call(obj) !== "[object Object]" || |
|
646 obj.nodeType || obj.setInterval) { |
|
647 return false; |
|
648 } |
|
649 |
|
650 // Not own constructor property must be Object |
|
651 const hasOwnProperty = Object.prototype.hasOwnProperty; |
|
652 |
|
653 if (obj.constructor && |
|
654 !hasOwnProperty.call(obj, "constructor") && |
|
655 !hasOwnProperty.call(obj.constructor.prototype, "isPrototypeOf")) { |
|
656 return false; |
|
657 } |
|
658 |
|
659 // Own properties are enumerated firstly, so to speed up, |
|
660 // if last one is own, then all properties are own. |
|
661 |
|
662 var key; |
|
663 for (key in obj) {} |
|
664 |
|
665 return key === undefined || hasOwnProperty.call(obj, key); |
|
666 }, |
|
667 |
|
668 // ---------- |
|
669 // Function: isEmptyObject |
|
670 // Returns true if the given object has no members. |
|
671 isEmptyObject: function Utils_isEmptyObject(obj) { |
|
672 for (let name in obj) |
|
673 return false; |
|
674 return true; |
|
675 }, |
|
676 |
|
677 // ---------- |
|
678 // Function: copy |
|
679 // Returns a copy of the argument. Note that this is a shallow copy; if the argument |
|
680 // has properties that are themselves objects, those properties will be copied by reference. |
|
681 copy: function Utils_copy(value) { |
|
682 if (value && typeof value == 'object') { |
|
683 if (Array.isArray(value)) |
|
684 return this.extend([], value); |
|
685 return this.extend({}, value); |
|
686 } |
|
687 return value; |
|
688 }, |
|
689 |
|
690 // ---------- |
|
691 // Function: merge |
|
692 // Merge two array-like objects into the first and return it. |
|
693 merge: function Utils_merge(first, second) { |
|
694 Array.forEach(second, function(el) Array.push(first, el)); |
|
695 return first; |
|
696 }, |
|
697 |
|
698 // ---------- |
|
699 // Function: extend |
|
700 // Pass several objects in and it will combine them all into the first object and return it. |
|
701 extend: function Utils_extend() { |
|
702 |
|
703 // copy reference to target object |
|
704 let target = arguments[0] || {}; |
|
705 // Deep copy is not supported |
|
706 if (typeof target === "boolean") { |
|
707 this.assert(false, "The first argument of extend cannot be a boolean." + |
|
708 "Deep copy is not supported."); |
|
709 return target; |
|
710 } |
|
711 |
|
712 // Back when this was in iQ + iQ.fn, so you could extend iQ objects with it. |
|
713 // This is no longer supported. |
|
714 let length = arguments.length; |
|
715 if (length === 1) { |
|
716 this.assert(false, "Extending the iQ prototype using extend is not supported."); |
|
717 return target; |
|
718 } |
|
719 |
|
720 // Handle case when target is a string or something |
|
721 if (typeof target != "object" && typeof target != "function") { |
|
722 target = {}; |
|
723 } |
|
724 |
|
725 for (let i = 1; i < length; i++) { |
|
726 // Only deal with non-null/undefined values |
|
727 let options = arguments[i]; |
|
728 if (options != null) { |
|
729 // Extend the base object |
|
730 for (let name in options) { |
|
731 let copy = options[name]; |
|
732 |
|
733 // Prevent never-ending loop |
|
734 if (target === copy) |
|
735 continue; |
|
736 |
|
737 if (copy !== undefined) |
|
738 target[name] = copy; |
|
739 } |
|
740 } |
|
741 } |
|
742 |
|
743 // Return the modified object |
|
744 return target; |
|
745 }, |
|
746 |
|
747 // ---------- |
|
748 // Function: attempt |
|
749 // Tries to execute a number of functions. Returns immediately the return |
|
750 // value of the first non-failed function without executing successive |
|
751 // functions, or null. |
|
752 attempt: function Utils_attempt() { |
|
753 let args = arguments; |
|
754 |
|
755 for (let i = 0; i < args.length; i++) { |
|
756 try { |
|
757 return args[i](); |
|
758 } catch (e) {} |
|
759 } |
|
760 |
|
761 return null; |
|
762 } |
|
763 }; |
|
764 |
|
765 // ########## |
|
766 // Class: MRUList |
|
767 // A most recently used list. |
|
768 // |
|
769 // Constructor: MRUList |
|
770 // If a is an array of entries, creates a copy of it. |
|
771 this.MRUList = function MRUList(a) { |
|
772 if (Array.isArray(a)) |
|
773 this._list = a.concat(); |
|
774 else |
|
775 this._list = []; |
|
776 }; |
|
777 |
|
778 MRUList.prototype = { |
|
779 // ---------- |
|
780 // Function: toString |
|
781 // Prints [List (entry1, entry2, ...)] for debug use |
|
782 toString: function MRUList_toString() { |
|
783 return "[List (" + this._list.join(", ") + ")]"; |
|
784 }, |
|
785 |
|
786 // ---------- |
|
787 // Function: update |
|
788 // Updates/inserts the given entry as the most recently used one in the list. |
|
789 update: function MRUList_update(entry) { |
|
790 this.remove(entry); |
|
791 this._list.unshift(entry); |
|
792 }, |
|
793 |
|
794 // ---------- |
|
795 // Function: remove |
|
796 // Removes the given entry from the list. |
|
797 remove: function MRUList_remove(entry) { |
|
798 let index = this._list.indexOf(entry); |
|
799 if (index > -1) |
|
800 this._list.splice(index, 1); |
|
801 }, |
|
802 |
|
803 // ---------- |
|
804 // Function: peek |
|
805 // Returns the most recently used entry. If a filter exists, gets the most |
|
806 // recently used entry which matches the filter. |
|
807 peek: function MRUList_peek(filter) { |
|
808 let match = null; |
|
809 if (filter && typeof filter == "function") |
|
810 this._list.some(function MRUList_peek_getEntry(entry) { |
|
811 if (filter(entry)) { |
|
812 match = entry |
|
813 return true; |
|
814 } |
|
815 return false; |
|
816 }); |
|
817 else |
|
818 match = this._list.length > 0 ? this._list[0] : null; |
|
819 |
|
820 return match; |
|
821 }, |
|
822 }; |
|
823 |