Merge branch 'MDL-32509' of git://github.com/danpoltawski/moodle
[moodle.git] / lib / yui / 3.5.0 / build / node-core / node-core.js
blobe83de4fbf00b7df54739c4b55b1be627dbf9aef6
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     }
227  * Imports utility methods to be added as Y.Node methods.
228  * @method importMethod
229  * @static
231  * @param {Object} host The object that contains the method to import.
232  * @param {String} name The name of the method to import
233  * @param {String} altName An optional name to use in place of the host name
234  * @param {Object} context An optional context to call the method with
235  */
236 Y_Node.importMethod = function(host, name, altName) {
237     if (typeof name == 'string') {
238         altName = altName || name;
239         Y_Node.addMethod(altName, host[name], host);
240     } else {
241         Y.Array.each(name, function(n) {
242             Y_Node.importMethod(host, n);
243         });
244     }
248  * Retrieves a NodeList based on the given CSS selector.
249  * @method all
251  * @param {string} selector The CSS selector to test against.
252  * @return {NodeList} A NodeList instance for the matching HTMLCollection/Array.
253  * @for YUI
254  */
257  * Returns a single Node instance bound to the node or the
258  * first element matching the given selector. Returns null if no match found.
259  * <strong>Note:</strong> For chaining purposes you may want to
260  * use <code>Y.all</code>, which returns a NodeList when no match is found.
261  * @method one
262  * @param {String | HTMLElement} node a node or Selector
263  * @return {Node | null} a Node instance or null if no match found.
264  * @for YUI
265  */
268  * Returns a single Node instance bound to the node or the
269  * first element matching the given selector. Returns null if no match found.
270  * <strong>Note:</strong> For chaining purposes you may want to
271  * use <code>Y.all</code>, which returns a NodeList when no match is found.
272  * @method one
273  * @static
274  * @param {String | HTMLElement} node a node or Selector
275  * @return {Node | null} a Node instance or null if no match found.
276  * @for Node
277  */
278 Y_Node.one = function(node) {
279     var instance = null,
280         cachedNode,
281         uid;
283     if (node) {
284         if (typeof node == 'string') {
285             node = Y_Node._fromString(node);
286             if (!node) {
287                 return null; // NOTE: return
288             }
289         } else if (node.getDOMNode) {
290             return node; // NOTE: return
291         }
293         if (node.nodeType || Y.DOM.isWindow(node)) { // avoid bad input (numbers, boolean, etc)
294             uid = (node.uniqueID && node.nodeType !== 9) ? node.uniqueID : node._yuid;
295             instance = Y_Node._instances[uid]; // reuse exising instances
296             cachedNode = instance ? instance._node : null;
297             if (!instance || (cachedNode && node !== cachedNode)) { // new Node when nodes don't match
298                 instance = new Y_Node(node);
299                 if (node.nodeType != 11) { // dont cache document fragment
300                     Y_Node._instances[instance[UID]] = instance; // cache node
301                 }
302             }
303         }
304     }
306     return instance;
310  * The default setter for DOM properties
311  * Called with instance context (this === the Node instance)
312  * @method DEFAULT_SETTER
313  * @static
314  * @param {String} name The attribute/property being set
315  * @param {any} val The value to be set
316  * @return {any} The value
317  */
318 Y_Node.DEFAULT_SETTER = function(name, val) {
319     var node = this._stateProxy,
320         strPath;
322     if (name.indexOf(DOT) > -1) {
323         strPath = name;
324         name = name.split(DOT);
325         // only allow when defined on node
326         Y.Object.setValue(node, name, val);
327     } else if (typeof node[name] != 'undefined') { // pass thru DOM properties
328         node[name] = val;
329     }
331     return val;
335  * The default getter for DOM properties
336  * Called with instance context (this === the Node instance)
337  * @method DEFAULT_GETTER
338  * @static
339  * @param {String} name The attribute/property to look up
340  * @return {any} The current value
341  */
342 Y_Node.DEFAULT_GETTER = function(name) {
343     var node = this._stateProxy,
344         val;
346     if (name.indexOf && name.indexOf(DOT) > -1) {
347         val = Y.Object.getValue(node, name.split(DOT));
348     } else if (typeof node[name] != 'undefined') { // pass thru from DOM
349         val = node[name];
350     }
352     return val;
355 Y.mix(Y_Node.prototype, {
356     DATA_PREFIX: 'data-',
358     /**
359      * The method called when outputting Node instances as strings
360      * @method toString
361      * @return {String} A string representation of the Node instance
362      */
363     toString: function() {
364         var str = this[UID] + ': not bound to a node',
365             node = this._node,
366             attrs, id, className;
368         if (node) {
369             attrs = node.attributes;
370             id = (attrs && attrs.id) ? node.getAttribute('id') : null;
371             className = (attrs && attrs.className) ? node.getAttribute('className') : null;
372             str = node[NODE_NAME];
374             if (id) {
375                 str += '#' + id;
376             }
378             if (className) {
379                 str += '.' + className.replace(' ', '.');
380             }
382             // TODO: add yuid?
383             str += ' ' + this[UID];
384         }
385         return str;
386     },
388     /**
389      * Returns an attribute value on the Node instance.
390      * Unless pre-configured (via `Node.ATTRS`), get hands
391      * off to the underlying DOM node.  Only valid
392      * attributes/properties for the node will be queried.
393      * @method get
394      * @param {String} attr The attribute
395      * @return {any} The current value of the attribute
396      */
397     get: function(attr) {
398         var val;
400         if (this._getAttr) { // use Attribute imple
401             val = this._getAttr(attr);
402         } else {
403             val = this._get(attr);
404         }
406         if (val) {
407             val = Y_Node.scrubVal(val, this);
408         } else if (val === null) {
409             val = null; // IE: DOM null is not true null (even though they ===)
410         }
411         return val;
412     },
414     /**
415      * Helper method for get.
416      * @method _get
417      * @private
418      * @param {String} attr The attribute
419      * @return {any} The current value of the attribute
420      */
421     _get: function(attr) {
422         var attrConfig = Y_Node.ATTRS[attr],
423             val;
425         if (attrConfig && attrConfig.getter) {
426             val = attrConfig.getter.call(this);
427         } else if (Y_Node.re_aria.test(attr)) {
428             val = this._node.getAttribute(attr, 2);
429         } else {
430             val = Y_Node.DEFAULT_GETTER.apply(this, arguments);
431         }
433         return val;
434     },
436     /**
437      * Sets an attribute on the Node instance.
438      * Unless pre-configured (via Node.ATTRS), set hands
439      * off to the underlying DOM node.  Only valid
440      * attributes/properties for the node will be set.
441      * To set custom attributes use setAttribute.
442      * @method set
443      * @param {String} attr The attribute to be set.
444      * @param {any} val The value to set the attribute to.
445      * @chainable
446      */
447     set: function(attr, val) {
448         var attrConfig = Y_Node.ATTRS[attr];
450         if (this._setAttr) { // use Attribute imple
451             this._setAttr.apply(this, arguments);
452         } else { // use setters inline
453             if (attrConfig && attrConfig.setter) {
454                 attrConfig.setter.call(this, val, attr);
455             } else if (Y_Node.re_aria.test(attr)) { // special case Aria
456                 this._node.setAttribute(attr, val);
457             } else {
458                 Y_Node.DEFAULT_SETTER.apply(this, arguments);
459             }
460         }
462         return this;
463     },
465     /**
466      * Sets multiple attributes.
467      * @method setAttrs
468      * @param {Object} attrMap an object of name/value pairs to set
469      * @chainable
470      */
471     setAttrs: function(attrMap) {
472         if (this._setAttrs) { // use Attribute imple
473             this._setAttrs(attrMap);
474         } else { // use setters inline
475             Y.Object.each(attrMap, function(v, n) {
476                 this.set(n, v);
477             }, this);
478         }
480         return this;
481     },
483     /**
484      * Returns an object containing the values for the requested attributes.
485      * @method getAttrs
486      * @param {Array} attrs an array of attributes to get values
487      * @return {Object} An object with attribute name/value pairs.
488      */
489     getAttrs: function(attrs) {
490         var ret = {};
491         if (this._getAttrs) { // use Attribute imple
492             this._getAttrs(attrs);
493         } else { // use setters inline
494             Y.Array.each(attrs, function(v, n) {
495                 ret[v] = this.get(v);
496             }, this);
497         }
499         return ret;
500     },
502     /**
503      * Compares nodes to determine if they match.
504      * Node instances can be compared to each other and/or HTMLElements.
505      * @method compareTo
506      * @param {HTMLElement | Node} refNode The reference node to compare to the node.
507      * @return {Boolean} True if the nodes match, false if they do not.
508      */
509     compareTo: function(refNode) {
510         var node = this._node;
512         if (refNode && refNode._node) {
513             refNode = refNode._node;
514         }
515         return node === refNode;
516     },
518     /**
519      * Determines whether the node is appended to the document.
520      * @method inDoc
521      * @param {Node|HTMLElement} doc optional An optional document to check against.
522      * Defaults to current document.
523      * @return {Boolean} Whether or not this node is appended to the document.
524      */
525     inDoc: function(doc) {
526         var node = this._node;
527         doc = (doc) ? doc._node || doc : node[OWNER_DOCUMENT];
528         if (doc.documentElement) {
529             return Y_DOM.contains(doc.documentElement, node);
530         }
531     },
533     getById: function(id) {
534         var node = this._node,
535             ret = Y_DOM.byId(id, node[OWNER_DOCUMENT]);
536         if (ret && Y_DOM.contains(node, ret)) {
537             ret = Y.one(ret);
538         } else {
539             ret = null;
540         }
541         return ret;
542     },
544    /**
545      * Returns the nearest ancestor that passes the test applied by supplied boolean method.
546      * @method ancestor
547      * @param {String | Function} fn A selector string or boolean method for testing elements.
548      * If a function is used, it receives the current node being tested as the only argument.
549      * @param {Boolean} testSelf optional Whether or not to include the element in the scan
550      * @param {String | Function} stopFn optional A selector string or boolean
551      * method to indicate when the search should stop. The search bails when the function
552      * returns true or the selector matches.
553      * If a function is used, it receives the current node being tested as the only argument.
554      * @return {Node} The matching Node instance or null if not found
555      */
556     ancestor: function(fn, testSelf, stopFn) {
557         // testSelf is optional, check for stopFn as 2nd arg
558         if (arguments.length === 2 &&
559                 (typeof testSelf == 'string' || typeof testSelf == 'function')) {
560             stopFn = testSelf;
561         }
563         return Y.one(Y_DOM.ancestor(this._node, _wrapFn(fn), testSelf, _wrapFn(stopFn)));
564     },
566    /**
567      * Returns the ancestors that pass the test applied by supplied boolean method.
568      * @method ancestors
569      * @param {String | Function} fn A selector string or boolean method for testing elements.
570      * @param {Boolean} testSelf optional Whether or not to include the element in the scan
571      * If a function is used, it receives the current node being tested as the only argument.
572      * @return {NodeList} A NodeList instance containing the matching elements
573      */
574     ancestors: function(fn, testSelf, stopFn) {
575         if (arguments.length === 2 &&
576                 (typeof testSelf == 'string' || typeof testSelf == 'function')) {
577             stopFn = testSelf;
578         }
579         return Y.all(Y_DOM.ancestors(this._node, _wrapFn(fn), testSelf, _wrapFn(stopFn)));
580     },
582     /**
583      * Returns the previous matching sibling.
584      * Returns the nearest element node sibling if no method provided.
585      * @method previous
586      * @param {String | Function} fn A selector or boolean method for testing elements.
587      * If a function is used, it receives the current node being tested as the only argument.
588      * @return {Node} Node instance or null if not found
589      */
590     previous: function(fn, all) {
591         return Y.one(Y_DOM.elementByAxis(this._node, 'previousSibling', _wrapFn(fn), all));
592     },
594     /**
595      * Returns the next matching sibling.
596      * Returns the nearest element node sibling if no method provided.
597      * @method next
598      * @param {String | Function} fn A selector or boolean method for testing elements.
599      * If a function is used, it receives the current node being tested as the only argument.
600      * @return {Node} Node instance or null if not found
601      */
602     next: function(fn, all) {
603         return Y.one(Y_DOM.elementByAxis(this._node, 'nextSibling', _wrapFn(fn), all));
604     },
606     /**
607      * Returns all matching siblings.
608      * Returns all siblings if no method provided.
609      * @method siblings
610      * @param {String | Function} fn A selector or boolean method for testing elements.
611      * If a function is used, it receives the current node being tested as the only argument.
612      * @return {NodeList} NodeList instance bound to found siblings
613      */
614     siblings: function(fn) {
615         return Y.all(Y_DOM.siblings(this._node, _wrapFn(fn)));
616     },
618     /**
619      * Retrieves a Node instance of nodes based on the given CSS selector.
620      * @method one
621      *
622      * @param {string} selector The CSS selector to test against.
623      * @return {Node} A Node instance for the matching HTMLElement.
624      */
625     one: function(selector) {
626         return Y.one(Y.Selector.query(selector, this._node, true));
627     },
629     /**
630      * Retrieves a NodeList based on the given CSS selector.
631      * @method all
632      *
633      * @param {string} selector The CSS selector to test against.
634      * @return {NodeList} A NodeList instance for the matching HTMLCollection/Array.
635      */
636     all: function(selector) {
637         var nodelist = Y.all(Y.Selector.query(selector, this._node));
638         nodelist._query = selector;
639         nodelist._queryRoot = this._node;
640         return nodelist;
641     },
643     // TODO: allow fn test
644     /**
645      * Test if the supplied node matches the supplied selector.
646      * @method test
647      *
648      * @param {string} selector The CSS selector to test against.
649      * @return {boolean} Whether or not the node matches the selector.
650      */
651     test: function(selector) {
652         return Y.Selector.test(this._node, selector);
653     },
655     /**
656      * Removes the node from its parent.
657      * Shortcut for myNode.get('parentNode').removeChild(myNode);
658      * @method remove
659      * @param {Boolean} destroy whether or not to call destroy() on the node
660      * after removal.
661      * @chainable
662      *
663      */
664     remove: function(destroy) {
665         var node = this._node;
667         if (node && node.parentNode) {
668             node.parentNode.removeChild(node);
669         }
671         if (destroy) {
672             this.destroy();
673         }
675         return this;
676     },
678     /**
679      * Replace the node with the other node. This is a DOM update only
680      * and does not change the node bound to the Node instance.
681      * Shortcut for myNode.get('parentNode').replaceChild(newNode, myNode);
682      * @method replace
683      * @param {Node | HTMLNode} newNode Node to be inserted
684      * @chainable
685      *
686      */
687     replace: function(newNode) {
688         var node = this._node;
689         if (typeof newNode == 'string') {
690             newNode = Y_Node.create(newNode);
691         }
692         node.parentNode.replaceChild(Y_Node.getDOMNode(newNode), node);
693         return this;
694     },
696     /**
697      * @method replaceChild
698      * @for Node
699      * @param {String | HTMLElement | Node} node Node to be inserted
700      * @param {HTMLElement | Node} refNode Node to be replaced
701      * @return {Node} The replaced node
702      */
703     replaceChild: function(node, refNode) {
704         if (typeof node == 'string') {
705             node = Y_DOM.create(node);
706         }
708         return Y.one(this._node.replaceChild(Y_Node.getDOMNode(node), Y_Node.getDOMNode(refNode)));
709     },
711     /**
712      * Nulls internal node references, removes any plugins and event listeners
713      * @method destroy
714      * @param {Boolean} recursivePurge (optional) Whether or not to remove listeners from the
715      * node's subtree (default is false)
716      *
717      */
718     destroy: function(recursive) {
719         var UID = Y.config.doc.uniqueID ? 'uniqueID' : '_yuid',
720             instance;
722         this.purge(); // TODO: only remove events add via this Node
724         if (this.unplug) { // may not be a PluginHost
725             this.unplug();
726         }
728         this.clearData();
730         if (recursive) {
731             Y.NodeList.each(this.all('*'), function(node) {
732                 instance = Y_Node._instances[node[UID]];
733                 if (instance) {
734                    instance.destroy();
735                 }
736             });
737         }
739         this._node = null;
740         this._stateProxy = null;
742         delete Y_Node._instances[this._yuid];
743     },
745     /**
746      * Invokes a method on the Node instance
747      * @method invoke
748      * @param {String} method The name of the method to invoke
749      * @param {Any}  a, b, c, etc. Arguments to invoke the method with.
750      * @return Whatever the underly method returns.
751      * DOM Nodes and Collections return values
752      * are converted to Node/NodeList instances.
753      *
754      */
755     invoke: function(method, a, b, c, d, e) {
756         var node = this._node,
757             ret;
759         if (a && a._node) {
760             a = a._node;
761         }
763         if (b && b._node) {
764             b = b._node;
765         }
767         ret = node[method](a, b, c, d, e);
768         return Y_Node.scrubVal(ret, this);
769     },
771     /**
772     * @method swap
773     * @description Swap DOM locations with the given node.
774     * This does not change which DOM node each Node instance refers to.
775     * @param {Node} otherNode The node to swap with
776      * @chainable
777     */
778     swap: Y.config.doc.documentElement.swapNode ?
779         function(otherNode) {
780             this._node.swapNode(Y_Node.getDOMNode(otherNode));
781         } :
782         function(otherNode) {
783             otherNode = Y_Node.getDOMNode(otherNode);
784             var node = this._node,
785                 parent = otherNode.parentNode,
786                 nextSibling = otherNode.nextSibling;
788             if (nextSibling === node) {
789                 parent.insertBefore(node, otherNode);
790             } else if (otherNode === node.nextSibling) {
791                 parent.insertBefore(otherNode, node);
792             } else {
793                 node.parentNode.replaceChild(otherNode, node);
794                 Y_DOM.addHTML(parent, node, nextSibling);
795             }
796             return this;
797         },
800     hasMethod: function(method) {
801         var node = this._node;
802         return !!(node && method in node &&
803                 typeof node[method] != 'unknown' &&
804             (typeof node[method] == 'function' ||
805                 String(node[method]).indexOf('function') === 1)); // IE reports as object, prepends space
806     },
808     isFragment: function() {
809         return (this.get('nodeType') === 11);
810     },
812     /**
813      * Removes and destroys all of the nodes within the node.
814      * @method empty
815      * @chainable
816      */
817     empty: function() {
818         this.get('childNodes').remove().destroy(true);
819         return this;
820     },
822     /**
823      * Returns the DOM node bound to the Node instance
824      * @method getDOMNode
825      * @return {DOMNode}
826      */
827     getDOMNode: function() {
828         return this._node;
829     }
830 }, true);
832 Y.Node = Y_Node;
833 Y.one = Y_Node.one;
835  * The NodeList module provides support for managing collections of Nodes.
836  * @module node
837  * @submodule node-core
838  */
841  * The NodeList class provides a wrapper for manipulating DOM NodeLists.
842  * NodeList properties can be accessed via the set/get methods.
843  * Use Y.all() to retrieve NodeList instances.
845  * @class NodeList
846  * @constructor
847  */
849 var NodeList = function(nodes) {
850     var tmp = [];
852     if (nodes) {
853         if (typeof nodes === 'string') { // selector query
854             this._query = nodes;
855             nodes = Y.Selector.query(nodes);
856         } else if (nodes.nodeType || Y_DOM.isWindow(nodes)) { // domNode || window
857             nodes = [nodes];
858         } else if (nodes._node) { // Y.Node
859             nodes = [nodes._node];
860         } else if (nodes[0] && nodes[0]._node) { // allow array of Y.Nodes
861             Y.Array.each(nodes, function(node) {
862                 if (node._node) {
863                     tmp.push(node._node);
864                 }
865             });
866             nodes = tmp;
867         } else { // array of domNodes or domNodeList (no mixed array of Y.Node/domNodes)
868             nodes = Y.Array(nodes, 0, true);
869         }
870     }
872     /**
873      * The underlying array of DOM nodes bound to the Y.NodeList instance
874      * @property _nodes
875      * @private
876      */
877     this._nodes = nodes || [];
880 NodeList.NAME = 'NodeList';
883  * Retrieves the DOM nodes bound to a NodeList instance
884  * @method getDOMNodes
885  * @static
887  * @param {NodeList} nodelist The NodeList instance
888  * @return {Array} The array of DOM nodes bound to the NodeList
889  */
890 NodeList.getDOMNodes = function(nodelist) {
891     return (nodelist && nodelist._nodes) ? nodelist._nodes : nodelist;
894 NodeList.each = function(instance, fn, context) {
895     var nodes = instance._nodes;
896     if (nodes && nodes.length) {
897         Y.Array.each(nodes, fn, context || instance);
898     } else {
899     }
902 NodeList.addMethod = function(name, fn, context) {
903     if (name && fn) {
904         NodeList.prototype[name] = function() {
905             var ret = [],
906                 args = arguments;
908             Y.Array.each(this._nodes, function(node) {
909                 var UID = (node.uniqueID && node.nodeType !== 9 ) ? 'uniqueID' : '_yuid',
910                     instance = Y.Node._instances[node[UID]],
911                     ctx,
912                     result;
914                 if (!instance) {
915                     instance = NodeList._getTempNode(node);
916                 }
917                 ctx = context || instance;
918                 result = fn.apply(ctx, args);
919                 if (result !== undefined && result !== instance) {
920                     ret[ret.length] = result;
921                 }
922             });
924             // TODO: remove tmp pointer
925             return ret.length ? ret : this;
926         };
927     } else {
928     }
931 NodeList.importMethod = function(host, name, altName) {
932     if (typeof name === 'string') {
933         altName = altName || name;
934         NodeList.addMethod(name, host[name]);
935     } else {
936         Y.Array.each(name, function(n) {
937             NodeList.importMethod(host, n);
938         });
939     }
942 NodeList._getTempNode = function(node) {
943     var tmp = NodeList._tempNode;
944     if (!tmp) {
945         tmp = Y.Node.create('<div></div>');
946         NodeList._tempNode = tmp;
947     }
949     tmp._node = node;
950     tmp._stateProxy = node;
951     return tmp;
954 Y.mix(NodeList.prototype, {
955     _invoke: function(method, args, getter) {
956         var ret = (getter) ? [] : this;
958         this.each(function(node) {
959             var val = node[method].apply(node, args);
960             if (getter) {
961                 ret.push(val);
962             }
963         });
965         return ret;
966     },
968     /**
969      * Retrieves the Node instance at the given index.
970      * @method item
971      *
972      * @param {Number} index The index of the target Node.
973      * @return {Node} The Node instance at the given index.
974      */
975     item: function(index) {
976         return Y.one((this._nodes || [])[index]);
977     },
979     /**
980      * Applies the given function to each Node in the NodeList.
981      * @method each
982      * @param {Function} fn The function to apply. It receives 3 arguments:
983      * the current node instance, the node's index, and the NodeList instance
984      * @param {Object} context optional An optional context to apply the function with
985      * Default context is the current Node instance
986      * @chainable
987      */
988     each: function(fn, context) {
989         var instance = this;
990         Y.Array.each(this._nodes, function(node, index) {
991             node = Y.one(node);
992             return fn.call(context || node, node, index, instance);
993         });
994         return instance;
995     },
997     batch: function(fn, context) {
998         var nodelist = this;
1000         Y.Array.each(this._nodes, function(node, index) {
1001             var instance = Y.Node._instances[node[UID]];
1002             if (!instance) {
1003                 instance = NodeList._getTempNode(node);
1004             }
1006             return fn.call(context || instance, instance, index, nodelist);
1007         });
1008         return nodelist;
1009     },
1011     /**
1012      * Executes the function once for each node until a true value is returned.
1013      * @method some
1014      * @param {Function} fn The function to apply. It receives 3 arguments:
1015      * the current node instance, the node's index, and the NodeList instance
1016      * @param {Object} context optional An optional context to execute the function from.
1017      * Default context is the current Node instance
1018      * @return {Boolean} Whether or not the function returned true for any node.
1019      */
1020     some: function(fn, context) {
1021         var instance = this;
1022         return Y.Array.some(this._nodes, function(node, index) {
1023             node = Y.one(node);
1024             context = context || node;
1025             return fn.call(context, node, index, instance);
1026         });
1027     },
1029     /**
1030      * Creates a documenFragment from the nodes bound to the NodeList instance
1031      * @method toFrag
1032      * @return {Node} a Node instance bound to the documentFragment
1033      */
1034     toFrag: function() {
1035         return Y.one(Y.DOM._nl2frag(this._nodes));
1036     },
1038     /**
1039      * Returns the index of the node in the NodeList instance
1040      * or -1 if the node isn't found.
1041      * @method indexOf
1042      * @param {Node | DOMNode} node the node to search for
1043      * @return {Int} the index of the node value or -1 if not found
1044      */
1045     indexOf: function(node) {
1046         return Y.Array.indexOf(this._nodes, Y.Node.getDOMNode(node));
1047     },
1049     /**
1050      * Filters the NodeList instance down to only nodes matching the given selector.
1051      * @method filter
1052      * @param {String} selector The selector to filter against
1053      * @return {NodeList} NodeList containing the updated collection
1054      * @see Selector
1055      */
1056     filter: function(selector) {
1057         return Y.all(Y.Selector.filter(this._nodes, selector));
1058     },
1061     /**
1062      * Creates a new NodeList containing all nodes at every n indices, where
1063      * remainder n % index equals r.
1064      * (zero-based index).
1065      * @method modulus
1066      * @param {Int} n The offset to use (return every nth node)
1067      * @param {Int} r An optional remainder to use with the modulus operation (defaults to zero)
1068      * @return {NodeList} NodeList containing the updated collection
1069      */
1070     modulus: function(n, r) {
1071         r = r || 0;
1072         var nodes = [];
1073         NodeList.each(this, function(node, i) {
1074             if (i % n === r) {
1075                 nodes.push(node);
1076             }
1077         });
1079         return Y.all(nodes);
1080     },
1082     /**
1083      * Creates a new NodeList containing all nodes at odd indices
1084      * (zero-based index).
1085      * @method odd
1086      * @return {NodeList} NodeList containing the updated collection
1087      */
1088     odd: function() {
1089         return this.modulus(2, 1);
1090     },
1092     /**
1093      * Creates a new NodeList containing all nodes at even indices
1094      * (zero-based index), including zero.
1095      * @method even
1096      * @return {NodeList} NodeList containing the updated collection
1097      */
1098     even: function() {
1099         return this.modulus(2);
1100     },
1102     destructor: function() {
1103     },
1105     /**
1106      * Reruns the initial query, when created using a selector query
1107      * @method refresh
1108      * @chainable
1109      */
1110     refresh: function() {
1111         var doc,
1112             nodes = this._nodes,
1113             query = this._query,
1114             root = this._queryRoot;
1116         if (query) {
1117             if (!root) {
1118                 if (nodes && nodes[0] && nodes[0].ownerDocument) {
1119                     root = nodes[0].ownerDocument;
1120                 }
1121             }
1123             this._nodes = Y.Selector.query(query, root);
1124         }
1126         return this;
1127     },
1129     /**
1130      * Returns the current number of items in the NodeList.
1131      * @method size
1132      * @return {Int} The number of items in the NodeList.
1133      */
1134     size: function() {
1135         return this._nodes.length;
1136     },
1138     /**
1139      * Determines if the instance is bound to any nodes
1140      * @method isEmpty
1141      * @return {Boolean} Whether or not the NodeList is bound to any nodes
1142      */
1143     isEmpty: function() {
1144         return this._nodes.length < 1;
1145     },
1147     toString: function() {
1148         var str = '',
1149             errorMsg = this[UID] + ': not bound to any nodes',
1150             nodes = this._nodes,
1151             node;
1153         if (nodes && nodes[0]) {
1154             node = nodes[0];
1155             str += node[NODE_NAME];
1156             if (node.id) {
1157                 str += '#' + node.id;
1158             }
1160             if (node.className) {
1161                 str += '.' + node.className.replace(' ', '.');
1162             }
1164             if (nodes.length > 1) {
1165                 str += '...[' + nodes.length + ' items]';
1166             }
1167         }
1168         return str || errorMsg;
1169     },
1171     /**
1172      * Returns the DOM node bound to the Node instance
1173      * @method getDOMNodes
1174      * @return {Array}
1175      */
1176     getDOMNodes: function() {
1177         return this._nodes;
1178     }
1179 }, true);
1181 NodeList.importMethod(Y.Node.prototype, [
1182     /** Called on each Node instance
1183       * @method destroy
1184       * @see Node.destroy
1185       */
1186     'destroy',
1188     /** Called on each Node instance
1189       * @method empty
1190       * @see Node.empty
1191       */
1192     'empty',
1194     /** Called on each Node instance
1195       * @method remove
1196       * @see Node.remove
1197       */
1198     'remove',
1200     /** Called on each Node instance
1201       * @method set
1202       * @see Node.set
1203       */
1204     'set'
1207 // one-off implementation to convert array of Nodes to NodeList
1208 // e.g. Y.all('input').get('parentNode');
1210 /** Called on each Node instance
1211   * @method get
1212   * @see Node
1213   */
1214 NodeList.prototype.get = function(attr) {
1215     var ret = [],
1216         nodes = this._nodes,
1217         isNodeList = false,
1218         getTemp = NodeList._getTempNode,
1219         instance,
1220         val;
1222     if (nodes[0]) {
1223         instance = Y.Node._instances[nodes[0]._yuid] || getTemp(nodes[0]);
1224         val = instance._get(attr);
1225         if (val && val.nodeType) {
1226             isNodeList = true;
1227         }
1228     }
1230     Y.Array.each(nodes, function(node) {
1231         instance = Y.Node._instances[node._yuid];
1233         if (!instance) {
1234             instance = getTemp(node);
1235         }
1237         val = instance._get(attr);
1238         if (!isNodeList) { // convert array of Nodes to NodeList
1239             val = Y.Node.scrubVal(val, instance);
1240         }
1242         ret.push(val);
1243     });
1245     return (isNodeList) ? Y.all(ret) : ret;
1248 Y.NodeList = NodeList;
1250 Y.all = function(nodes) {
1251     return new NodeList(nodes);
1254 Y.Node.all = Y.all;
1256  * @module node
1257  * @submodule node-core
1258  */
1260 var Y_NodeList = Y.NodeList,
1261     ArrayProto = Array.prototype,
1262     ArrayMethods = {
1263         /** Returns a new NodeList combining the given NodeList(s)
1264           * @for NodeList
1265           * @method concat
1266           * @param {NodeList | Array} valueN Arrays/NodeLists and/or values to
1267           * concatenate to the resulting NodeList
1268           * @return {NodeList} A new NodeList comprised of this NodeList joined with the input.
1269           */
1270         'concat': 1,
1271         /** Removes the last from the NodeList and returns it.
1272           * @for NodeList
1273           * @method pop
1274           * @return {Node} The last item in the NodeList.
1275           */
1276         'pop': 0,
1277         /** Adds the given Node(s) to the end of the NodeList.
1278           * @for NodeList
1279           * @method push
1280           * @param {Node | DOMNode} nodes One or more nodes to add to the end of the NodeList.
1281           */
1282         'push': 0,
1283         /** Removes the first item from the NodeList and returns it.
1284           * @for NodeList
1285           * @method shift
1286           * @return {Node} The first item in the NodeList.
1287           */
1288         'shift': 0,
1289         /** Returns a new NodeList comprising the Nodes in the given range.
1290           * @for NodeList
1291           * @method slice
1292           * @param {Number} begin Zero-based index at which to begin extraction.
1293           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.
1294           * @param {Number} end Zero-based index at which to end extraction. slice extracts up to but not including end.
1295           slice(1,4) extracts the second element through the fourth element (elements indexed 1, 2, and 3).
1296           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.
1297           If end is omitted, slice extracts to the end of the sequence.
1298           * @return {NodeList} A new NodeList comprised of this NodeList joined with the input.
1299           */
1300         'slice': 1,
1301         /** Changes the content of the NodeList, adding new elements while removing old elements.
1302           * @for NodeList
1303           * @method splice
1304           * @param {Number} index Index at which to start changing the array. If negative, will begin that many elements from the end.
1305           * @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.
1306           * {Node | DOMNode| element1, ..., elementN
1307           The elements to add to the array. If you don't specify any elements, splice simply removes elements from the array.
1308           * @return {NodeList} The element(s) removed.
1309           */
1310         'splice': 1,
1311         /** Adds the given Node(s) to the beginning of the NodeList.
1312           * @for NodeList
1313           * @method unshift
1314           * @param {Node | DOMNode} nodes One or more nodes to add to the NodeList.
1315           */
1316         'unshift': 0
1317     };
1320 Y.Object.each(ArrayMethods, function(returnNodeList, name) {
1321     Y_NodeList.prototype[name] = function() {
1322         var args = [],
1323             i = 0,
1324             arg,
1325             ret;
1327         while (typeof (arg = arguments[i++]) != 'undefined') { // use DOM nodes/nodeLists
1328             args.push(arg._node || arg._nodes || arg);
1329         }
1331         ret = ArrayProto[name].apply(this._nodes, args);
1333         if (returnNodeList) {
1334             ret = Y.all(ret);
1335         } else {
1336             ret = Y.Node.scrubVal(ret);
1337         }
1339         return ret;
1340     };
1343  * @module node
1344  * @submodule node-core
1345  */
1347 Y.Array.each([
1348     /**
1349      * Passes through to DOM method.
1350      * @for Node
1351      * @method removeChild
1352      * @param {HTMLElement | Node} node Node to be removed
1353      * @return {Node} The removed node
1354      */
1355     'removeChild',
1357     /**
1358      * Passes through to DOM method.
1359      * @method hasChildNodes
1360      * @return {Boolean} Whether or not the node has any childNodes
1361      */
1362     'hasChildNodes',
1364     /**
1365      * Passes through to DOM method.
1366      * @method cloneNode
1367      * @param {Boolean} deep Whether or not to perform a deep clone, which includes
1368      * subtree and attributes
1369      * @return {Node} The clone
1370      */
1371     'cloneNode',
1373     /**
1374      * Passes through to DOM method.
1375      * @method hasAttribute
1376      * @param {String} attribute The attribute to test for
1377      * @return {Boolean} Whether or not the attribute is present
1378      */
1379     'hasAttribute',
1381     /**
1382      * Passes through to DOM method.
1383      * @method scrollIntoView
1384      * @chainable
1385      */
1386     'scrollIntoView',
1388     /**
1389      * Passes through to DOM method.
1390      * @method getElementsByTagName
1391      * @param {String} tagName The tagName to collect
1392      * @return {NodeList} A NodeList representing the HTMLCollection
1393      */
1394     'getElementsByTagName',
1396     /**
1397      * Passes through to DOM method.
1398      * @method focus
1399      * @chainable
1400      */
1401     'focus',
1403     /**
1404      * Passes through to DOM method.
1405      * @method blur
1406      * @chainable
1407      */
1408     'blur',
1410     /**
1411      * Passes through to DOM method.
1412      * Only valid on FORM elements
1413      * @method submit
1414      * @chainable
1415      */
1416     'submit',
1418     /**
1419      * Passes through to DOM method.
1420      * Only valid on FORM elements
1421      * @method reset
1422      * @chainable
1423      */
1424     'reset',
1426     /**
1427      * Passes through to DOM method.
1428      * @method select
1429      * @chainable
1430      */
1431      'select',
1433     /**
1434      * Passes through to DOM method.
1435      * Only valid on TABLE elements
1436      * @method createCaption
1437      * @chainable
1438      */
1439     'createCaption'
1441 ], function(method) {
1442     Y.Node.prototype[method] = function(arg1, arg2, arg3) {
1443         var ret = this.invoke(method, arg1, arg2, arg3);
1444         return ret;
1445     };
1449  * Passes through to DOM method.
1450  * @method removeAttribute
1451  * @param {String} attribute The attribute to be removed
1452  * @chainable
1453  */
1454  // one-off implementation due to IE returning boolean, breaking chaining
1455 Y.Node.prototype.removeAttribute = function(attr) {
1456     var node = this._node;
1457     if (node) {
1458         node.removeAttribute(attr);
1459     }
1461     return this;
1464 Y.Node.importMethod(Y.DOM, [
1465     /**
1466      * Determines whether the node is an ancestor of another HTML element in the DOM hierarchy.
1467      * @method contains
1468      * @param {Node | HTMLElement} needle The possible node or descendent
1469      * @return {Boolean} Whether or not this node is the needle its ancestor
1470      */
1471     'contains',
1472     /**
1473      * Allows setting attributes on DOM nodes, normalizing in some cases.
1474      * This passes through to the DOM node, allowing for custom attributes.
1475      * @method setAttribute
1476      * @for Node
1477      * @chainable
1478      * @param {string} name The attribute name
1479      * @param {string} value The value to set
1480      */
1481     'setAttribute',
1482     /**
1483      * Allows getting attributes on DOM nodes, normalizing in some cases.
1484      * This passes through to the DOM node, allowing for custom attributes.
1485      * @method getAttribute
1486      * @for Node
1487      * @param {string} name The attribute name
1488      * @return {string} The attribute value
1489      */
1490     'getAttribute',
1492     /**
1493      * Wraps the given HTML around the node.
1494      * @method wrap
1495      * @param {String} html The markup to wrap around the node.
1496      * @chainable
1497      * @for Node
1498      */
1499     'wrap',
1501     /**
1502      * Removes the node's parent node.
1503      * @method unwrap
1504      * @chainable
1505      */
1506     'unwrap',
1508     /**
1509      * Applies a unique ID to the node if none exists
1510      * @method generateID
1511      * @return {String} The existing or generated ID
1512      */
1513     'generateID'
1516 Y.NodeList.importMethod(Y.Node.prototype, [
1518  * Allows getting attributes on DOM nodes, normalizing in some cases.
1519  * This passes through to the DOM node, allowing for custom attributes.
1520  * @method getAttribute
1521  * @see Node
1522  * @for NodeList
1523  * @param {string} name The attribute name
1524  * @return {string} The attribute value
1525  */
1527     'getAttribute',
1529  * Allows setting attributes on DOM nodes, normalizing in some cases.
1530  * This passes through to the DOM node, allowing for custom attributes.
1531  * @method setAttribute
1532  * @see Node
1533  * @for NodeList
1534  * @chainable
1535  * @param {string} name The attribute name
1536  * @param {string} value The value to set
1537  */
1538     'setAttribute',
1541  * Allows for removing attributes on DOM nodes.
1542  * This passes through to the DOM node, allowing for custom attributes.
1543  * @method removeAttribute
1544  * @see Node
1545  * @for NodeList
1546  * @param {string} name The attribute to remove
1547  */
1548     'removeAttribute',
1550  * Removes the parent node from node in the list.
1551  * @method unwrap
1552  * @chainable
1553  */
1554     'unwrap',
1556  * Wraps the given HTML around each node.
1557  * @method wrap
1558  * @param {String} html The markup to wrap around the node.
1559  * @chainable
1560  */
1561     'wrap',
1564  * Applies a unique ID to each node if none exists
1565  * @method generateID
1566  * @return {String} The existing or generated ID
1567  */
1568     'generateID'
1572 }, '3.5.0' ,{requires:['dom-core', 'selector']});