browser/components/tabview/iq.js

Wed, 31 Dec 2014 06:55:46 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:55:46 +0100
changeset 1
ca08bd8f51b2
permissions
-rw-r--r--

Added tag TORBROWSER_REPLICA for changeset 6474c204b198

     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/. */
     5 // **********
     6 // Title: iq.js
     7 // Various helper functions, in the vein of jQuery.
     9 // ----------
    10 // Function: iQ
    11 // Returns an iQClass object which represents an individual element or a group
    12 // of elements. It works pretty much like jQuery(), with a few exceptions,
    13 // most notably that you can't use strings with complex html,
    14 // just simple tags like '<div>'.
    15 function iQ(selector, context) {
    16   // The iQ object is actually just the init constructor 'enhanced'
    17   return new iQClass(selector, context);
    18 };
    20 // A simple way to check for HTML strings or ID strings
    21 // (both of which we optimize for)
    22 let quickExpr = /^[^<]*(<[\w\W]+>)[^>]*$|^#([\w-]+)$/;
    24 // Match a standalone tag
    25 let rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>)?$/;
    27 // ##########
    28 // Class: iQClass
    29 // The actual class of iQ result objects, representing an individual element
    30 // or a group of elements.
    31 //
    32 // ----------
    33 // Function: iQClass
    34 // You don't call this directly; this is what's called by iQ().
    35 function iQClass(selector, context) {
    37   // Handle $(""), $(null), or $(undefined)
    38   if (!selector) {
    39     return this;
    40   }
    42   // Handle $(DOMElement)
    43   if (selector.nodeType) {
    44     this.context = selector;
    45     this[0] = selector;
    46     this.length = 1;
    47     return this;
    48   }
    50   // The body element only exists once, optimize finding it
    51   if (selector === "body" && !context) {
    52     this.context = document;
    53     this[0] = document.body;
    54     this.selector = "body";
    55     this.length = 1;
    56     return this;
    57   }
    59   // Handle HTML strings
    60   if (typeof selector === "string") {
    61     // Are we dealing with HTML string or an ID?
    63     let match = quickExpr.exec(selector);
    65     // Verify a match, and that no context was specified for #id
    66     if (match && (match[1] || !context)) {
    68       // HANDLE $(html) -> $(array)
    69       if (match[1]) {
    70         let doc = (context ? context.ownerDocument || context : document);
    72         // If a single string is passed in and it's a single tag
    73         // just do a createElement and skip the rest
    74         let ret = rsingleTag.exec(selector);
    76         if (ret) {
    77           if (Utils.isPlainObject(context)) {
    78             Utils.assert(false, 'does not support HTML creation with context');
    79           } else {
    80             selector = [doc.createElement(ret[1])];
    81           }
    83         } else {
    84           Utils.assert(false, 'does not support complex HTML creation');
    85         }
    87         return Utils.merge(this, selector);
    89       // HANDLE $("#id")
    90       } else {
    91         let elem = document.getElementById(match[2]);
    93         if (elem) {
    94           this.length = 1;
    95           this[0] = elem;
    96         }
    98         this.context = document;
    99         this.selector = selector;
   100         return this;
   101       }
   103     // HANDLE $("TAG")
   104     } else if (!context && /^\w+$/.test(selector)) {
   105       this.selector = selector;
   106       this.context = document;
   107       selector = document.getElementsByTagName(selector);
   108       return Utils.merge(this, selector);
   110     // HANDLE $(expr, $(...))
   111     } else if (!context || context.iq) {
   112       return (context || iQ(document)).find(selector);
   114     // HANDLE $(expr, context)
   115     // (which is just equivalent to: $(context).find(expr)
   116     } else {
   117       return iQ(context).find(selector);
   118     }
   120   // HANDLE $(function)
   121   // Shortcut for document ready
   122   } else if (typeof selector == "function") {
   123     Utils.log('iQ does not support ready functions');
   124     return null;
   125   }
   127   if ("selector" in selector) {
   128     this.selector = selector.selector;
   129     this.context = selector.context;
   130   }
   132   let ret = this || [];
   133   if (selector != null) {
   134     // The window, strings (and functions) also have 'length'
   135     if (selector.length == null || typeof selector == "string" || selector.setInterval) {
   136       Array.push(ret, selector);
   137     } else {
   138       Utils.merge(ret, selector);
   139     }
   140   }
   141   return ret;
   142 };
   144 iQClass.prototype = {
   146   // ----------
   147   // Function: toString
   148   // Prints [iQ...] for debug use
   149   toString: function iQClass_toString() {
   150     if (this.length > 1) {
   151       if (this.selector)
   152         return "[iQ (" + this.selector + ")]";
   153       else
   154         return "[iQ multi-object]";
   155     }
   157     if (this.length == 1)
   158       return "[iQ (" + this[0].toString() + ")]";
   160     return "[iQ non-object]";
   161   },
   163   // Start with an empty selector
   164   selector: "",
   166   // The default length of a iQ object is 0
   167   length: 0,
   169   // ----------
   170   // Function: each
   171   // Execute a callback for every element in the matched set.
   172   each: function iQClass_each(callback) {
   173     if (typeof callback != "function") {
   174       Utils.assert(false, "each's argument must be a function");
   175       return null;
   176     }
   177     for (let i = 0; this[i] != null && callback(this[i]) !== false; i++) {}
   178     return this;
   179   },
   181   // ----------
   182   // Function: addClass
   183   // Adds the given class(es) to the receiver.
   184   addClass: function iQClass_addClass(value) {
   185     Utils.assertThrow(typeof value == "string" && value,
   186                       'requires a valid string argument');
   188     let length = this.length;
   189     for (let i = 0; i < length; i++) {
   190       let elem = this[i];
   191       if (elem.nodeType === 1) {
   192         value.split(/\s+/).forEach(function(className) {
   193           elem.classList.add(className);
   194         });
   195       }
   196     }
   198     return this;
   199   },
   201   // ----------
   202   // Function: removeClass
   203   // Removes the given class(es) from the receiver.
   204   removeClass: function iQClass_removeClass(value) {
   205     if (typeof value != "string" || !value) {
   206       Utils.assert(false, 'does not support function argument');
   207       return null;
   208     }
   210     let length = this.length;
   211     for (let i = 0; i < length; i++) {
   212       let elem = this[i];
   213       if (elem.nodeType === 1 && elem.className) {
   214         value.split(/\s+/).forEach(function(className) {
   215           elem.classList.remove(className);
   216         });
   217       }
   218     }
   220     return this;
   221   },
   223   // ----------
   224   // Function: hasClass
   225   // Returns true is the receiver has the given css class.
   226   hasClass: function iQClass_hasClass(singleClassName) {
   227     let length = this.length;
   228     for (let i = 0; i < length; i++) {
   229       if (this[i].classList.contains(singleClassName)) {
   230         return true;
   231       }
   232     }
   233     return false;
   234   },
   236   // ----------
   237   // Function: find
   238   // Searches the receiver and its children, returning a new iQ object with
   239   // elements that match the given selector.
   240   find: function iQClass_find(selector) {
   241     let ret = [];
   242     let length = 0;
   244     let l = this.length;
   245     for (let i = 0; i < l; i++) {
   246       length = ret.length;
   247       try {
   248         Utils.merge(ret, this[i].querySelectorAll(selector));
   249       } catch(e) {
   250         Utils.log('iQ.find error (bad selector)', e);
   251       }
   253       if (i > 0) {
   254         // Make sure that the results are unique
   255         for (let n = length; n < ret.length; n++) {
   256           for (let r = 0; r < length; r++) {
   257             if (ret[r] === ret[n]) {
   258               ret.splice(n--, 1);
   259               break;
   260             }
   261           }
   262         }
   263       }
   264     }
   266     return iQ(ret);
   267   },
   269   // ----------
   270   // Function: contains
   271   // Check to see if a given DOM node descends from the receiver.
   272   contains: function iQClass_contains(selector) {
   273     Utils.assert(this.length == 1, 'does not yet support multi-objects (or null objects)');
   275     // fast path when querySelector() can be used
   276     if ('string' == typeof selector)
   277       return null != this[0].querySelector(selector);
   279     let object = iQ(selector);
   280     Utils.assert(object.length <= 1, 'does not yet support multi-objects');
   282     let elem = object[0];
   283     if (!elem || !elem.parentNode)
   284       return false;
   286     do {
   287       elem = elem.parentNode;
   288     } while (elem && this[0] != elem);
   290     return this[0] == elem;
   291   },
   293   // ----------
   294   // Function: remove
   295   // Removes the receiver from the DOM.
   296   remove: function iQClass_remove(options) {
   297     if (!options || !options.preserveEventHandlers)
   298       this.unbindAll();
   299     for (let i = 0; this[i] != null; i++) {
   300       let elem = this[i];
   301       if (elem.parentNode) {
   302         elem.parentNode.removeChild(elem);
   303       }
   304     }
   305     return this;
   306   },
   308   // ----------
   309   // Function: empty
   310   // Removes all of the reciever's children and HTML content from the DOM.
   311   empty: function iQClass_empty() {
   312     for (let i = 0; this[i] != null; i++) {
   313       let elem = this[i];
   314       while (elem.firstChild) {
   315         iQ(elem.firstChild).unbindAll();
   316         elem.removeChild(elem.firstChild);
   317       }
   318     }
   319     return this;
   320   },
   322   // ----------
   323   // Function: width
   324   // Returns the width of the receiver, including padding and border.
   325   width: function iQClass_width() {
   326     return Math.floor(this[0].offsetWidth);
   327   },
   329   // ----------
   330   // Function: height
   331   // Returns the height of the receiver, including padding and border.
   332   height: function iQClass_height() {
   333     return Math.floor(this[0].offsetHeight);
   334   },
   336   // ----------
   337   // Function: position
   338   // Returns an object with the receiver's position in left and top
   339   // properties.
   340   position: function iQClass_position() {
   341     let bounds = this.bounds();
   342     return new Point(bounds.left, bounds.top);
   343   },
   345   // ----------
   346   // Function: bounds
   347   // Returns a <Rect> with the receiver's bounds.
   348   bounds: function iQClass_bounds() {
   349     Utils.assert(this.length == 1, 'does not yet support multi-objects (or null objects)');
   350     let rect = this[0].getBoundingClientRect();
   351     return new Rect(Math.floor(rect.left), Math.floor(rect.top),
   352                     Math.floor(rect.width), Math.floor(rect.height));
   353   },
   355   // ----------
   356   // Function: data
   357   // Pass in both key and value to attach some data to the receiver;
   358   // pass in just key to retrieve it.
   359   data: function iQClass_data(key, value) {
   360     let data = null;
   361     if (value === undefined) {
   362       Utils.assert(this.length == 1, 'does not yet support multi-objects (or null objects)');
   363       data = this[0].iQData;
   364       if (data)
   365         return data[key];
   366       else
   367         return null;
   368     }
   370     for (let i = 0; this[i] != null; i++) {
   371       let elem = this[i];
   372       data = elem.iQData;
   374       if (!data)
   375         data = elem.iQData = {};
   377       data[key] = value;
   378     }
   380     return this;
   381   },
   383   // ----------
   384   // Function: html
   385   // Given a value, sets the receiver's innerHTML to it; otherwise returns
   386   // what's already there.
   387   html: function iQClass_html(value) {
   388     Utils.assert(this.length == 1, 'does not yet support multi-objects (or null objects)');
   389     if (value === undefined)
   390       return this[0].innerHTML;
   392     this[0].innerHTML = value;
   393     return this;
   394   },
   396   // ----------
   397   // Function: text
   398   // Given a value, sets the receiver's textContent to it; otherwise returns
   399   // what's already there.
   400   text: function iQClass_text(value) {
   401     Utils.assert(this.length == 1, 'does not yet support multi-objects (or null objects)');
   402     if (value === undefined) {
   403       return this[0].textContent;
   404     }
   406     return this.empty().append((this[0] && this[0].ownerDocument || document).createTextNode(value));
   407   },
   409   // ----------
   410   // Function: val
   411   // Given a value, sets the receiver's value to it; otherwise returns what's already there.
   412   val: function iQClass_val(value) {
   413     Utils.assert(this.length == 1, 'does not yet support multi-objects (or null objects)');
   414     if (value === undefined) {
   415       return this[0].value;
   416     }
   418     this[0].value = value;
   419     return this;
   420   },
   422   // ----------
   423   // Function: appendTo
   424   // Appends the receiver to the result of iQ(selector).
   425   appendTo: function iQClass_appendTo(selector) {
   426     Utils.assert(this.length == 1, 'does not yet support multi-objects (or null objects)');
   427     iQ(selector).append(this);
   428     return this;
   429   },
   431   // ----------
   432   // Function: append
   433   // Appends the result of iQ(selector) to the receiver.
   434   append: function iQClass_append(selector) {
   435     let object = iQ(selector);
   436     Utils.assert(object.length == 1 && this.length == 1, 
   437         'does not yet support multi-objects (or null objects)');
   438     this[0].appendChild(object[0]);
   439     return this;
   440   },
   442   // ----------
   443   // Function: attr
   444   // Sets or gets an attribute on the element(s).
   445   attr: function iQClass_attr(key, value) {
   446     Utils.assert(typeof key === 'string', 'string key');
   447     if (value === undefined) {
   448       Utils.assert(this.length == 1, 'retrieval does not support multi-objects (or null objects)');
   449       return this[0].getAttribute(key);
   450     }
   452     for (let i = 0; this[i] != null; i++)
   453       this[i].setAttribute(key, value);
   455     return this;
   456   },
   458   // ----------
   459   // Function: css
   460   // Sets or gets CSS properties on the receiver. When setting certain numerical properties,
   461   // will automatically add "px". A property can be removed by setting it to null.
   462   //
   463   // Possible call patterns:
   464   //   a: object, b: undefined - sets with properties from a
   465   //   a: string, b: undefined - gets property specified by a
   466   //   a: string, b: string/number - sets property specified by a to b
   467   css: function iQClass_css(a, b) {
   468     let properties = null;
   470     if (typeof a === 'string') {
   471       let key = a;
   472       if (b === undefined) {
   473         Utils.assert(this.length == 1, 'retrieval does not support multi-objects (or null objects)');
   475         return window.getComputedStyle(this[0], null).getPropertyValue(key);
   476       }
   477       properties = {};
   478       properties[key] = b;
   479     } else if (a instanceof Rect) {
   480       properties = {
   481         left: a.left,
   482         top: a.top,
   483         width: a.width,
   484         height: a.height
   485       };
   486     } else {
   487       properties = a;
   488     }
   490     let pixels = {
   491       'left': true,
   492       'top': true,
   493       'right': true,
   494       'bottom': true,
   495       'width': true,
   496       'height': true
   497     };
   499     for (let i = 0; this[i] != null; i++) {
   500       let elem = this[i];
   501       for (let key in properties) {
   502         let value = properties[key];
   504         if (pixels[key] && typeof value != 'string')
   505           value += 'px';
   507         if (value == null) {
   508           elem.style.removeProperty(key);
   509         } else if (key.indexOf('-') != -1)
   510           elem.style.setProperty(key, value, '');
   511         else
   512           elem.style[key] = value;
   513       }
   514     }
   516     return this;
   517   },
   519   // ----------
   520   // Function: animate
   521   // Uses CSS transitions to animate the element.
   522   //
   523   // Parameters:
   524   //   css - an object map of the CSS properties to change
   525   //   options - an object with various properites (see below)
   526   //
   527   // Possible "options" properties:
   528   //   duration - how long to animate, in milliseconds
   529   //   easing - easing function to use. Possibilities include
   530   //     "tabviewBounce", "easeInQuad". Default is "ease".
   531   //   complete - function to call once the animation is done, takes nothing
   532   //     in, but "this" is set to the element that was animated.
   533   animate: function iQClass_animate(css, options) {
   534     Utils.assert(this.length == 1, 'does not yet support multi-objects (or null objects)');
   536     if (!options)
   537       options = {};
   539     let easings = {
   540       tabviewBounce: "cubic-bezier(0.0, 0.63, .6, 1.29)", 
   541       easeInQuad: 'ease-in', // TODO: make it a real easeInQuad, or decide we don't care
   542       fast: 'cubic-bezier(0.7,0,1,1)'
   543     };
   545     let duration = (options.duration || 400);
   546     let easing = (easings[options.easing] || 'ease');
   548     if (css instanceof Rect) {
   549       css = {
   550         left: css.left,
   551         top: css.top,
   552         width: css.width,
   553         height: css.height
   554       };
   555     }
   558     // The latest versions of Firefox do not animate from a non-explicitly
   559     // set css properties. So for each element to be animated, go through
   560     // and explicitly define 'em.
   561     let rupper = /([A-Z])/g;
   562     this.each(function(elem) {
   563       let cStyle = window.getComputedStyle(elem, null);
   564       for (let prop in css) {
   565         prop = prop.replace(rupper, "-$1").toLowerCase();
   566         iQ(elem).css(prop, cStyle.getPropertyValue(prop));
   567       }
   568     });
   570     this.css({
   571       'transition-property': Object.keys(css).join(", "),
   572       'transition-duration': (duration / 1000) + 's',
   573       'transition-timing-function': easing
   574     });
   576     this.css(css);
   578     let self = this;
   579     setTimeout(function() {
   580       self.css({
   581         'transition-property': 'none',
   582         'transition-duration': '',
   583         'transition-timing-function': ''
   584       });
   586       if (typeof options.complete == "function")
   587         options.complete.apply(self);
   588     }, duration);
   590     return this;
   591   },
   593   // ----------
   594   // Function: fadeOut
   595   // Animates the receiver to full transparency. Calls callback on completion.
   596   fadeOut: function iQClass_fadeOut(callback) {
   597     Utils.assert(typeof callback == "function" || callback === undefined, 
   598         'does not yet support duration');
   600     this.animate({
   601       opacity: 0
   602     }, {
   603       duration: 400,
   604       complete: function() {
   605         iQ(this).css({display: 'none'});
   606         if (typeof callback == "function")
   607           callback.apply(this);
   608       }
   609     });
   611     return this;
   612   },
   614   // ----------
   615   // Function: fadeIn
   616   // Animates the receiver to full opacity.
   617   fadeIn: function iQClass_fadeIn() {
   618     this.css({display: ''});
   619     this.animate({
   620       opacity: 1
   621     }, {
   622       duration: 400
   623     });
   625     return this;
   626   },
   628   // ----------
   629   // Function: hide
   630   // Hides the receiver.
   631   hide: function iQClass_hide() {
   632     this.css({display: 'none', opacity: 0});
   633     return this;
   634   },
   636   // ----------
   637   // Function: show
   638   // Shows the receiver.
   639   show: function iQClass_show() {
   640     this.css({display: '', opacity: 1});
   641     return this;
   642   },
   644   // ----------
   645   // Function: bind
   646   // Binds the given function to the given event type. Also wraps the function
   647   // in a try/catch block that does a Utils.log on any errors.
   648   bind: function iQClass_bind(type, func) {
   649     let handler = function(event) func.apply(this, [event]);
   651     for (let i = 0; this[i] != null; i++) {
   652       let elem = this[i];
   653       if (!elem.iQEventData)
   654         elem.iQEventData = {};
   656       if (!elem.iQEventData[type])
   657         elem.iQEventData[type] = [];
   659       elem.iQEventData[type].push({
   660         original: func,
   661         modified: handler
   662       });
   664       elem.addEventListener(type, handler, false);
   665     }
   667     return this;
   668   },
   670   // ----------
   671   // Function: one
   672   // Binds the given function to the given event type, but only for one call;
   673   // automatically unbinds after the event fires once.
   674   one: function iQClass_one(type, func) {
   675     Utils.assert(typeof func == "function", 'does not support eventData argument');
   677     let handler = function(e) {
   678       iQ(this).unbind(type, handler);
   679       return func.apply(this, [e]);
   680     };
   682     return this.bind(type, handler);
   683   },
   685   // ----------
   686   // Function: unbind
   687   // Unbinds the given function from the given event type.
   688   unbind: function iQClass_unbind(type, func) {
   689     Utils.assert(typeof func == "function", 'Must provide a function');
   691     for (let i = 0; this[i] != null; i++) {
   692       let elem = this[i];
   693       let handler = func;
   694       if (elem.iQEventData && elem.iQEventData[type]) {
   695         let count = elem.iQEventData[type].length;
   696         for (let a = 0; a < count; a++) {
   697           let pair = elem.iQEventData[type][a];
   698           if (pair.original == func) {
   699             handler = pair.modified;
   700             elem.iQEventData[type].splice(a, 1);
   701             if (!elem.iQEventData[type].length) {
   702               delete elem.iQEventData[type];
   703               if (!Object.keys(elem.iQEventData).length)
   704                 delete elem.iQEventData;
   705             }
   706             break;
   707           }
   708         }
   709       }
   711       elem.removeEventListener(type, handler, false);
   712     }
   714     return this;
   715   },
   717   // ----------
   718   // Function: unbindAll
   719   // Unbinds all event handlers.
   720   unbindAll: function iQClass_unbindAll() {
   721     for (let i = 0; this[i] != null; i++) {
   722       let elem = this[i];
   724       for (let j = 0; j < elem.childElementCount; j++)
   725         iQ(elem.children[j]).unbindAll();
   727       if (!elem.iQEventData)
   728         continue;
   730       Object.keys(elem.iQEventData).forEach(function (type) {
   731         while (elem.iQEventData && elem.iQEventData[type])
   732           this.unbind(type, elem.iQEventData[type][0].original);
   733       }, this);
   734     }
   736     return this;
   737   }
   738 };
   740 // ----------
   741 // Create various event aliases
   742 let events = [
   743   'keyup',
   744   'keydown',
   745   'keypress',
   746   'mouseup',
   747   'mousedown',
   748   'mouseover',
   749   'mouseout',
   750   'mousemove',
   751   'click',
   752   'dblclick',
   753   'resize',
   754   'change',
   755   'blur',
   756   'focus'
   757 ];
   759 events.forEach(function(event) {
   760   iQClass.prototype[event] = function(func) {
   761     return this.bind(event, func);
   762   };
   763 });

mercurial