Merge branch 'MDL-32509' of git://github.com/danpoltawski/moodle
[moodle.git] / lib / yui / 3.5.0 / build / node-core / node-core-debug.js
blob340bd09c6300b89779c28a1fd1da460dcb15bf7f
1 /*
2 YUI 3.5.0 (build 5089)
3 Copyright 2012 Yahoo! Inc. All rights reserved.
4 Licensed under the BSD License.
5 http://yuilibrary.com/license/
6 */
7 YUI.add('node-core', function(Y) {
9 /**
10  * The Node Utility provides a DOM-like interface for interacting with DOM nodes.
11  * @module node
12  * @main node
13  * @submodule node-core
14  */
16 /**
17  * The Node class provides a wrapper for manipulating DOM Nodes.
18  * Node properties can be accessed via the set/get methods.
19  * Use `Y.one()` to retrieve Node instances.
20  *
21  * <strong>NOTE:</strong> Node properties are accessed using
22  * the <code>set</code> and <code>get</code> methods.
23  *
24  * @class Node
25  * @constructor
26  * @param {DOMNode} node the DOM node to be mapped to the Node instance.
27  * @uses EventTarget
28  */
30 // "globals"
31 var DOT = '.',
32     NODE_NAME = 'nodeName',
33     NODE_TYPE = 'nodeType',
34     OWNER_DOCUMENT = 'ownerDocument',
35     TAG_NAME = 'tagName',
36     UID = '_yuid',
37     EMPTY_OBJ = {},
39     _slice = Array.prototype.slice,
41     Y_DOM = Y.DOM,
43     Y_Node = function(node) {
44         if (!this.getDOMNode) { // support optional "new"
45             return new Y_Node(node);
46         }
48         if (typeof node == 'string') {
49             node = Y_Node._fromString(node);
50             if (!node) {
51                 return null; // NOTE: return
52             }
53         }
55         var uid = (node.nodeType !== 9) ? node.uniqueID : node[UID];
57         if (uid && Y_Node._instances[uid] && Y_Node._instances[uid]._node !== node) {
58             node[UID] = null; // unset existing uid to prevent collision (via clone or hack)
59         }
61         uid = uid || Y.stamp(node);
62         if (!uid) { // stamp failed; likely IE non-HTMLElement
63             uid = Y.guid();
64         }
66         this[UID] = uid;
68         /**
69          * The underlying DOM node bound to the Y.Node instance
70          * @property _node
71          * @private
72          */
73         this._node = node;
75         this._stateProxy = node; // when augmented with Attribute
77         if (this._initPlugins) { // when augmented with Plugin.Host
78             this._initPlugins();
79         }
80     },
82     // used with previous/next/ancestor tests
83     _wrapFn = function(fn) {
84         var ret = null;
85         if (fn) {
86             ret = (typeof fn == 'string') ?
87             function(n) {
88                 return Y.Selector.test(n, fn);
89             } :
90             function(n) {
91                 return fn(Y.one(n));
92             };
93         }
95         return ret;
96     };
97 // end "globals"
99 Y_Node.ATTRS = {};
100 Y_Node.DOM_EVENTS = {};
102 Y_Node._fromString = function(node) {
103     if (node) {
104         if (node.indexOf('doc') === 0) { // doc OR document
105             node = Y.config.doc;
106         } else if (node.indexOf('win') === 0) { // win OR window
107             node = Y.config.win;
108         } else {
109             node = Y.Selector.query(node, null, true);
110         }
111     }
113     return node || null;
117  * The name of the component
118  * @static
119  * @property NAME
120  */
121 Y_Node.NAME = 'node';
124  * The pattern used to identify ARIA attributes
125  */
126 Y_Node.re_aria = /^(?:role$|aria-)/;
128 Y_Node.SHOW_TRANSITION = 'fadeIn';
129 Y_Node.HIDE_TRANSITION = 'fadeOut';
132  * A list of Node instances that have been created
133  * @private
134  * @property _instances
135  * @static
137  */
138 Y_Node._instances = {};
141  * Retrieves the DOM node bound to a Node instance
142  * @method getDOMNode
143  * @static
145  * @param {Node | HTMLNode} node The Node instance or an HTMLNode
146  * @return {HTMLNode} The DOM node bound to the Node instance.  If a DOM node is passed
147  * as the node argument, it is simply returned.
148  */
149 Y_Node.getDOMNode = function(node) {
150     if (node) {
151         return (node.nodeType) ? node : node._node || null;
152     }
153     return null;
157  * Checks Node return values and wraps DOM Nodes as Y.Node instances
158  * and DOM Collections / Arrays as Y.NodeList instances.
159  * Other return values just pass thru.  If undefined is returned (e.g. no return)
160  * then the Node instance is returned for chainability.
161  * @method scrubVal
162  * @static
164  * @param {any} node The Node instance or an HTMLNode
165  * @return {Node | NodeList | Any} Depends on what is returned from the DOM node.
166  */
167 Y_Node.scrubVal = function(val, node) {
168     if (val) { // only truthy values are risky
169          if (typeof val == 'object' || typeof val == 'function') { // safari nodeList === function
170             if (NODE_TYPE in val || Y_DOM.isWindow(val)) {// node || window
171                 val = Y.one(val);
172             } else if ((val.item && !val._nodes) || // dom collection or Node instance
173                     (val[0] && val[0][NODE_TYPE])) { // array of DOM Nodes
174                 val = Y.all(val);
175             }
176         }
177     } else if (typeof val === 'undefined') {
178         val = node; // for chaining
179     } else if (val === null) {
180         val = null; // IE: DOM null not the same as null
181     }
183     return val;
187  * Adds methods to the Y.Node prototype, routing through scrubVal.
188  * @method addMethod
189  * @static
191  * @param {String} name The name of the method to add
192  * @param {Function} fn The function that becomes the method
193  * @param {Object} context An optional context to call the method with
194  * (defaults to the Node instance)
195  * @return {any} Depends on what is returned from the DOM node.
196  */
197 Y_Node.addMethod = function(name, fn, context) {
198     if (name && fn && typeof fn == 'function') {
199         Y_Node.prototype[name] = function() {
200             var args = _slice.call(arguments),
201                 node = this,
202                 ret;
204             if (args[0] && args[0]._node) {
205                 args[0] = args[0]._node;
206             }
208             if (args[1] && args[1]._node) {
209                 args[1] = args[1]._node;
210             }
211             args.unshift(node._node);
213             ret = fn.apply(node, args);
215             if (ret) { // scrub truthy
216                 ret = Y_Node.scrubVal(ret, node);
217             }
219             (typeof ret != 'undefined') || (ret = node);
220             return ret;
221         };
222     } else {
223         Y.log('unable to add method: ' + name, 'warn', 'Node');
224     }
228  * Imports utility methods to be added as Y.Node methods.
229  * @method importMethod
230  * @static
232  * @param {Object} host The object that contains the method to import.
233  * @param {String} name The name of the method to import
234  * @param {String} altName An optional name to use in place of the host name
235  * @param {Object} context An optional context to call the method with
236  */
237 Y_Node.importMethod = function(host, name, altName) {
238     if (typeof name == 'string') {
239         altName = altName || name;
240         Y_Node.addMethod(altName, host[name], host);
241     } else {
242         Y.Array.each(name, function(n) {
243             Y_Node.importMethod(host, n);
244         });
245     }
249  * Retrieves a NodeList based on the given CSS selector.
250  * @method all
252  * @param {string} selector The CSS selector to test against.
253  * @return {NodeList} A NodeList instance for the matching HTMLCollection/Array.
254  * @for YUI
255  */
258  * Returns a single Node instance bound to the node or the
259  * first element matching the given selector. Returns null if no match found.
260  * <strong>Note:</strong> For chaining purposes you may want to
261  * use <code>Y.all</code>, which returns a NodeList when no match is found.
262  * @method one
263  * @param {String | HTMLElement} node a node or Selector
264  * @return {Node | null} a Node instance or null if no match found.
265  * @for YUI
266  */
269  * Returns a single Node instance bound to the node or the
270  * first element matching the given selector. Returns null if no match found.
271  * <strong>Note:</strong> For chaining purposes you may want to
272  * use <code>Y.all</code>, which returns a NodeList when no match is found.
273  * @method one
274  * @static
275  * @param {String | HTMLElement} node a node or Selector
276  * @return {Node | null} a Node instance or null if no match found.
277  * @for Node
278  */
279 Y_Node.one = function(node) {
280     var instance = null,
281         cachedNode,
282         uid;
284     if (node) {
285         if (typeof node == 'string') {
286             node = Y_Node._fromString(node);
287             if (!node) {
288                 return null; // NOTE: return
289             }
290         } else if (node.getDOMNode) {
291             return node; // NOTE: return
292         }
294         if (node.nodeType || Y.DOM.isWindow(node)) { // avoid bad input (numbers, boolean, etc)
295             uid = (node.uniqueID && node.nodeType !== 9) ? node.uniqueID : node._yuid;
296             instance = Y_Node._instances[uid]; // reuse exising instances
297             cachedNode = instance ? instance._node : null;
298             if (!instance || (cachedNode && node !== cachedNode)) { // new Node when nodes don't match
299                 instance = new Y_Node(node);
300                 if (node.nodeType != 11) { // dont cache document fragment
301                     Y_Node._instances[instance[UID]] = instance; // cache node
302                 }
303             }
304         }
305     }
307     return instance;
311  * The default setter for DOM properties
312  * Called with instance context (this === the Node instance)
313  * @method DEFAULT_SETTER
314  * @static
315  * @param {String} name The attribute/property being set
316  * @param {any} val The value to be set
317  * @return {any} The value
318  */
319 Y_Node.DEFAULT_SETTER = function(name, val) {
320     var node = this._stateProxy,
321         strPath;
323     if (name.indexOf(DOT) > -1) {
324         strPath = name;
325         name = name.split(DOT);
326         // only allow when defined on node
327         Y.Object.setValue(node, name, val);
328     } else if (typeof node[name] != 'undefined') { // pass thru DOM properties
329         node[name] = val;
330     }
332     return val;
336  * The default getter for DOM properties
337  * Called with instance context (this === the Node instance)
338  * @method DEFAULT_GETTER
339  * @static
340  * @param {String} name The attribute/property to look up
341  * @return {any} The current value
342  */
343 Y_Node.DEFAULT_GETTER = function(name) {
344     var node = this._stateProxy,
345         val;
347     if (name.indexOf && name.indexOf(DOT) > -1) {
348         val = Y.Object.getValue(node, name.split(DOT));
349     } else if (typeof node[name] != 'undefined') { // pass thru from DOM
350         val = node[name];
351     }
353     return val;
356 Y.mix(Y_Node.prototype, {
357     DATA_PREFIX: 'data-',
359     /**
360      * The method called when outputting Node instances as strings
361      * @method toString
362      * @return {String} A string representation of the Node instance
363      */
364     toString: function() {
365         var str = this[UID] + ': not bound to a node',
366             node = this._node,
367             attrs, id, className;
369         if (node) {
370             attrs = node.attributes;
371             id = (attrs && attrs.id) ? node.getAttribute('id') : null;
372             className = (attrs && attrs.className) ? node.getAttribute('className') : null;
373             str = node[NODE_NAME];
375             if (id) {
376                 str += '#' + id;
377             }
379             if (className) {
380                 str += '.' + className.replace(' ', '.');
381             }
383             // TODO: add yuid?
384             str += ' ' + this[UID];
385         }
386         return str;
387     },
389     /**
390      * Returns an attribute value on the Node instance.
391      * Unless pre-configured (via `Node.ATTRS`), get hands
392      * off to the underlying DOM node.  Only valid
393      * attributes/properties for the node will be queried.
394      * @method get
395      * @param {String} attr The attribute
396      * @return {any} The current value of the attribute
397      */
398     get: function(attr) {
399         var val;
401         if (this._getAttr) { // use Attribute imple
402             val = this._getAttr(attr);
403         } else {
404             val = this._get(attr);
405         }
407         if (val) {
408             val = Y_Node.scrubVal(val, this);
409         } else if (val === null) {
410             val = null; // IE: DOM null is not true null (even though they ===)
411         }
412         return val;
413     },
415     /**
416      * Helper method for get.
417      * @method _get
418      * @private
419      * @param {String} attr The attribute
420      * @return {any} The current value of the attribute
421      */
422     _get: function(attr) {
423         var attrConfig = Y_Node.ATTRS[attr],
424             val;
426         if (attrConfig && attrConfig.getter) {
427             val = attrConfig.getter.call(this);
428         } else if (Y_Node.re_aria.test(attr)) {
429             val = this._node.getAttribute(attr, 2);
430         } else {
431             val = Y_Node.DEFAULT_GETTER.apply(this, arguments);
432         }
434         return val;
435     },
437     /**
438      * Sets an attribute on the Node instance.
439      * Unless pre-configured (via Node.ATTRS), set hands
440      * off to the underlying DOM node.  Only valid
441      * attributes/properties for the node will be set.
442      * To set custom attributes use setAttribute.
443      * @method set
444      * @param {String} attr The attribute to be set.
445      * @param {any} val The value to set the attribute to.
446      * @chainable
447      */
448     set: function(attr, val) {
449         var attrConfig = Y_Node.ATTRS[attr];
451         if (this._setAttr) { // use Attribute imple
452             this._setAttr.apply(this, arguments);
453         } else { // use setters inline
454             if (attrConfig && attrConfig.setter) {
455                 attrConfig.setter.call(this, val, attr);
456             } else if (Y_Node.re_aria.test(attr)) { // special case Aria
457                 this._node.setAttribute(attr, val);
458             } else {
459                 Y_Node.DEFAULT_SETTER.apply(this, arguments);
460             }
461         }
463         return this;
464     },
466     /**
467      * Sets multiple attributes.
468      * @method setAttrs
469      * @param {Object} attrMap an object of name/value pairs to set
470      * @chainable
471      */
472     setAttrs: function(attrMap) {
473         if (this._setAttrs) { // use Attribute imple
474             this._setAttrs(attrMap);
475         } else { // use setters inline
476             Y.Object.each(attrMap, function(v, n) {
477                 this.set(n, v);
478             }, this);
479         }
481         return this;
482     },
484     /**
485      * Returns an object containing the values for the requested attributes.
486      * @method getAttrs
487      * @param {Array} attrs an array of attributes to get values
488      * @return {Object} An object with attribute name/value pairs.
489      */
490     getAttrs: function(attrs) {
491         var ret = {};
492         if (this._getAttrs) { // use Attribute imple
493             this._getAttrs(attrs);
494         } else { // use setters inline
495             Y.Array.each(attrs, function(v, n) {
496                 ret[v] = this.get(v);
497             }, this);
498         }
500         return ret;
501     },
503     /**
504      * Compares nodes to determine if they match.
505      * Node instances can be compared to each other and/or HTMLElements.
506      * @method compareTo
507      * @param {HTMLElement | Node} refNode The reference node to compare to the node.
508      * @return {Boolean} True if the nodes match, false if they do not.
509      */
510     compareTo: function(refNode) {
511         var node = this._node;
513         if (refNode && refNode._node) {
514             refNode = refNode._node;
515         }
516         return node === refNode;
517     },
519     /**
520      * Determines whether the node is appended to the document.
521      * @method inDoc
522      * @param {Node|HTMLElement} doc optional An optional document to check against.
523      * Defaults to current document.
524      * @return {Boolean} Whether or not this node is appended to the document.
525      */
526     inDoc: function(doc) {
527         var node = this._node;
528         doc = (doc) ? doc._node || doc : node[OWNER_DOCUMENT];
529         if (doc.documentElement) {
530             return Y_DOM.contains(doc.documentElement, node);
531         }
532     },
534     getById: function(id) {
535         var node = this._node,
536             ret = Y_DOM.byId(id, node[OWNER_DOCUMENT]);
537         if (ret && Y_DOM.contains(node, ret)) {
538             ret = Y.one(ret);
539         } else {
540             ret = null;
541         }
542         return ret;
543     },
545    /**
546      * Returns the nearest ancestor that passes the test applied by supplied boolean method.
547      * @method ancestor
548      * @param {String | Function} fn A selector string or boolean method for testing elements.
549      * If a function is used, it receives the current node being tested as the only argument.
550      * @param {Boolean} testSelf optional Whether or not to include the element in the scan
551      * @param {String | Function} stopFn optional A selector string or boolean
552      * method to indicate when the search should stop. The search bails when the function
553      * returns true or the selector matches.
554      * If a function is used, it receives the current node being tested as the only argument.
555      * @return {Node} The matching Node instance or null if not found
556      */
557     ancestor: function(fn, testSelf, stopFn) {
558         // testSelf is optional, check for stopFn as 2nd arg
559         if (arguments.length === 2 &&
560                 (typeof testSelf == 'string' || typeof testSelf == 'function')) {
561             stopFn = testSelf;
562         }
564         return Y.one(Y_DOM.ancestor(this._node, _wrapFn(fn), testSelf, _wrapFn(stopFn)));
565     },
567    /**
568      * Returns the ancestors that pass the test applied by supplied boolean method.
569      * @method ancestors
570      * @param {String | Function} fn A selector string or boolean method for testing elements.
571      * @param {Boolean} testSelf optional Whether or not to include the element in the scan
572      * If a function is used, it receives the current node being tested as the only argument.
573      * @return {NodeList} A NodeList instance containing the matching elements
574      */
575     ancestors: function(fn, testSelf, stopFn) {
576         if (arguments.length === 2 &&
577                 (typeof testSelf == 'string' || typeof testSelf == 'function')) {
578             stopFn = testSelf;
579         }
580         return Y.all(Y_DOM.ancestors(this._node, _wrapFn(fn), testSelf, _wrapFn(stopFn)));
581     },
583     /**
584      * Returns the previous matching sibling.
585      * Returns the nearest element node sibling if no method provided.
586      * @method previous
587      * @param {String | Function} fn A selector or boolean method for testing elements.
588      * If a function is used, it receives the current node being tested as the only argument.
589      * @return {Node} Node instance or null if not found
590      */
591     previous: function(fn, all) {
592         return Y.one(Y_DOM.elementByAxis(this._node, 'previousSibling', _wrapFn(fn), all));
593     },
595     /**
596      * Returns the next matching sibling.
597      * Returns the nearest element node sibling if no method provided.
598      * @method next
599      * @param {String | Function} fn A selector or boolean method for testing elements.
600      * If a function is used, it receives the current node being tested as the only argument.
601      * @return {Node} Node instance or null if not found
602      */
603     next: function(fn, all) {
604         return Y.one(Y_DOM.elementByAxis(this._node, 'nextSibling', _wrapFn(fn), all));
605     },
607     /**
608      * Returns all matching siblings.
609      * Returns all siblings if no method provided.
610      * @method siblings
611      * @param {String | Function} fn A selector or boolean method for testing elements.
612      * If a function is used, it receives the current node being tested as the only argument.
613      * @return {NodeList} NodeList instance bound to found siblings
614      */
615     siblings: function(fn) {
616         return Y.all(Y_DOM.siblings(this._node, _wrapFn(fn)));
617     },
619     /**
620      * Retrieves a Node instance of nodes based on the given CSS selector.
621      * @method one
622      *
623      * @param {string} selector The CSS selector to test against.
624      * @return {Node} A Node instance for the matching HTMLElement.
625      */
626     one: function(selector) {
627         return Y.one(Y.Selector.query(selector, this._node, true));
628     },
630     /**
631      * Retrieves a NodeList based on the given CSS selector.
632      * @method all
633      *
634      * @param {string} selector The CSS selector to test against.
635      * @return {NodeList} A NodeList instance for the matching HTMLCollection/Array.
636      */
637     all: function(selector) {
638         var nodelist = Y.all(Y.Selector.query(selector, this._node));
639         nodelist._query = selector;
640         nodelist._queryRoot = this._node;
641         return nodelist;
642     },
644     // TODO: allow fn test
645     /**
646      * Test if the supplied node matches the supplied selector.
647      * @method test
648      *
649      * @param {string} selector The CSS selector to test against.
650      * @return {boolean} Whether or not the node matches the selector.
651      */
652     test: function(selector) {
653         return Y.Selector.test(this._node, selector);
654     },
656     /**
657      * Removes the node from its parent.
658      * Shortcut for myNode.get('parentNode').removeChild(myNode);
659      * @method remove
660      * @param {Boolean} destroy whether or not to call destroy() on the node
661      * after removal.
662      * @chainable
663      *
664      */
665     remove: function(destroy) {
666         var node = this._node;
668         if (node && node.parentNode) {
669             node.parentNode.removeChild(node);
670         }
672         if (destroy) {
673             this.destroy();
674         }
676         return this;
677     },
679     /**
680      * Replace the node with the other node. This is a DOM update only
681      * and does not change the node bound to the Node instance.
682      * Shortcut for myNode.get('parentNode').replaceChild(newNode, myNode);
683      * @method replace
684      * @param {Node | HTMLNode} newNode Node to be inserted
685      * @chainable
686      *
687      */
688     replace: function(newNode) {
689         var node = this._node;
690         if (typeof newNode == 'string') {
691             newNode = Y_Node.create(newNode);
692         }
693         node.parentNode.replaceChild(Y_Node.getDOMNode(newNode), node);
694         return this;
695     },
697     /**
698      * @method replaceChild
699      * @for Node
700      * @param {String | HTMLElement | Node} node Node to be inserted
701      * @param {HTMLElement | Node} refNode Node to be replaced
702      * @return {Node} The replaced node
703      */
704     replaceChild: function(node, refNode) {
705         if (typeof node == 'string') {
706             node = Y_DOM.create(node);
707         }
709         return Y.one(this._node.replaceChild(Y_Node.getDOMNode(node), Y_Node.getDOMNode(refNode)));
710     },
712     /**
713      * Nulls internal node references, removes any plugins and event listeners
714      * @method destroy
715      * @param {Boolean} recursivePurge (optional) Whether or not to remove listeners from the
716      * node's subtree (default is false)
717      *
718      */
719     destroy: function(recursive) {
720         var UID = Y.config.doc.uniqueID ? 'uniqueID' : '_yuid',
721             instance;
723         this.purge(); // TODO: only remove events add via this Node
725         if (this.unplug) { // may not be a PluginHost
726             this.unplug();
727         }
729         this.clearData();
731         if (recursive) {
732             Y.NodeList.each(this.all('*'), function(node) {
733                 instance = Y_Node._instances[node[UID]];
734                 if (instance) {
735                    instance.destroy();
736                 }
737             });
738         }
740         this._node = null;
741         this._stateProxy = null;
743         delete Y_Node._instances[this._yuid];
744     },
746     /**
747      * Invokes a method on the Node instance
748      * @method invoke
749      * @param {String} method The name of the method to invoke
750      * @param {Any}  a, b, c, etc. Arguments to invoke the method with.
751      * @return Whatever the underly method returns.
752      * DOM Nodes and Collections return values
753      * are converted to Node/NodeList instances.
754      *
755      */
756     invoke: function(method, a, b, c, d, e) {
757         var node = this._node,
758             ret;
760         if (a && a._node) {
761             a = a._node;
762         }
764         if (b && b._node) {
765             b = b._node;
766         }
768         ret = node[method](a, b, c, d, e);
769         return Y_Node.scrubVal(ret, this);
770     },
772     /**
773     * @method swap
774     * @description Swap DOM locations with the given node.
775     * This does not change which DOM node each Node instance refers to.
776     * @param {Node} otherNode The node to swap with
777      * @chainable
778     */
779     swap: Y.config.doc.documentElement.swapNode ?
780         function(otherNode) {
781             this._node.swapNode(Y_Node.getDOMNode(otherNode));
782         } :
783         function(otherNode) {
784             otherNode = Y_Node.getDOMNode(otherNode);
785             var node = this._node,
786                 parent = otherNode.parentNode,
787                 nextSibling = otherNode.nextSibling;
789             if (nextSibling === node) {
790                 parent.insertBefore(node, otherNode);
791             } else if (otherNode === node.nextSibling) {
792                 parent.insertBefore(otherNode, node);
793             } else {
794                 node.parentNode.replaceChild(otherNode, node);
795                 Y_DOM.addHTML(parent, node, nextSibling);
796             }
797             return this;
798         },
801     hasMethod: function(method) {
802         var node = this._node;
803         return !!(node && method in node &&
804                 typeof node[method] != 'unknown' &&
805             (typeof node[method] == 'function' ||
806                 String(node[method]).indexOf('function') === 1)); // IE reports as object, prepends space
807     },
809     isFragment: function() {
810         return (this.get('nodeType') === 11);
811     },
813     /**
814      * Removes and destroys all of the nodes within the node.
815      * @method empty
816      * @chainable
817      */
818     empty: function() {
819         this.get('childNodes').remove().destroy(true);
820         return this;
821     },
823     /**
824      * Returns the DOM node bound to the Node instance
825      * @method getDOMNode
826      * @return {DOMNode}
827      */
828     getDOMNode: function() {
829         return this._node;
830     }
831 }, true);
833 Y.Node = Y_Node;
834 Y.one = Y_Node.one;
836  * The NodeList module provides support for managing collections of Nodes.
837  * @module node
838  * @submodule node-core
839  */
842  * The NodeList class provides a wrapper for manipulating DOM NodeLists.
843  * NodeList properties can be accessed via the set/get methods.
844  * Use Y.all() to retrieve NodeList instances.
846  * @class NodeList
847  * @constructor
848  */
850 var NodeList = function(nodes) {
851     var tmp = [];
853     if (nodes) {
854         if (typeof nodes === 'string') { // selector query
855             this._query = nodes;
856             nodes = Y.Selector.query(nodes);
857         } else if (nodes.nodeType || Y_DOM.isWindow(nodes)) { // domNode || window
858             nodes = [nodes];
859         } else if (nodes._node) { // Y.Node
860             nodes = [nodes._node];
861         } else if (nodes[0] && nodes[0]._node) { // allow array of Y.Nodes
862             Y.Array.each(nodes, function(node) {
863                 if (node._node) {
864                     tmp.push(node._node);
865                 }
866             });
867             nodes = tmp;
868         } else { // array of domNodes or domNodeList (no mixed array of Y.Node/domNodes)
869             nodes = Y.Array(nodes, 0, true);
870         }
871     }
873     /**
874      * The underlying array of DOM nodes bound to the Y.NodeList instance
875      * @property _nodes
876      * @private
877      */
878     this._nodes = nodes || [];
881 NodeList.NAME = 'NodeList';
884  * Retrieves the DOM nodes bound to a NodeList instance
885  * @method getDOMNodes
886  * @static
888  * @param {NodeList} nodelist The NodeList instance
889  * @return {Array} The array of DOM nodes bound to the NodeList
890  */
891 NodeList.getDOMNodes = function(nodelist) {
892     return (nodelist && nodelist._nodes) ? nodelist._nodes : nodelist;
895 NodeList.each = function(instance, fn, context) {
896     var nodes = instance._nodes;
897     if (nodes && nodes.length) {
898         Y.Array.each(nodes, fn, context || instance);
899     } else {
900         Y.log('no nodes bound to ' + this, 'warn', 'NodeList');
901     }
904 NodeList.addMethod = function(name, fn, context) {
905     if (name && fn) {
906         NodeList.prototype[name] = function() {
907             var ret = [],
908                 args = arguments;
910             Y.Array.each(this._nodes, function(node) {
911                 var UID = (node.uniqueID && node.nodeType !== 9 ) ? 'uniqueID' : '_yuid',
912                     instance = Y.Node._instances[node[UID]],
913                     ctx,
914                     result;
916                 if (!instance) {
917                     instance = NodeList._getTempNode(node);
918                 }
919                 ctx = context || instance;
920                 result = fn.apply(ctx, args);
921                 if (result !== undefined && result !== instance) {
922                     ret[ret.length] = result;
923                 }
924             });
926             // TODO: remove tmp pointer
927             return ret.length ? ret : this;
928         };
929     } else {
930         Y.log('unable to add method: ' + name + ' to NodeList', 'warn', 'node');
931     }
934 NodeList.importMethod = function(host, name, altName) {
935     if (typeof name === 'string') {
936         altName = altName || name;
937         NodeList.addMethod(name, host[name]);
938     } else {
939         Y.Array.each(name, function(n) {
940             NodeList.importMethod(host, n);
941         });
942     }
945 NodeList._getTempNode = function(node) {
946     var tmp = NodeList._tempNode;
947     if (!tmp) {
948         tmp = Y.Node.create('<div></div>');
949         NodeList._tempNode = tmp;
950     }
952     tmp._node = node;
953     tmp._stateProxy = node;
954     return tmp;
957 Y.mix(NodeList.prototype, {
958     _invoke: function(method, args, getter) {
959         var ret = (getter) ? [] : this;
961         this.each(function(node) {
962             var val = node[method].apply(node, args);
963             if (getter) {
964                 ret.push(val);
965             }
966         });
968         return ret;
969     },
971     /**
972      * Retrieves the Node instance at the given index.
973      * @method item
974      *
975      * @param {Number} index The index of the target Node.
976      * @return {Node} The Node instance at the given index.
977      */
978     item: function(index) {
979         return Y.one((this._nodes || [])[index]);
980     },
982     /**
983      * Applies the given function to each Node in the NodeList.
984      * @method each
985      * @param {Function} fn The function to apply. It receives 3 arguments:
986      * the current node instance, the node's index, and the NodeList instance
987      * @param {Object} context optional An optional context to apply the function with
988      * Default context is the current Node instance
989      * @chainable
990      */
991     each: function(fn, context) {
992         var instance = this;
993         Y.Array.each(this._nodes, function(node, index) {
994             node = Y.one(node);
995             return fn.call(context || node, node, index, instance);
996         });
997         return instance;
998     },
1000     batch: function(fn, context) {
1001         var nodelist = this;
1003         Y.Array.each(this._nodes, function(node, index) {
1004             var instance = Y.Node._instances[node[UID]];
1005             if (!instance) {
1006                 instance = NodeList._getTempNode(node);
1007             }
1009             return fn.call(context || instance, instance, index, nodelist);
1010         });
1011         return nodelist;
1012     },
1014     /**
1015      * Executes the function once for each node until a true value is returned.
1016      * @method some
1017      * @param {Function} fn The function to apply. It receives 3 arguments:
1018      * the current node instance, the node's index, and the NodeList instance
1019      * @param {Object} context optional An optional context to execute the function from.
1020      * Default context is the current Node instance
1021      * @return {Boolean} Whether or not the function returned true for any node.
1022      */
1023     some: function(fn, context) {
1024         var instance = this;
1025         return Y.Array.some(this._nodes, function(node, index) {
1026             node = Y.one(node);
1027             context = context || node;
1028             return fn.call(context, node, index, instance);
1029         });
1030     },
1032     /**
1033      * Creates a documenFragment from the nodes bound to the NodeList instance
1034      * @method toFrag
1035      * @return {Node} a Node instance bound to the documentFragment
1036      */
1037     toFrag: function() {
1038         return Y.one(Y.DOM._nl2frag(this._nodes));
1039     },
1041     /**
1042      * Returns the index of the node in the NodeList instance
1043      * or -1 if the node isn't found.
1044      * @method indexOf
1045      * @param {Node | DOMNode} node the node to search for
1046      * @return {Int} the index of the node value or -1 if not found
1047      */
1048     indexOf: function(node) {
1049         return Y.Array.indexOf(this._nodes, Y.Node.getDOMNode(node));
1050     },
1052     /**
1053      * Filters the NodeList instance down to only nodes matching the given selector.
1054      * @method filter
1055      * @param {String} selector The selector to filter against
1056      * @return {NodeList} NodeList containing the updated collection
1057      * @see Selector
1058      */
1059     filter: function(selector) {
1060         return Y.all(Y.Selector.filter(this._nodes, selector));
1061     },
1064     /**
1065      * Creates a new NodeList containing all nodes at every n indices, where
1066      * remainder n % index equals r.
1067      * (zero-based index).
1068      * @method modulus
1069      * @param {Int} n The offset to use (return every nth node)
1070      * @param {Int} r An optional remainder to use with the modulus operation (defaults to zero)
1071      * @return {NodeList} NodeList containing the updated collection
1072      */
1073     modulus: function(n, r) {
1074         r = r || 0;
1075         var nodes = [];
1076         NodeList.each(this, function(node, i) {
1077             if (i % n === r) {
1078                 nodes.push(node);
1079             }
1080         });
1082         return Y.all(nodes);
1083     },
1085     /**
1086      * Creates a new NodeList containing all nodes at odd indices
1087      * (zero-based index).
1088      * @method odd
1089      * @return {NodeList} NodeList containing the updated collection
1090      */
1091     odd: function() {
1092         return this.modulus(2, 1);
1093     },
1095     /**
1096      * Creates a new NodeList containing all nodes at even indices
1097      * (zero-based index), including zero.
1098      * @method even
1099      * @return {NodeList} NodeList containing the updated collection
1100      */
1101     even: function() {
1102         return this.modulus(2);
1103     },
1105     destructor: function() {
1106     },
1108     /**
1109      * Reruns the initial query, when created using a selector query
1110      * @method refresh
1111      * @chainable
1112      */
1113     refresh: function() {
1114         var doc,
1115             nodes = this._nodes,
1116             query = this._query,
1117             root = this._queryRoot;
1119         if (query) {
1120             if (!root) {
1121                 if (nodes && nodes[0] && nodes[0].ownerDocument) {
1122                     root = nodes[0].ownerDocument;
1123                 }
1124             }
1126             this._nodes = Y.Selector.query(query, root);
1127         }
1129         return this;
1130     },
1132     /**
1133      * Returns the current number of items in the NodeList.
1134      * @method size
1135      * @return {Int} The number of items in the NodeList.
1136      */
1137     size: function() {
1138         return this._nodes.length;
1139     },
1141     /**
1142      * Determines if the instance is bound to any nodes
1143      * @method isEmpty
1144      * @return {Boolean} Whether or not the NodeList is bound to any nodes
1145      */
1146     isEmpty: function() {
1147         return this._nodes.length < 1;
1148     },
1150     toString: function() {
1151         var str = '',
1152             errorMsg = this[UID] + ': not bound to any nodes',
1153             nodes = this._nodes,
1154             node;
1156         if (nodes && nodes[0]) {
1157             node = nodes[0];
1158             str += node[NODE_NAME];
1159             if (node.id) {
1160                 str += '#' + node.id;
1161             }
1163             if (node.className) {
1164                 str += '.' + node.className.replace(' ', '.');
1165             }
1167             if (nodes.length > 1) {
1168                 str += '...[' + nodes.length + ' items]';
1169             }
1170         }
1171         return str || errorMsg;
1172     },
1174     /**
1175      * Returns the DOM node bound to the Node instance
1176      * @method getDOMNodes
1177      * @return {Array}
1178      */
1179     getDOMNodes: function() {
1180         return this._nodes;
1181     }
1182 }, true);
1184 NodeList.importMethod(Y.Node.prototype, [
1185     /** Called on each Node instance
1186       * @method destroy
1187       * @see Node.destroy
1188       */
1189     'destroy',
1191     /** Called on each Node instance
1192       * @method empty
1193       * @see Node.empty
1194       */
1195     'empty',
1197     /** Called on each Node instance
1198       * @method remove
1199       * @see Node.remove
1200       */
1201     'remove',
1203     /** Called on each Node instance
1204       * @method set
1205       * @see Node.set
1206       */
1207     'set'
1210 // one-off implementation to convert array of Nodes to NodeList
1211 // e.g. Y.all('input').get('parentNode');
1213 /** Called on each Node instance
1214   * @method get
1215   * @see Node
1216   */
1217 NodeList.prototype.get = function(attr) {
1218     var ret = [],
1219         nodes = this._nodes,
1220         isNodeList = false,
1221         getTemp = NodeList._getTempNode,
1222         instance,
1223         val;
1225     if (nodes[0]) {
1226         instance = Y.Node._instances[nodes[0]._yuid] || getTemp(nodes[0]);
1227         val = instance._get(attr);
1228         if (val && val.nodeType) {
1229             isNodeList = true;
1230         }
1231     }
1233     Y.Array.each(nodes, function(node) {
1234         instance = Y.Node._instances[node._yuid];
1236         if (!instance) {
1237             instance = getTemp(node);
1238         }
1240         val = instance._get(attr);
1241         if (!isNodeList) { // convert array of Nodes to NodeList
1242             val = Y.Node.scrubVal(val, instance);
1243         }
1245         ret.push(val);
1246     });
1248     return (isNodeList) ? Y.all(ret) : ret;
1251 Y.NodeList = NodeList;
1253 Y.all = function(nodes) {
1254     return new NodeList(nodes);
1257 Y.Node.all = Y.all;
1259  * @module node
1260  * @submodule node-core
1261  */
1263 var Y_NodeList = Y.NodeList,
1264     ArrayProto = Array.prototype,
1265     ArrayMethods = {
1266         /** Returns a new NodeList combining the given NodeList(s)
1267           * @for NodeList
1268           * @method concat
1269           * @param {NodeList | Array} valueN Arrays/NodeLists and/or values to
1270           * concatenate to the resulting NodeList
1271           * @return {NodeList} A new NodeList comprised of this NodeList joined with the input.
1272           */
1273         'concat': 1,
1274         /** Removes the last from the NodeList and returns it.
1275           * @for NodeList
1276           * @method pop
1277           * @return {Node} The last item in the NodeList.
1278           */
1279         'pop': 0,
1280         /** Adds the given Node(s) to the end of the NodeList.
1281           * @for NodeList
1282           * @method push
1283           * @param {Node | DOMNode} nodes One or more nodes to add to the end of the NodeList.
1284           */
1285         'push': 0,
1286         /** Removes the first item from the NodeList and returns it.
1287           * @for NodeList
1288           * @method shift
1289           * @return {Node} The first item in the NodeList.
1290           */
1291         'shift': 0,
1292         /** Returns a new NodeList comprising the Nodes in the given range.
1293           * @for NodeList
1294           * @method slice
1295           * @param {Number} begin Zero-based index at which to begin extraction.
1296           As a negative index, start indicates an offset from the end of the sequence. slice(-2) extracts the second-to-last element and the last element in the sequence.
1297           * @param {Number} end Zero-based index at which to end extraction. slice extracts up to but not including end.
1298           slice(1,4) extracts the second element through the fourth element (elements indexed 1, 2, and 3).
1299           As a negative index, end indicates an offset from the end of the sequence. slice(2,-1) extracts the third element through the second-to-last element in the sequence.
1300           If end is omitted, slice extracts to the end of the sequence.
1301           * @return {NodeList} A new NodeList comprised of this NodeList joined with the input.
1302           */
1303         'slice': 1,
1304         /** Changes the content of the NodeList, adding new elements while removing old elements.
1305           * @for NodeList
1306           * @method splice
1307           * @param {Number} index Index at which to start changing the array. If negative, will begin that many elements from the end.
1308           * @param {Number} howMany An integer indicating the number of old array elements to remove. If howMany is 0, no elements are removed. In this case, you should specify at least one new element. If no howMany parameter is specified (second syntax above, which is a SpiderMonkey extension), all elements after index are removed.
1309           * {Node | DOMNode| element1, ..., elementN
1310           The elements to add to the array. If you don't specify any elements, splice simply removes elements from the array.
1311           * @return {NodeList} The element(s) removed.
1312           */
1313         'splice': 1,
1314         /** Adds the given Node(s) to the beginning of the NodeList.
1315           * @for NodeList
1316           * @method unshift
1317           * @param {Node | DOMNode} nodes One or more nodes to add to the NodeList.
1318           */
1319         'unshift': 0
1320     };
1323 Y.Object.each(ArrayMethods, function(returnNodeList, name) {
1324     Y_NodeList.prototype[name] = function() {
1325         var args = [],
1326             i = 0,
1327             arg,
1328             ret;
1330         while (typeof (arg = arguments[i++]) != 'undefined') { // use DOM nodes/nodeLists
1331             args.push(arg._node || arg._nodes || arg);
1332         }
1334         ret = ArrayProto[name].apply(this._nodes, args);
1336         if (returnNodeList) {
1337             ret = Y.all(ret);
1338         } else {
1339             ret = Y.Node.scrubVal(ret);
1340         }
1342         return ret;
1343     };
1346  * @module node
1347  * @submodule node-core
1348  */
1350 Y.Array.each([
1351     /**
1352      * Passes through to DOM method.
1353      * @for Node
1354      * @method removeChild
1355      * @param {HTMLElement | Node} node Node to be removed
1356      * @return {Node} The removed node
1357      */
1358     'removeChild',
1360     /**
1361      * Passes through to DOM method.
1362      * @method hasChildNodes
1363      * @return {Boolean} Whether or not the node has any childNodes
1364      */
1365     'hasChildNodes',
1367     /**
1368      * Passes through to DOM method.
1369      * @method cloneNode
1370      * @param {Boolean} deep Whether or not to perform a deep clone, which includes
1371      * subtree and attributes
1372      * @return {Node} The clone
1373      */
1374     'cloneNode',
1376     /**
1377      * Passes through to DOM method.
1378      * @method hasAttribute
1379      * @param {String} attribute The attribute to test for
1380      * @return {Boolean} Whether or not the attribute is present
1381      */
1382     'hasAttribute',
1384     /**
1385      * Passes through to DOM method.
1386      * @method scrollIntoView
1387      * @chainable
1388      */
1389     'scrollIntoView',
1391     /**
1392      * Passes through to DOM method.
1393      * @method getElementsByTagName
1394      * @param {String} tagName The tagName to collect
1395      * @return {NodeList} A NodeList representing the HTMLCollection
1396      */
1397     'getElementsByTagName',
1399     /**
1400      * Passes through to DOM method.
1401      * @method focus
1402      * @chainable
1403      */
1404     'focus',
1406     /**
1407      * Passes through to DOM method.
1408      * @method blur
1409      * @chainable
1410      */
1411     'blur',
1413     /**
1414      * Passes through to DOM method.
1415      * Only valid on FORM elements
1416      * @method submit
1417      * @chainable
1418      */
1419     'submit',
1421     /**
1422      * Passes through to DOM method.
1423      * Only valid on FORM elements
1424      * @method reset
1425      * @chainable
1426      */
1427     'reset',
1429     /**
1430      * Passes through to DOM method.
1431      * @method select
1432      * @chainable
1433      */
1434      'select',
1436     /**
1437      * Passes through to DOM method.
1438      * Only valid on TABLE elements
1439      * @method createCaption
1440      * @chainable
1441      */
1442     'createCaption'
1444 ], function(method) {
1445     Y.log('adding: ' + method, 'info', 'node');
1446     Y.Node.prototype[method] = function(arg1, arg2, arg3) {
1447         var ret = this.invoke(method, arg1, arg2, arg3);
1448         return ret;
1449     };
1453  * Passes through to DOM method.
1454  * @method removeAttribute
1455  * @param {String} attribute The attribute to be removed
1456  * @chainable
1457  */
1458  // one-off implementation due to IE returning boolean, breaking chaining
1459 Y.Node.prototype.removeAttribute = function(attr) {
1460     var node = this._node;
1461     if (node) {
1462         node.removeAttribute(attr);
1463     }
1465     return this;
1468 Y.Node.importMethod(Y.DOM, [
1469     /**
1470      * Determines whether the node is an ancestor of another HTML element in the DOM hierarchy.
1471      * @method contains
1472      * @param {Node | HTMLElement} needle The possible node or descendent
1473      * @return {Boolean} Whether or not this node is the needle its ancestor
1474      */
1475     'contains',
1476     /**
1477      * Allows setting attributes on DOM nodes, normalizing in some cases.
1478      * This passes through to the DOM node, allowing for custom attributes.
1479      * @method setAttribute
1480      * @for Node
1481      * @chainable
1482      * @param {string} name The attribute name
1483      * @param {string} value The value to set
1484      */
1485     'setAttribute',
1486     /**
1487      * Allows getting attributes on DOM nodes, normalizing in some cases.
1488      * This passes through to the DOM node, allowing for custom attributes.
1489      * @method getAttribute
1490      * @for Node
1491      * @param {string} name The attribute name
1492      * @return {string} The attribute value
1493      */
1494     'getAttribute',
1496     /**
1497      * Wraps the given HTML around the node.
1498      * @method wrap
1499      * @param {String} html The markup to wrap around the node.
1500      * @chainable
1501      * @for Node
1502      */
1503     'wrap',
1505     /**
1506      * Removes the node's parent node.
1507      * @method unwrap
1508      * @chainable
1509      */
1510     'unwrap',
1512     /**
1513      * Applies a unique ID to the node if none exists
1514      * @method generateID
1515      * @return {String} The existing or generated ID
1516      */
1517     'generateID'
1520 Y.NodeList.importMethod(Y.Node.prototype, [
1522  * Allows getting attributes on DOM nodes, normalizing in some cases.
1523  * This passes through to the DOM node, allowing for custom attributes.
1524  * @method getAttribute
1525  * @see Node
1526  * @for NodeList
1527  * @param {string} name The attribute name
1528  * @return {string} The attribute value
1529  */
1531     'getAttribute',
1533  * Allows setting attributes on DOM nodes, normalizing in some cases.
1534  * This passes through to the DOM node, allowing for custom attributes.
1535  * @method setAttribute
1536  * @see Node
1537  * @for NodeList
1538  * @chainable
1539  * @param {string} name The attribute name
1540  * @param {string} value The value to set
1541  */
1542     'setAttribute',
1545  * Allows for removing attributes on DOM nodes.
1546  * This passes through to the DOM node, allowing for custom attributes.
1547  * @method removeAttribute
1548  * @see Node
1549  * @for NodeList
1550  * @param {string} name The attribute to remove
1551  */
1552     'removeAttribute',
1554  * Removes the parent node from node in the list.
1555  * @method unwrap
1556  * @chainable
1557  */
1558     'unwrap',
1560  * Wraps the given HTML around each node.
1561  * @method wrap
1562  * @param {String} html The markup to wrap around the node.
1563  * @chainable
1564  */
1565     'wrap',
1568  * Applies a unique ID to each node if none exists
1569  * @method generateID
1570  * @return {String} The existing or generated ID
1571  */
1572     'generateID'
1576 }, '3.5.0' ,{requires:['dom-core', 'selector']});