2 Copyright (c) 2010, Yahoo! Inc. All rights reserved.
3 Code licensed under the BSD License:
4 http://developer.yahoo.com/yui/license.html
8 YUI.add('node-base', function(Y) {
11 * The Node Utility provides a DOM-like interface for interacting with DOM nodes.
13 * @submodule node-base
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.get() to retrieve Node instances.
21 * <strong>NOTE:</strong> Node properties are accessed using
22 * the <code>set</code> and <code>get</code> methods.
26 * @param {DOMNode} node the DOM node to be mapped to the Node instance.
32 NODE_NAME = 'nodeName',
33 NODE_TYPE = 'nodeType',
34 OWNER_DOCUMENT = 'ownerDocument',
38 _slice = Array.prototype.slice,
42 Y_Node = function(node) {
43 var uid = (node.nodeType !== 9) ? node.uniqueID : node[UID];
45 if (uid && Y_Node._instances[uid] && Y_Node._instances[uid]._node !== node) {
46 node[UID] = null; // unset existing uid to prevent collision (via clone or hack)
49 uid = uid || Y.stamp(node);
50 if (!uid) { // stamp failed; likely IE non-HTMLElement
57 * The underlying DOM node bound to the Y.Node instance
62 Y_Node._instances[uid] = this;
64 this._stateProxy = node; // when augmented with Attribute
66 Y.EventTarget.call(this, {emitFacade:true});
68 if (this._initPlugins) { // when augmented with Plugin.Host
72 this.SHOW_TRANSITION = Y_Node.SHOW_TRANSITION;
73 this.HIDE_TRANSITION = Y_Node.HIDE_TRANSITION;
76 // used with previous/next/ancestor tests
77 _wrapFn = function(fn) {
80 ret = (typeof fn == 'string') ?
82 return Y.Selector.test(n, fn);
94 * The name of the component
101 * The pattern used to identify ARIA attributes
103 Y_Node.re_aria = /^(?:role$|aria-)/;
105 Y_Node.SHOW_TRANSITION = 'fadeIn';
106 Y_Node.HIDE_TRANSITION = 'fadeOut';
109 * List of events that route to DOM events
111 * @property DOM_EVENTS
114 Y_Node.DOM_EVENTS = {
149 orientationchange: 1,
160 // Add custom event adaptors to this list. This will make it so
161 // that delegate, key, available, contentready, etc all will
162 // be available through Node.on
163 Y.mix(Y_Node.DOM_EVENTS, Y.Env.evt.plugins);
166 * A list of Node instances that have been created
168 * @property _instances
172 Y_Node._instances = {};
175 * Retrieves the DOM node bound to a Node instance
179 * @param {Y.Node || HTMLNode} node The Node instance or an HTMLNode
180 * @return {HTMLNode} The DOM node bound to the Node instance. If a DOM node is passed
181 * as the node argument, it is simply returned.
183 Y_Node.getDOMNode = function(node) {
185 return (node.nodeType) ? node : node._node || null;
191 * Checks Node return values and wraps DOM Nodes as Y.Node instances
192 * and DOM Collections / Arrays as Y.NodeList instances.
193 * Other return values just pass thru. If undefined is returned (e.g. no return)
194 * then the Node instance is returned for chainability.
198 * @param {any} node The Node instance or an HTMLNode
199 * @return {Y.Node | Y.NodeList | any} Depends on what is returned from the DOM node.
201 Y_Node.scrubVal = function(val, node) {
202 if (val) { // only truthy values are risky
203 if (typeof val == 'object' || typeof val == 'function') { // safari nodeList === function
204 if (NODE_TYPE in val || Y_DOM.isWindow(val)) {// node || window
206 } else if ((val.item && !val._nodes) || // dom collection or Node instance
207 (val[0] && val[0][NODE_TYPE])) { // array of DOM Nodes
211 } else if (typeof val === 'undefined') {
212 val = node; // for chaining
213 } else if (val === null) {
214 val = null; // IE: DOM null not the same as null
221 * Adds methods to the Y.Node prototype, routing through scrubVal.
225 * @param {String} name The name of the method to add
226 * @param {Function} fn The function that becomes the method
227 * @param {Object} context An optional context to call the method with
228 * (defaults to the Node instance)
229 * @return {any} Depends on what is returned from the DOM node.
231 Y_Node.addMethod = function(name, fn, context) {
232 if (name && fn && typeof fn == 'function') {
233 Y_Node.prototype[name] = function() {
234 var args = _slice.call(arguments),
238 if (args[0] && Y.instanceOf(args[0], Y_Node)) {
239 args[0] = args[0]._node;
242 if (args[1] && Y.instanceOf(args[1], Y_Node)) {
243 args[1] = args[1]._node;
245 args.unshift(node._node);
247 ret = fn.apply(node, args);
249 if (ret) { // scrub truthy
250 ret = Y_Node.scrubVal(ret, node);
253 (typeof ret != 'undefined') || (ret = node);
261 * Imports utility methods to be added as Y.Node methods.
262 * @method importMethod
265 * @param {Object} host The object that contains the method to import.
266 * @param {String} name The name of the method to import
267 * @param {String} altName An optional name to use in place of the host name
268 * @param {Object} context An optional context to call the method with
270 Y_Node.importMethod = function(host, name, altName) {
271 if (typeof name == 'string') {
272 altName = altName || name;
273 Y_Node.addMethod(altName, host[name], host);
275 Y.Array.each(name, function(n) {
276 Y_Node.importMethod(host, n);
282 * Returns a single Node instance bound to the node or the
283 * first element matching the given selector. Returns null if no match found.
284 * <strong>Note:</strong> For chaining purposes you may want to
285 * use <code>Y.all</code>, which returns a NodeList when no match is found.
288 * @param {String | HTMLElement} node a node or Selector
289 * @return {Y.Node | null} a Node instance or null if no match found.
291 Y_Node.one = function(node) {
297 if (typeof node == 'string') {
298 if (node.indexOf('doc') === 0) { // doc OR document
300 } else if (node.indexOf('win') === 0) { // win OR window
303 node = Y.Selector.query(node, null, true);
308 } else if (Y.instanceOf(node, Y_Node)) {
309 return node; // NOTE: return
312 if (node.nodeType || Y.DOM.isWindow(node)) { // avoid bad input (numbers, boolean, etc)
313 uid = (node.uniqueID && node.nodeType !== 9) ? node.uniqueID : node._yuid;
314 instance = Y_Node._instances[uid]; // reuse exising instances
315 cachedNode = instance ? instance._node : null;
316 if (!instance || (cachedNode && node !== cachedNode)) { // new Node when nodes don't match
317 instance = new Y_Node(node);
325 * Returns a new dom node using the provided markup string.
328 * @param {String} html The markup used to create the element
329 * @param {HTMLDocument} doc An optional document context
330 * @return {Node} A Node instance bound to a DOM node or fragment
332 Y_Node.create = function(html, doc) {
333 if (doc && doc._node) {
336 return Y.one(Y_DOM.create(html, doc));
340 * Static collection of configuration attributes for special handling
347 * Allows for getting and setting the text of an element.
348 * Formatting is preserved and special characters are treated literally.
354 return Y_DOM.getText(this._node);
357 setter: function(content) {
358 Y_DOM.setText(this._node, content);
365 return this._node.getElementsByTagName('option');
370 * Returns a NodeList instance of all HTMLElement children.
377 var node = this._node,
378 children = node.children,
382 childNodes = node.childNodes;
385 for (i = 0, len = childNodes.length; i < len; ++i) {
386 if (childNodes[i][TAG_NAME]) {
387 children[children.length] = childNodes[i];
391 return Y.all(children);
397 return Y_DOM.getValue(this._node);
400 setter: function(val) {
401 Y_DOM.setValue(this._node, val);
408 * The default setter for DOM properties
409 * Called with instance context (this === the Node instance)
410 * @method DEFAULT_SETTER
412 * @param {String} name The attribute/property being set
413 * @param {any} val The value to be set
414 * @return {any} The value
416 Y_Node.DEFAULT_SETTER = function(name, val) {
417 var node = this._stateProxy,
420 if (name.indexOf(DOT) > -1) {
422 name = name.split(DOT);
423 // only allow when defined on node
424 Y.Object.setValue(node, name, val);
425 } else if (typeof node[name] != 'undefined') { // pass thru DOM properties
433 * The default getter for DOM properties
434 * Called with instance context (this === the Node instance)
435 * @method DEFAULT_GETTER
437 * @param {String} name The attribute/property to look up
438 * @return {any} The current value
440 Y_Node.DEFAULT_GETTER = function(name) {
441 var node = this._stateProxy,
444 if (name.indexOf && name.indexOf(DOT) > -1) {
445 val = Y.Object.getValue(node, name.split(DOT));
446 } else if (typeof node[name] != 'undefined') { // pass thru from DOM
453 // Basic prototype augment - no lazy constructor invocation.
454 Y.mix(Y_Node, Y.EventTarget, false, null, 1);
456 Y.mix(Y_Node.prototype, {
458 * The method called when outputting Node instances as strings
460 * @return {String} A string representation of the Node instance
462 toString: function() {
463 var str = this[UID] + ': not bound to a node',
465 attrs, id, className;
468 attrs = node.attributes;
469 id = (attrs && attrs.id) ? node.getAttribute('id') : null;
470 className = (attrs && attrs.className) ? node.getAttribute('className') : null;
471 str = node[NODE_NAME];
478 str += '.' + className.replace(' ', '.');
482 str += ' ' + this[UID];
488 * Returns an attribute value on the Node instance.
489 * Unless pre-configured (via Node.ATTRS), get hands
490 * off to the underlying DOM node. Only valid
491 * attributes/properties for the node will be set.
493 * @param {String} attr The attribute
494 * @return {any} The current value of the attribute
496 get: function(attr) {
499 if (this._getAttr) { // use Attribute imple
500 val = this._getAttr(attr);
502 val = this._get(attr);
506 val = Y_Node.scrubVal(val, this);
507 } else if (val === null) {
508 val = null; // IE: DOM null is not true null (even though they ===)
514 * Helper method for get.
517 * @param {String} attr The attribute
518 * @return {any} The current value of the attribute
520 _get: function(attr) {
521 var attrConfig = Y_Node.ATTRS[attr],
524 if (attrConfig && attrConfig.getter) {
525 val = attrConfig.getter.call(this);
526 } else if (Y_Node.re_aria.test(attr)) {
527 val = this._node.getAttribute(attr, 2);
529 val = Y_Node.DEFAULT_GETTER.apply(this, arguments);
536 * Sets an attribute on the Node instance.
537 * Unless pre-configured (via Node.ATTRS), set hands
538 * off to the underlying DOM node. Only valid
539 * attributes/properties for the node will be set.
540 * To set custom attributes use setAttribute.
542 * @param {String} attr The attribute to be set.
543 * @param {any} val The value to set the attribute to.
546 set: function(attr, val) {
547 var attrConfig = Y_Node.ATTRS[attr];
549 if (this._setAttr) { // use Attribute imple
550 this._setAttr.apply(this, arguments);
551 } else { // use setters inline
552 if (attrConfig && attrConfig.setter) {
553 attrConfig.setter.call(this, val, attr);
554 } else if (Y_Node.re_aria.test(attr)) { // special case Aria
555 this._node.setAttribute(attr, val);
557 Y_Node.DEFAULT_SETTER.apply(this, arguments);
565 * Sets multiple attributes.
567 * @param {Object} attrMap an object of name/value pairs to set
570 setAttrs: function(attrMap) {
571 if (this._setAttrs) { // use Attribute imple
572 this._setAttrs(attrMap);
573 } else { // use setters inline
574 Y.Object.each(attrMap, function(v, n) {
583 * Returns an object containing the values for the requested attributes.
585 * @param {Array} attrs an array of attributes to get values
586 * @return {Object} An object with attribute name/value pairs.
588 getAttrs: function(attrs) {
590 if (this._getAttrs) { // use Attribute imple
591 this._getAttrs(attrs);
592 } else { // use setters inline
593 Y.Array.each(attrs, function(v, n) {
594 ret[v] = this.get(v);
602 * Creates a new Node using the provided markup string.
604 * @param {String} html The markup used to create the element
605 * @param {HTMLDocument} doc An optional document context
606 * @return {Node} A Node instance bound to a DOM node or fragment
608 create: Y_Node.create,
611 * Compares nodes to determine if they match.
612 * Node instances can be compared to each other and/or HTMLElements.
614 * @param {HTMLElement | Node} refNode The reference node to compare to the node.
615 * @return {Boolean} True if the nodes match, false if they do not.
617 compareTo: function(refNode) {
618 var node = this._node;
620 if (Y.instanceOf(refNode, Y_Node)) {
621 refNode = refNode._node;
623 return node === refNode;
627 * Determines whether the node is appended to the document.
629 * @param {Node|HTMLElement} doc optional An optional document to check against.
630 * Defaults to current document.
631 * @return {Boolean} Whether or not this node is appended to the document.
633 inDoc: function(doc) {
634 var node = this._node;
635 doc = (doc) ? doc._node || doc : node[OWNER_DOCUMENT];
636 if (doc.documentElement) {
637 return Y_DOM.contains(doc.documentElement, node);
641 getById: function(id) {
642 var node = this._node,
643 ret = Y_DOM.byId(id, node[OWNER_DOCUMENT]);
644 if (ret && Y_DOM.contains(node, ret)) {
653 * Returns the nearest ancestor that passes the test applied by supplied boolean method.
655 * @param {String | Function} fn A selector string or boolean method for testing elements.
656 * @param {Boolean} testSelf optional Whether or not to include the element in the scan
657 * If a function is used, it receives the current node being tested as the only argument.
658 * @return {Node} The matching Node instance or null if not found
660 ancestor: function(fn, testSelf) {
661 return Y.one(Y_DOM.ancestor(this._node, _wrapFn(fn), testSelf));
665 * Returns the ancestors that pass the test applied by supplied boolean method.
667 * @param {String | Function} fn A selector string or boolean method for testing elements.
668 * @param {Boolean} testSelf optional Whether or not to include the element in the scan
669 * If a function is used, it receives the current node being tested as the only argument.
670 * @return {NodeList} A NodeList instance containing the matching elements
672 ancestors: function(fn, testSelf) {
673 return Y.all(Y_DOM.ancestors(this._node, _wrapFn(fn), testSelf));
677 * Returns the previous matching sibling.
678 * Returns the nearest element node sibling if no method provided.
680 * @param {String | Function} fn A selector or boolean method for testing elements.
681 * If a function is used, it receives the current node being tested as the only argument.
682 * @return {Node} Node instance or null if not found
684 previous: function(fn, all) {
685 return Y.one(Y_DOM.elementByAxis(this._node, 'previousSibling', _wrapFn(fn), all));
689 * Returns the next matching sibling.
690 * Returns the nearest element node sibling if no method provided.
692 * @param {String | Function} fn A selector or boolean method for testing elements.
693 * If a function is used, it receives the current node being tested as the only argument.
694 * @return {Node} Node instance or null if not found
696 next: function(fn, all) {
697 return Y.one(Y_DOM.elementByAxis(this._node, 'nextSibling', _wrapFn(fn), all));
701 * Returns all matching siblings.
702 * Returns all siblings if no method provided.
704 * @param {String | Function} fn A selector or boolean method for testing elements.
705 * If a function is used, it receives the current node being tested as the only argument.
706 * @return {NodeList} NodeList instance bound to found siblings
708 siblings: function(fn) {
709 return Y.all(Y_DOM.siblings(this._node, _wrapFn(fn)));
713 * Retrieves a Node instance of nodes based on the given CSS selector.
716 * @param {string} selector The CSS selector to test against.
717 * @return {Node} A Node instance for the matching HTMLElement.
719 one: function(selector) {
720 return Y.one(Y.Selector.query(selector, this._node, true));
724 * Retrieves a nodeList based on the given CSS selector.
727 * @param {string} selector The CSS selector to test against.
728 * @return {NodeList} A NodeList instance for the matching HTMLCollection/Array.
730 all: function(selector) {
731 var nodelist = Y.all(Y.Selector.query(selector, this._node));
732 nodelist._query = selector;
733 nodelist._queryRoot = this._node;
737 // TODO: allow fn test
739 * Test if the supplied node matches the supplied selector.
742 * @param {string} selector The CSS selector to test against.
743 * @return {boolean} Whether or not the node matches the selector.
745 test: function(selector) {
746 return Y.Selector.test(this._node, selector);
750 * Removes the node from its parent.
751 * Shortcut for myNode.get('parentNode').removeChild(myNode);
753 * @param {Boolean} destroy whether or not to call destroy() on the node
758 remove: function(destroy) {
759 var node = this._node,
760 parentNode = node.parentNode;
763 parentNode.removeChild(node);
774 * Replace the node with the other node. This is a DOM update only
775 * and does not change the node bound to the Node instance.
776 * Shortcut for myNode.get('parentNode').replaceChild(newNode, myNode);
778 * @param {Y.Node || HTMLNode} newNode Node to be inserted
782 replace: function(newNode) {
783 var node = this._node;
784 if (typeof newNode == 'string') {
785 newNode = Y_Node.create(newNode);
787 node.parentNode.replaceChild(Y_Node.getDOMNode(newNode), node);
792 * @method replaceChild
794 * @param {String | HTMLElement | Node} node Node to be inserted
795 * @param {HTMLElement | Node} refNode Node to be replaced
796 * @return {Node} The replaced node
798 replaceChild: function(node, refNode) {
799 if (typeof node == 'string') {
800 node = Y_DOM.create(node);
803 return Y.one(this._node.replaceChild(Y_Node.getDOMNode(node), Y_Node.getDOMNode(refNode)));
807 * @method appendChild
808 * @param {String | HTMLElement | Node} node Node to be appended
809 * @return {Node} The appended node
811 appendChild: function(node) {
812 return Y_Node.scrubVal(this._insert(node));
816 * @method insertBefore
817 * @param {String | HTMLElement | Node} newNode Node to be appended
818 * @param {HTMLElement | Node} refNode Node to be inserted before
819 * @return {Node} The inserted node
821 insertBefore: function(newNode, refNode) {
822 return Y.Node.scrubVal(this._insert(newNode, refNode));
826 * Removes event listeners from the node and (optionally) its subtree
828 * @param {Boolean} recurse (optional) Whether or not to remove listeners from the
830 * @param {String} type (optional) Only remove listeners of the specified type
834 purge: function(recurse, type) {
835 Y.Event.purgeElement(this._node, recurse, type);
840 * Nulls internal node references, removes any plugins and event listeners
842 * @param {Boolean} recursivePurge (optional) Whether or not to remove listeners from the
843 * node's subtree (default is false)
846 destroy: function(recursive) {
847 this.purge(); // TODO: only remove events add via this Node
849 if (this.unplug) { // may not be a PluginHost
856 this.all('*').destroy();
860 this._stateProxy = null;
862 delete Y_Node._instances[this[UID]];
866 * Invokes a method on the Node instance
868 * @param {String} method The name of the method to invoke
869 * @param {Any} a, b, c, etc. Arguments to invoke the method with.
870 * @return Whatever the underly method returns.
871 * DOM Nodes and Collections return values
872 * are converted to Node/NodeList instances.
875 invoke: function(method, a, b, c, d, e) {
876 var node = this._node,
879 if (a && Y.instanceOf(a, Y_Node)) {
883 if (b && Y.instanceOf(b, Y_Node)) {
887 ret = node[method](a, b, c, d, e);
888 return Y_Node.scrubVal(ret, this);
892 * Inserts the content before the reference node.
894 * @param {String | Y.Node | HTMLElement | Y.NodeList | HTMLCollection} content The content to insert
895 * @param {Int | Y.Node | HTMLElement | String} where The position to insert at.
896 * Possible "where" arguments
899 * <dd>The Node to insert before</dd>
900 * <dt>HTMLElement</dt>
901 * <dd>The element to insert before</dd>
903 * <dd>The index of the child element to insert before</dd>
905 * <dd>Replaces the existing HTML</dd>
907 * <dd>Inserts before the existing HTML</dd>
909 * <dd>Inserts content before the node</dd>
911 * <dd>Inserts content after the node</dd>
915 insert: function(content, where) {
916 this._insert(content, where);
920 _insert: function(content, where) {
921 var node = this._node,
924 if (typeof where == 'number') { // allow index
925 where = this._node.childNodes[where];
926 } else if (where && where._node) { // Node
930 if (content && typeof content != 'string') { // allow Node or NodeList/Array instances
931 content = content._node || content._nodes || content;
933 ret = Y_DOM.addHTML(node, content, where);
939 * Inserts the content as the firstChild of the node.
941 * @param {String | Y.Node | HTMLElement} content The content to insert
944 prepend: function(content) {
945 return this.insert(content, 0);
949 * Inserts the content as the lastChild of the node.
951 * @param {String | Y.Node | HTMLElement} content The content to insert
954 append: function(content) {
955 return this.insert(content, null);
959 * Appends the node to the given node.
961 * @param {Y.Node | HTMLElement} node The node to append to
964 appendTo: function(node) {
965 Y.one(node).append(this);
970 * Replaces the node's current content with the content.
972 * @param {String | Y.Node | HTMLElement | Y.NodeList | HTMLCollection} content The content to insert
975 setContent: function(content) {
976 this._insert(content, 'replace');
981 * Returns the node's current content (e.g. innerHTML)
983 * @return {String} The current content
985 getContent: function(content) {
986 return this.get('innerHTML');
991 * @description Swap DOM locations with the given node.
992 * This does not change which DOM node each Node instance refers to.
993 * @param {Node} otherNode The node to swap with
996 swap: Y.config.doc.documentElement.swapNode ?
997 function(otherNode) {
998 this._node.swapNode(Y_Node.getDOMNode(otherNode));
1000 function(otherNode) {
1001 otherNode = Y_Node.getDOMNode(otherNode);
1002 var node = this._node,
1003 parent = otherNode.parentNode,
1004 nextSibling = otherNode.nextSibling;
1006 if (nextSibling === node) {
1007 parent.insertBefore(node, otherNode);
1008 } else if (otherNode === node.nextSibling) {
1009 parent.insertBefore(otherNode, node);
1011 node.parentNode.replaceChild(otherNode, node);
1012 Y_DOM.addHTML(parent, node, nextSibling);
1020 * @description Retrieves arbitrary data stored on a Node instance.
1021 * This is not stored with the DOM node.
1022 * @param {string} name Optional name of the data field to retrieve.
1023 * If no name is given, all data is returned.
1024 * @return {any | Object} Whatever is stored at the given field,
1025 * or an object hash of all fields.
1027 getData: function(name) {
1029 this._data = this._data || {};
1030 if (arguments.length) {
1031 ret = this._data[name];
1042 * @description Stores arbitrary data on a Node instance.
1043 * This is not stored with the DOM node.
1044 * @param {string} name The name of the field to set. If no name
1045 * is given, name is treated as the data and overrides any existing data.
1046 * @param {any} val The value to be assigned to the field.
1049 setData: function(name, val) {
1050 this._data = this._data || {};
1051 if (arguments.length > 1) {
1052 this._data[name] = val;
1062 * @description Clears stored data.
1063 * @param {string} name The name of the field to clear. If no name
1064 * is given, all data is cleared.
1067 clearData: function(name) {
1068 if ('_data' in this) {
1070 delete this._data[name];
1079 hasMethod: function(method) {
1080 var node = this._node;
1081 return !!(node && method in node &&
1082 typeof node[method] != 'unknown' &&
1083 (typeof node[method] == 'function' ||
1084 String(node[method]).indexOf('function') === 1)); // IE reports as object, prepends space
1087 SHOW_TRANSITION: null,
1088 HIDE_TRANSITION: null,
1091 * Makes the node visible.
1092 * If the "transition" module is loaded, show optionally
1093 * animates the showing of the node using either the default
1094 * transition effect ('fadeIn'), or the given named effect.
1096 * @param {String} name A named Transition effect to use as the show effect.
1097 * @param {Object} config Options to use with the transition.
1098 * @param {Function} callback An optional function to run after the transition completes.
1101 show: function(callback) {
1102 callback = arguments[arguments.length - 1];
1103 this.toggleView(true, callback);
1108 * The implementation for showing nodes.
1109 * Default is to toggle the style.display property.
1114 this.setStyle('display', '');
1118 _isHidden: function() {
1119 return Y.DOM.getStyle(this._node, 'display') === 'none';
1122 toggleView: function(on, callback) {
1123 this._toggleView.apply(this, arguments);
1126 _toggleView: function(on, callback) {
1127 callback = arguments[arguments.length - 1];
1129 // base on current state if not forcing
1130 if (typeof on != 'boolean') {
1131 on = (this._isHidden()) ? 1 : 0;
1140 if (typeof callback == 'function') {
1141 callback.call(this);
1149 * If the "transition" module is loaded, hide optionally
1150 * animates the hiding of the node using either the default
1151 * transition effect ('fadeOut'), or the given named effect.
1153 * @param {String} name A named Transition effect to use as the show effect.
1154 * @param {Object} config Options to use with the transition.
1155 * @param {Function} callback An optional function to run after the transition completes.
1158 hide: function(callback) {
1159 callback = arguments[arguments.length - 1];
1160 this.toggleView(false, callback);
1165 * The implementation for hiding nodes.
1166 * Default is to toggle the style.display property.
1171 this.setStyle('display', 'none');
1174 isFragment: function() {
1175 return (this.get('nodeType') === 11);
1179 * Removes all of the child nodes from the node.
1180 * @param {Boolean} destroy Whether the nodes should also be destroyed.
1183 empty: function(destroy) {
1184 this.get('childNodes').remove(destroy);
1193 * The NodeList module provides support for managing collections of Nodes.
1195 * @submodule nodelist
1199 * The NodeList class provides a wrapper for manipulating DOM NodeLists.
1200 * NodeList properties can be accessed via the set/get methods.
1201 * Use Y.all() to retrieve NodeList instances.
1207 var NodeList = function(nodes) {
1209 if (typeof nodes === 'string') { // selector query
1210 this._query = nodes;
1211 nodes = Y.Selector.query(nodes);
1212 } else if (nodes.nodeType || Y_DOM.isWindow(nodes)) { // domNode || window
1214 } else if (Y.instanceOf(nodes, Y.Node)) {
1215 nodes = [nodes._node];
1216 } else if (Y.instanceOf(nodes[0], Y.Node)) { // allow array of Y.Nodes
1217 Y.Array.each(nodes, function(node) {
1219 tmp.push(node._node);
1223 } else { // array of domNodes or domNodeList (no mixed array of Y.Node/domNodes)
1224 nodes = Y.Array(nodes, 0, true);
1228 * The underlying array of DOM nodes bound to the Y.NodeList instance
1232 this._nodes = nodes;
1235 NodeList.NAME = 'NodeList';
1238 * Retrieves the DOM nodes bound to a NodeList instance
1239 * @method NodeList.getDOMNodes
1242 * @param {Y.NodeList} nodelist The NodeList instance
1243 * @return {Array} The array of DOM nodes bound to the NodeList
1245 NodeList.getDOMNodes = function(nodelist) {
1246 return (nodelist && nodelist._nodes) ? nodelist._nodes : nodelist;
1249 NodeList.each = function(instance, fn, context) {
1250 var nodes = instance._nodes;
1251 if (nodes && nodes.length) {
1252 Y.Array.each(nodes, fn, context || instance);
1257 NodeList.addMethod = function(name, fn, context) {
1259 NodeList.prototype[name] = function() {
1263 Y.Array.each(this._nodes, function(node) {
1264 var UID = (node.uniqueID && node.nodeType !== 9 ) ? 'uniqueID' : '_yuid',
1265 instance = Y.Node._instances[node[UID]],
1270 instance = NodeList._getTempNode(node);
1272 ctx = context || instance;
1273 result = fn.apply(ctx, args);
1274 if (result !== undefined && result !== instance) {
1275 ret[ret.length] = result;
1279 // TODO: remove tmp pointer
1280 return ret.length ? ret : this;
1286 NodeList.importMethod = function(host, name, altName) {
1287 if (typeof name === 'string') {
1288 altName = altName || name;
1289 NodeList.addMethod(name, host[name]);
1291 Y.Array.each(name, function(n) {
1292 NodeList.importMethod(host, n);
1297 NodeList._getTempNode = function(node) {
1298 var tmp = NodeList._tempNode;
1300 tmp = Y.Node.create('<div></div>');
1301 NodeList._tempNode = tmp;
1305 tmp._stateProxy = node;
1309 Y.mix(NodeList.prototype, {
1311 * Retrieves the Node instance at the given index.
1314 * @param {Number} index The index of the target Node.
1315 * @return {Node} The Node instance at the given index.
1317 item: function(index) {
1318 return Y.one((this._nodes || [])[index]);
1322 * Applies the given function to each Node in the NodeList.
1324 * @param {Function} fn The function to apply. It receives 3 arguments:
1325 * the current node instance, the node's index, and the NodeList instance
1326 * @param {Object} context optional An optional context to apply the function with
1327 * Default context is the current Node instance
1330 each: function(fn, context) {
1331 var instance = this;
1332 Y.Array.each(this._nodes, function(node, index) {
1334 return fn.call(context || node, node, index, instance);
1339 batch: function(fn, context) {
1340 var nodelist = this;
1342 Y.Array.each(this._nodes, function(node, index) {
1343 var instance = Y.Node._instances[node[UID]];
1345 instance = NodeList._getTempNode(node);
1348 return fn.call(context || instance, instance, index, nodelist);
1354 * Executes the function once for each node until a true value is returned.
1356 * @param {Function} fn The function to apply. It receives 3 arguments:
1357 * the current node instance, the node's index, and the NodeList instance
1358 * @param {Object} context optional An optional context to execute the function from.
1359 * Default context is the current Node instance
1360 * @return {Boolean} Whether or not the function returned true for any node.
1362 some: function(fn, context) {
1363 var instance = this;
1364 return Y.Array.some(this._nodes, function(node, index) {
1366 context = context || node;
1367 return fn.call(context, node, index, instance);
1372 * Creates a documenFragment from the nodes bound to the NodeList instance
1374 * @return Node a Node instance bound to the documentFragment
1376 toFrag: function() {
1377 return Y.one(Y.DOM._nl2frag(this._nodes));
1381 * Returns the index of the node in the NodeList instance
1382 * or -1 if the node isn't found.
1384 * @param {Y.Node || DOMNode} node the node to search for
1385 * @return {Int} the index of the node value or -1 if not found
1387 indexOf: function(node) {
1388 return Y.Array.indexOf(this._nodes, Y.Node.getDOMNode(node));
1392 * Filters the NodeList instance down to only nodes matching the given selector.
1394 * @param {String} selector The selector to filter against
1395 * @return {NodeList} NodeList containing the updated collection
1398 filter: function(selector) {
1399 return Y.all(Y.Selector.filter(this._nodes, selector));
1404 * Creates a new NodeList containing all nodes at every n indices, where
1405 * remainder n % index equals r.
1406 * (zero-based index).
1408 * @param {Int} n The offset to use (return every nth node)
1409 * @param {Int} r An optional remainder to use with the modulus operation (defaults to zero)
1410 * @return {NodeList} NodeList containing the updated collection
1412 modulus: function(n, r) {
1415 NodeList.each(this, function(node, i) {
1421 return Y.all(nodes);
1425 * Creates a new NodeList containing all nodes at odd indices
1426 * (zero-based index).
1428 * @return {NodeList} NodeList containing the updated collection
1431 return this.modulus(2, 1);
1435 * Creates a new NodeList containing all nodes at even indices
1436 * (zero-based index), including zero.
1438 * @return {NodeList} NodeList containing the updated collection
1441 return this.modulus(2);
1444 destructor: function() {
1448 * Reruns the initial query, when created using a selector query
1452 refresh: function() {
1454 nodes = this._nodes,
1455 query = this._query,
1456 root = this._queryRoot;
1460 if (nodes && nodes[0] && nodes[0].ownerDocument) {
1461 root = nodes[0].ownerDocument;
1465 this._nodes = Y.Selector.query(query, root);
1471 _prepEvtArgs: function(type, fn, context) {
1472 // map to Y.on/after signature (type, fn, nodes, context, arg1, arg2, etc)
1473 var args = Y.Array(arguments, 0, true);
1475 if (args.length < 2) { // type only (event hash) just add nodes
1476 args[2] = this._nodes;
1478 args.splice(2, 0, this._nodes);
1481 args[3] = context || this; // default to NodeList instance as context
1487 * Applies an event listener to each Node bound to the NodeList.
1489 * @param {String} type The event being listened for
1490 * @param {Function} fn The handler to call when the event fires
1491 * @param {Object} context The context to call the handler with.
1492 * Default is the NodeList instance.
1493 * @param {Object} context The context to call the handler with.
1494 * param {mixed} arg* 0..n additional arguments to supply to the subscriber
1495 * when the event fires.
1496 * @return {Object} Returns an event handle that can later be use to detach().
1499 on: function(type, fn, context) {
1500 return Y.on.apply(Y, this._prepEvtArgs.apply(this, arguments));
1504 * Applies an one-time event listener to each Node bound to the NodeList.
1506 * @param {String} type The event being listened for
1507 * @param {Function} fn The handler to call when the event fires
1508 * @param {Object} context The context to call the handler with.
1509 * Default is the NodeList instance.
1510 * @return {Object} Returns an event handle that can later be use to detach().
1513 once: function(type, fn, context) {
1514 return Y.once.apply(Y, this._prepEvtArgs.apply(this, arguments));
1518 * Applies an event listener to each Node bound to the NodeList.
1519 * The handler is called only after all on() handlers are called
1520 * and the event is not prevented.
1522 * @param {String} type The event being listened for
1523 * @param {Function} fn The handler to call when the event fires
1524 * @param {Object} context The context to call the handler with.
1525 * Default is the NodeList instance.
1526 * @return {Object} Returns an event handle that can later be use to detach().
1529 after: function(type, fn, context) {
1530 return Y.after.apply(Y, this._prepEvtArgs.apply(this, arguments));
1534 * Returns the current number of items in the NodeList.
1536 * @return {Int} The number of items in the NodeList.
1539 return this._nodes.length;
1543 * Determines if the instance is bound to any nodes
1545 * @return {Boolean} Whether or not the NodeList is bound to any nodes
1547 isEmpty: function() {
1548 return this._nodes.length < 1;
1551 toString: function() {
1553 errorMsg = this[UID] + ': not bound to any nodes',
1554 nodes = this._nodes,
1557 if (nodes && nodes[0]) {
1559 str += node[NODE_NAME];
1561 str += '#' + node.id;
1564 if (node.className) {
1565 str += '.' + node.className.replace(' ', '.');
1568 if (nodes.length > 1) {
1569 str += '...[' + nodes.length + ' items]';
1572 return str || errorMsg;
1577 NodeList.importMethod(Y.Node.prototype, [
1579 * Called on each Node instance
1586 /** Called on each Node instance
1593 * Called on each Node instance
1599 /** Called on each Node instance
1601 * @see Node.detachAll
1605 /** Called on each Node instance
1611 /** Called on each Node instance
1617 /** Called on each Node instance
1623 /** Called on each Node instance
1629 /** Called on each Node instance
1635 /** Called on each Node instance
1636 * @method setContent
1637 * @see Node.setContent
1642 * Makes each node visible.
1643 * If the "transition" module is loaded, show optionally
1644 * animates the showing of the node using either the default
1645 * transition effect ('fadeIn'), or the given named effect.
1647 * @param {String} name A named Transition effect to use as the show effect.
1648 * @param {Object} config Options to use with the transition.
1649 * @param {Function} callback An optional function to run after the transition completes.
1656 * If the "transition" module is loaded, hide optionally
1657 * animates the hiding of the node using either the default
1658 * transition effect ('fadeOut'), or the given named effect.
1660 * @param {String} name A named Transition effect to use as the show effect.
1661 * @param {Object} config Options to use with the transition.
1662 * @param {Function} callback An optional function to run after the transition completes.
1670 // one-off implementation to convert array of Nodes to NodeList
1671 // e.g. Y.all('input').get('parentNode');
1673 /** Called on each Node instance
1677 NodeList.prototype.get = function(attr) {
1679 nodes = this._nodes,
1681 getTemp = NodeList._getTempNode,
1686 instance = Y.Node._instances[nodes[0]._yuid] || getTemp(nodes[0]);
1687 val = instance._get(attr);
1688 if (val && val.nodeType) {
1693 Y.Array.each(nodes, function(node) {
1694 instance = Y.Node._instances[node._yuid];
1697 instance = getTemp(node);
1700 val = instance._get(attr);
1701 if (!isNodeList) { // convert array of Nodes to NodeList
1702 val = Y.Node.scrubVal(val, instance);
1708 return (isNodeList) ? Y.all(ret) : ret;
1711 Y.NodeList = NodeList;
1713 Y.all = function(nodes) {
1714 return new NodeList(nodes);
1720 * Passes through to DOM method.
1722 * @method removeChild
1723 * @param {HTMLElement | Node} node Node to be removed
1724 * @return {Node} The removed node
1729 * Passes through to DOM method.
1730 * @method hasChildNodes
1731 * @return {Boolean} Whether or not the node has any childNodes
1736 * Passes through to DOM method.
1738 * @param {Boolean} deep Whether or not to perform a deep clone, which includes
1739 * subtree and attributes
1740 * @return {Node} The clone
1745 * Passes through to DOM method.
1746 * @method hasAttribute
1747 * @param {String} attribute The attribute to test for
1748 * @return {Boolean} Whether or not the attribute is present
1753 * Passes through to DOM method.
1754 * @method removeAttribute
1755 * @param {String} attribute The attribute to be removed
1761 * Passes through to DOM method.
1762 * @method scrollIntoView
1768 * Passes through to DOM method.
1769 * @method getElementsByTagName
1770 * @param {String} tagName The tagName to collect
1771 * @return {NodeList} A NodeList representing the HTMLCollection
1773 'getElementsByTagName',
1776 * Passes through to DOM method.
1783 * Passes through to DOM method.
1790 * Passes through to DOM method.
1791 * Only valid on FORM elements
1798 * Passes through to DOM method.
1799 * Only valid on FORM elements
1806 * Passes through to DOM method.
1813 * Passes through to DOM method.
1814 * Only valid on TABLE elements
1815 * @method createCaption
1820 ], function(method) {
1821 Y.Node.prototype[method] = function(arg1, arg2, arg3) {
1822 var ret = this.invoke(method, arg1, arg2, arg3);
1827 Y.Node.importMethod(Y.DOM, [
1829 * Determines whether the node is an ancestor of another HTML element in the DOM hierarchy.
1831 * @param {Node | HTMLElement} needle The possible node or descendent
1832 * @return {Boolean} Whether or not this node is the needle its ancestor
1836 * Allows setting attributes on DOM nodes, normalizing in some cases.
1837 * This passes through to the DOM node, allowing for custom attributes.
1838 * @method setAttribute
1842 * @param {string} name The attribute name
1843 * @param {string} value The value to set
1847 * Allows getting attributes on DOM nodes, normalizing in some cases.
1848 * This passes through to the DOM node, allowing for custom attributes.
1849 * @method getAttribute
1852 * @param {string} name The attribute name
1853 * @return {string} The attribute value
1858 * Wraps the given HTML around the node.
1860 * @param {String} html The markup to wrap around the node.
1866 * Removes the node's parent node.
1873 * Applies a unique ID to the node if none exists
1874 * @method generateID
1875 * @return {String} The existing or generated ID
1880 Y.NodeList.importMethod(Y.Node.prototype, [
1882 * Allows getting attributes on DOM nodes, normalizing in some cases.
1883 * This passes through to the DOM node, allowing for custom attributes.
1884 * @method getAttribute
1887 * @param {string} name The attribute name
1888 * @return {string} The attribute value
1893 * Allows setting attributes on DOM nodes, normalizing in some cases.
1894 * This passes through to the DOM node, allowing for custom attributes.
1895 * @method setAttribute
1899 * @param {string} name The attribute name
1900 * @param {string} value The value to set
1905 * Allows for removing attributes on DOM nodes.
1906 * This passes through to the DOM node, allowing for custom attributes.
1907 * @method removeAttribute
1910 * @param {string} name The attribute to remove
1914 * Removes the parent node from node in the list.
1920 * Wraps the given HTML around each node.
1922 * @param {String} html The markup to wrap around the node.
1928 * Applies a unique ID to each node if none exists
1929 * @method generateID
1930 * @return {String} The existing or generated ID
1937 * Determines whether each node has the given className.
1940 * @param {String} className the class name to search for
1941 * @return {Boolean} Whether or not the element has the specified class
1946 * Adds a class name to each node.
1948 * @param {String} className the class name to add to the node's class attribute
1954 * Removes a class name from each node.
1955 * @method removeClass
1956 * @param {String} className the class name to remove from the node's class attribute
1962 * Replace a class with another class for each node.
1963 * If no oldClassName is present, the newClassName is simply added.
1964 * @method replaceClass
1965 * @param {String} oldClassName the class name to be replaced
1966 * @param {String} newClassName the class name that will be replacing the old class name
1972 * If the className exists on the node it is removed, if it doesn't exist it is added.
1973 * @method toggleClass
1974 * @param {String} className the class name to be toggled
1975 * @param {Boolean} force Option to force adding or removing the class.
1981 Y.Node.importMethod(Y.DOM, methods);
1983 * Determines whether each node has the given className.
1985 * @see Node.hasClass
1987 * @param {String} className the class name to search for
1988 * @return {Array} An array of booleans for each node bound to the NodeList.
1992 * Adds a class name to each node.
1994 * @see Node.addClass
1995 * @param {String} className the class name to add to the node's class attribute
2000 * Removes a class name from each node.
2001 * @method removeClass
2002 * @see Node.removeClass
2003 * @param {String} className the class name to remove from the node's class attribute
2008 * Replace a class with another class for each node.
2009 * If no oldClassName is present, the newClassName is simply added.
2010 * @method replaceClass
2011 * @see Node.replaceClass
2012 * @param {String} oldClassName the class name to be replaced
2013 * @param {String} newClassName the class name that will be replacing the old class name
2018 * If the className exists on the node it is removed, if it doesn't exist it is added.
2019 * @method toggleClass
2020 * @see Node.toggleClass
2021 * @param {String} className the class name to be toggled
2024 Y.NodeList.importMethod(Y.Node.prototype, methods);
2027 if (!Y.config.doc.documentElement.hasAttribute) { // IE < 8
2028 Y.Node.prototype.hasAttribute = function(attr) {
2029 if (attr === 'value') {
2030 if (this.get('value') !== "") { // IE < 8 fails to populate specified when set in HTML
2034 return !!(this._node.attributes[attr] &&
2035 this._node.attributes[attr].specified);
2039 // IE throws an error when calling focus() on an element that's invisible, not
2040 // displayed, or disabled.
2041 Y.Node.prototype.focus = function () {
2048 // IE throws error when setting input.type = 'hidden',
2049 // input.setAttribute('type', 'hidden') and input.attributes.type.value = 'hidden'
2050 Y.Node.ATTRS.type = {
2051 setter: function(val) {
2052 if (val === 'hidden') {
2054 this._node.type = 'hidden';
2056 this.setStyle('display', 'none');
2057 this._inputType = 'hidden';
2060 try { // IE errors when changing the type from "hidden'
2061 this._node.type = val;
2068 getter: function() {
2069 return this._inputType || this._node.type;
2072 _bypassProxy: true // don't update DOM when using with Attribute
2075 if (Y.config.doc.createElement('form').elements.nodeType) {
2076 // IE: elements collection is also FORM node which trips up scrubVal.
2077 Y.Node.ATTRS.elements = {
2078 getter: function() {
2079 return this.all('input, textarea, button, select');
2084 Y.mix(Y.Node.ATTRS, {
2086 setter: function(h) {
2087 Y.DOM.setHeight(this._node, h);
2091 getter: function() {
2092 return this._node.offsetHeight;
2097 setter: function(w) {
2098 Y.DOM.setWidth(this._node, w);
2102 getter: function() {
2103 return this._node.offsetWidth;
2108 Y.mix(Y.Node.prototype, {
2109 sizeTo: function(w, h) {
2111 if (arguments.length < 2) {
2113 w = node.get('offsetWidth');
2114 h = node.get('offsetHeight');
2123 var Y_NodeList = Y.NodeList,
2124 ArrayProto = Array.prototype,
2126 /** Returns a new NodeList combining the given NodeList(s)
2129 * @param {NodeList | Array} valueN Arrays/NodeLists and/or values to
2130 * concatenate to the resulting NodeList
2131 * @return {NodeList} A new NodeList comprised of this NodeList joined with the input.
2134 /** Removes the first last from the NodeList and returns it.
2137 * @return {Node} The last item in the NodeList.
2140 /** Adds the given Node(s) to the end of the NodeList.
2143 * @param {Node | DOMNode} nodeN One or more nodes to add to the end of the NodeList.
2146 /** Removes the first item from the NodeList and returns it.
2149 * @return {Node} The first item in the NodeList.
2152 /** Returns a new NodeList comprising the Nodes in the given range.
2155 * @param {Number} begin Zero-based index at which to begin extraction.
2156 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.
2157 * @param {Number} end Zero-based index at which to end extraction. slice extracts up to but not including end.
2158 slice(1,4) extracts the second element through the fourth element (elements indexed 1, 2, and 3).
2159 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.
2160 If end is omitted, slice extracts to the end of the sequence.
2161 * @return {NodeList} A new NodeList comprised of this NodeList joined with the input.
2164 /** Changes the content of the NodeList, adding new elements while removing old elements.
2167 * @param {Number} index Index at which to start changing the array. If negative, will begin that many elements from the end.
2168 * @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.
2169 * {Node | DOMNode| element1, ..., elementN
2170 The elements to add to the array. If you don't specify any elements, splice simply removes elements from the array.
2171 * @return {NodeList} The element(s) removed.
2174 /** Adds the given Node(s) to the beginning of the NodeList.
2177 * @param {Node | DOMNode} nodeN One or more nodes to add to the NodeList.
2183 Y.Array.each(ArrayMethods, function(name) {
2184 Y_NodeList.prototype[name] = function() {
2189 while ((arg = arguments[i++])) { // use DOM nodes/nodeLists
2190 args.push(arg._node || arg._nodes || arg);
2192 return Y.Node.scrubVal(ArrayProto[name].apply(this._nodes, args));
2197 }, '3.3.0' ,{requires:['dom-base', 'selector-css2', 'event-base']});