- /**
- * The Node Utility provides a DOM-like interface for interacting with DOM nodes.
- * @module node
- * @main node
- * @submodule node-core
- */
-
- /**
- * The Node class provides a wrapper for manipulating DOM Nodes.
- * Node properties can be accessed via the set/get methods.
- * Use `Y.one()` to retrieve Node instances.
- *
- * <strong>NOTE:</strong> Node properties are accessed using
- * the <code>set</code> and <code>get</code> methods.
- *
- * @class Node
- * @constructor
- * @param {HTMLElement} node the DOM node to be mapped to the Node instance.
- * @uses EventTarget
- */
-
- // "globals"
- var DOT = '.',
- NODE_NAME = 'nodeName',
- NODE_TYPE = 'nodeType',
- OWNER_DOCUMENT = 'ownerDocument',
- TAG_NAME = 'tagName',
- UID = '_yuid',
- EMPTY_OBJ = {},
-
- _slice = Array.prototype.slice,
-
- Y_DOM = Y.DOM,
-
- Y_Node = function(node) {
- if (!this.getDOMNode) { // support optional "new"
- return new Y_Node(node);
- }
-
- if (typeof node == 'string') {
- node = Y_Node._fromString(node);
- if (!node) {
- return null; // NOTE: return
- }
- }
-
- var uid = (node.nodeType !== 9) ? node.uniqueID : node[UID];
-
- if (uid && Y_Node._instances[uid] && Y_Node._instances[uid]._node !== node) {
- node[UID] = null; // unset existing uid to prevent collision (via clone or hack)
- }
-
- uid = uid || Y.stamp(node);
- if (!uid) { // stamp failed; likely IE non-HTMLElement
- uid = Y.guid();
- }
-
- this[UID] = uid;
-
- /**
- * The underlying DOM node bound to the Y.Node instance
- * @property _node
- * @type HTMLElement
- * @private
- */
- this._node = node;
-
- this._stateProxy = node; // when augmented with Attribute
-
- if (this._initPlugins) { // when augmented with Plugin.Host
- this._initPlugins();
- }
- },
-
- // used with previous/next/ancestor tests
- _wrapFn = function(fn) {
- var ret = null;
- if (fn) {
- ret = (typeof fn == 'string') ?
- function(n) {
- return Y.Selector.test(n, fn);
- } :
- function(n) {
- return fn(Y.one(n));
- };
- }
-
- return ret;
- };
- // end "globals"
-
- Y_Node.ATTRS = {};
- Y_Node.DOM_EVENTS = {};
-
- Y_Node._fromString = function(node) {
- if (node) {
- if (node.indexOf('doc') === 0) { // doc OR document
- node = Y.config.doc;
- } else if (node.indexOf('win') === 0) { // win OR window
- node = Y.config.win;
- } else {
- node = Y.Selector.query(node, null, true);
- }
- }
-
- return node || null;
- };
-
- /**
- * The name of the component
- * @static
- * @type String
- * @property NAME
- */
- Y_Node.NAME = 'node';
-
- /*
- * The pattern used to identify ARIA attributes
- */
- Y_Node.re_aria = /^(?:role$|aria-)/;
-
- Y_Node.SHOW_TRANSITION = 'fadeIn';
- Y_Node.HIDE_TRANSITION = 'fadeOut';
-
- /**
- * A list of Node instances that have been created
- * @private
- * @type Object
- * @property _instances
- * @static
- *
- */
- Y_Node._instances = {};
-
- /**
- * Retrieves the DOM node bound to a Node instance
- * @method getDOMNode
- * @static
- *
- * @param {Node|HTMLElement} node The Node instance or an HTMLElement
- * @return {HTMLElement} The DOM node bound to the Node instance. If a DOM node is passed
- * as the node argument, it is simply returned.
- */
- Y_Node.getDOMNode = function(node) {
- if (node) {
- return (node.nodeType) ? node : node._node || null;
- }
- return null;
- };
-
- /**
- * Checks Node return values and wraps DOM Nodes as Y.Node instances
- * and DOM Collections / Arrays as Y.NodeList instances.
- * Other return values just pass thru. If undefined is returned (e.g. no return)
- * then the Node instance is returned for chainability.
- * @method scrubVal
- * @static
- *
- * @param {HTMLElement|HTMLElement[]|Node} node The Node instance or an HTMLElement
- * @return {Node | NodeList | Any} Depends on what is returned from the DOM node.
- */
- Y_Node.scrubVal = function(val, node) {
- if (val) { // only truthy values are risky
- if (typeof val == 'object' || typeof val == 'function') { // safari nodeList === function
- if (NODE_TYPE in val || Y_DOM.isWindow(val)) {// node || window
- val = Y.one(val);
- } else if ((val.item && !val._nodes) || // dom collection or Node instance
- (val[0] && val[0][NODE_TYPE])) { // array of DOM Nodes
- val = Y.all(val);
- }
- }
- } else if (typeof val === 'undefined') {
- val = node; // for chaining
- } else if (val === null) {
- val = null; // IE: DOM null not the same as null
- }
-
- return val;
- };
-
- /**
- * Adds methods to the Y.Node prototype, routing through scrubVal.
- * @method addMethod
- * @static
- *
- * @param {String} name The name of the method to add
- * @param {Function} fn The function that becomes the method
- * @param {Object} context An optional context to call the method with
- * (defaults to the Node instance)
- * @return {any} Depends on what is returned from the DOM node.
- */
- Y_Node.addMethod = function(name, fn, context) {
- if (name && fn && typeof fn == 'function') {
- Y_Node.prototype[name] = function() {
- var args = _slice.call(arguments),
- node = this,
- ret;
-
- if (args[0] && args[0]._node) {
- args[0] = args[0]._node;
- }
-
- if (args[1] && args[1]._node) {
- args[1] = args[1]._node;
- }
- args.unshift(node._node);
-
- ret = fn.apply(context || node, args);
-
- if (ret) { // scrub truthy
- ret = Y_Node.scrubVal(ret, node);
- }
-
- (typeof ret != 'undefined') || (ret = node);
- return ret;
- };
- } else {
- Y.log('unable to add method: ' + name, 'warn', 'Node');
- }
- };
-
- /**
- * Imports utility methods to be added as Y.Node methods.
- * @method importMethod
- * @static
- *
- * @param {Object} host The object that contains the method to import.
- * @param {String} name The name of the method to import
- * @param {String} altName An optional name to use in place of the host name
- * @param {Object} context An optional context to call the method with
- */
- Y_Node.importMethod = function(host, name, altName) {
- if (typeof name == 'string') {
- altName = altName || name;
- Y_Node.addMethod(altName, host[name], host);
- } else {
- Y.Array.each(name, function(n) {
- Y_Node.importMethod(host, n);
- });
- }
- };
-
- /**
- * Retrieves a NodeList based on the given CSS selector.
- * @method all
- *
- * @param {string} selector The CSS selector to test against.
- * @return {NodeList} A NodeList instance for the matching HTMLCollection/Array.
- * @for YUI
- */
-
- /**
- * Returns a single Node instance bound to the node or the
- * first element matching the given selector. Returns null if no match found.
- * <strong>Note:</strong> For chaining purposes you may want to
- * use <code>Y.all</code>, which returns a NodeList when no match is found.
- * @method one
- * @param {String | HTMLElement} node a node or Selector
- * @return {Node | null} a Node instance or null if no match found.
- * @for YUI
- */
-
- /**
- * Returns a single Node instance bound to the node or the
- * first element matching the given selector. Returns null if no match found.
- * <strong>Note:</strong> For chaining purposes you may want to
- * use <code>Y.all</code>, which returns a NodeList when no match is found.
- * @method one
- * @static
- * @param {String | HTMLElement} node a node or Selector
- * @return {Node | null} a Node instance or null if no match found.
- * @for Node
- */
- Y_Node.one = function(node) {
- var instance = null,
- cachedNode,
- uid;
-
- if (node) {
- if (typeof node == 'string') {
- node = Y_Node._fromString(node);
- if (!node) {
- return null; // NOTE: return
- }
- } else if (node.getDOMNode) {
- return node; // NOTE: return
- }
-
- if (node.nodeType || Y.DOM.isWindow(node)) { // avoid bad input (numbers, boolean, etc)
- uid = (node.uniqueID && node.nodeType !== 9) ? node.uniqueID : node._yuid;
- instance = Y_Node._instances[uid]; // reuse exising instances
- cachedNode = instance ? instance._node : null;
- if (!instance || (cachedNode && node !== cachedNode)) { // new Node when nodes don't match
- instance = new Y_Node(node);
- if (node.nodeType != 11) { // dont cache document fragment
- Y_Node._instances[instance[UID]] = instance; // cache node
- }
- }
- }
- }
-
- return instance;
- };
-
- /**
- * The default setter for DOM properties
- * Called with instance context (this === the Node instance)
- * @method DEFAULT_SETTER
- * @static
- * @param {String} name The attribute/property being set
- * @param {any} val The value to be set
- * @return {any} The value
- */
- Y_Node.DEFAULT_SETTER = function(name, val) {
- var node = this._stateProxy,
- strPath;
-
- if (name.indexOf(DOT) > -1) {
- strPath = name;
- name = name.split(DOT);
- // only allow when defined on node
- Y.Object.setValue(node, name, val);
- } else if (typeof node[name] != 'undefined') { // pass thru DOM properties
- node[name] = val;
- }
-
- return val;
- };
-
- /**
- * The default getter for DOM properties
- * Called with instance context (this === the Node instance)
- * @method DEFAULT_GETTER
- * @static
- * @param {String} name The attribute/property to look up
- * @return {any} The current value
- */
- Y_Node.DEFAULT_GETTER = function(name) {
- var node = this._stateProxy,
- val;
-
- if (name.indexOf && name.indexOf(DOT) > -1) {
- val = Y.Object.getValue(node, name.split(DOT));
- } else if (typeof node[name] != 'undefined') { // pass thru from DOM
- val = node[name];
- }
-
- return val;
- };
-
- Y.mix(Y_Node.prototype, {
- DATA_PREFIX: 'data-',
-
- /**
- * The method called when outputting Node instances as strings
- * @method toString
- * @return {String} A string representation of the Node instance
- */
- toString: function() {
- var str = this[UID] + ': not bound to a node',
- node = this._node,
- attrs, id, className;
-
- if (node) {
- attrs = node.attributes;
- id = (attrs && attrs.id) ? node.getAttribute('id') : null;
- className = (attrs && attrs.className) ? node.getAttribute('className') : null;
- str = node[NODE_NAME];
-
- if (id) {
- str += '#' + id;
- }
-
- if (className) {
- str += '.' + className.replace(' ', '.');
- }
-
- // TODO: add yuid?
- str += ' ' + this[UID];
- }
- return str;
- },
-
- /**
- * Returns an attribute value on the Node instance.
- * Unless pre-configured (via `Node.ATTRS`), get hands
- * off to the underlying DOM node. Only valid
- * attributes/properties for the node will be queried.
- * @method get
- * @param {String} attr The attribute
- * @return {any} The current value of the attribute
- */
- get: function(attr) {
- var val;
-
- if (this._getAttr) { // use Attribute imple
- val = this._getAttr(attr);
- } else {
- val = this._get(attr);
- }
-
- if (val) {
- val = Y_Node.scrubVal(val, this);
- } else if (val === null) {
- val = null; // IE: DOM null is not true null (even though they ===)
- }
- return val;
- },
-
- /**
- * Helper method for get.
- * @method _get
- * @private
- * @param {String} attr The attribute
- * @return {any} The current value of the attribute
- */
- _get: function(attr) {
- var attrConfig = Y_Node.ATTRS[attr],
- val;
-
- if (attrConfig && attrConfig.getter) {
- val = attrConfig.getter.call(this);
- } else if (Y_Node.re_aria.test(attr)) {
- val = this._node.getAttribute(attr, 2);
- } else {
- val = Y_Node.DEFAULT_GETTER.apply(this, arguments);
- }
-
- return val;
- },
-
- /**
- * Sets an attribute on the Node instance.
- * Unless pre-configured (via Node.ATTRS), set hands
- * off to the underlying DOM node. Only valid
- * attributes/properties for the node will be set.
- * To set custom attributes use setAttribute.
- * @method set
- * @param {String} attr The attribute to be set.
- * @param {any} val The value to set the attribute to.
- * @chainable
- */
- set: function(attr, val) {
- var attrConfig = Y_Node.ATTRS[attr];
-
- if (this._setAttr) { // use Attribute imple
- this._setAttr.apply(this, arguments);
- } else { // use setters inline
- if (attrConfig && attrConfig.setter) {
- attrConfig.setter.call(this, val, attr);
- } else if (Y_Node.re_aria.test(attr)) { // special case Aria
- this._node.setAttribute(attr, val);
- } else {
- Y_Node.DEFAULT_SETTER.apply(this, arguments);
- }
- }
-
- return this;
- },
-
- /**
- * Sets multiple attributes.
- * @method setAttrs
- * @param {Object} attrMap an object of name/value pairs to set
- * @chainable
- */
- setAttrs: function(attrMap) {
- if (this._setAttrs) { // use Attribute imple
- this._setAttrs(attrMap);
- } else { // use setters inline
- Y.Object.each(attrMap, function(v, n) {
- this.set(n, v);
- }, this);
- }
-
- return this;
- },
-
- /**
- * Returns an object containing the values for the requested attributes.
- * @method getAttrs
- * @param {Array} attrs an array of attributes to get values
- * @return {Object} An object with attribute name/value pairs.
- */
- getAttrs: function(attrs) {
- var ret = {};
- if (this._getAttrs) { // use Attribute imple
- this._getAttrs(attrs);
- } else { // use setters inline
- Y.Array.each(attrs, function(v, n) {
- ret[v] = this.get(v);
- }, this);
- }
-
- return ret;
- },
-
- /**
- * Compares nodes to determine if they match.
- * Node instances can be compared to each other and/or HTMLElements.
- * @method compareTo
- * @param {HTMLElement | Node} refNode The reference node to compare to the node.
- * @return {Boolean} True if the nodes match, false if they do not.
- */
- compareTo: function(refNode) {
- var node = this._node;
-
- if (refNode && refNode._node) {
- refNode = refNode._node;
- }
- return node === refNode;
- },
-
- /**
- * Determines whether the node is appended to the document.
- * @method inDoc
- * @param {Node|HTMLElement} doc optional An optional document to check against.
- * Defaults to current document.
- * @return {Boolean} Whether or not this node is appended to the document.
- */
- inDoc: function(doc) {
- var node = this._node;
-
- if (node) {
- doc = (doc) ? doc._node || doc : node[OWNER_DOCUMENT];
- if (doc.documentElement) {
- return Y_DOM.contains(doc.documentElement, node);
- }
- }
-
- return false;
- },
-
- getById: function(id) {
- var node = this._node,
- ret = Y_DOM.byId(id, node[OWNER_DOCUMENT]);
- if (ret && Y_DOM.contains(node, ret)) {
- ret = Y.one(ret);
- } else {
- ret = null;
- }
- return ret;
- },
-
- /**
- * Returns the nearest ancestor that passes the test applied by supplied boolean method.
- * @method ancestor
- * @param {String | Function} fn A selector string or boolean method for testing elements.
- * If a function is used, it receives the current node being tested as the only argument.
- * If fn is not passed as an argument, the parent node will be returned.
- * @param {Boolean} testSelf optional Whether or not to include the element in the scan
- * @param {String | Function} stopFn optional A selector string or boolean
- * method to indicate when the search should stop. The search bails when the function
- * returns true or the selector matches.
- * If a function is used, it receives the current node being tested as the only argument.
- * @return {Node} The matching Node instance or null if not found
- */
- ancestor: function(fn, testSelf, stopFn) {
- // testSelf is optional, check for stopFn as 2nd arg
- if (arguments.length === 2 &&
- (typeof testSelf == 'string' || typeof testSelf == 'function')) {
- stopFn = testSelf;
- }
-
- return Y.one(Y_DOM.ancestor(this._node, _wrapFn(fn), testSelf, _wrapFn(stopFn)));
- },
-
- /**
- * Returns the ancestors that pass the test applied by supplied boolean method.
- * @method ancestors
- * @param {String | Function} fn A selector string or boolean method for testing elements.
- * @param {Boolean} testSelf optional Whether or not to include the element in the scan
- * If a function is used, it receives the current node being tested as the only argument.
- * @return {NodeList} A NodeList instance containing the matching elements
- */
- ancestors: function(fn, testSelf, stopFn) {
- if (arguments.length === 2 &&
- (typeof testSelf == 'string' || typeof testSelf == 'function')) {
- stopFn = testSelf;
- }
- return Y.all(Y_DOM.ancestors(this._node, _wrapFn(fn), testSelf, _wrapFn(stopFn)));
- },
-
- /**
- * Returns the previous matching sibling.
- * Returns the nearest element node sibling if no method provided.
- * @method previous
- * @param {String | Function} fn A selector or boolean method for testing elements.
- * If a function is used, it receives the current node being tested as the only argument.
- * @param {Boolean} [all] Whether text nodes as well as element nodes should be returned, or
- * just element nodes will be returned(default)
- * @return {Node} Node instance or null if not found
- */
- previous: function(fn, all) {
- return Y.one(Y_DOM.elementByAxis(this._node, 'previousSibling', _wrapFn(fn), all));
- },
-
- /**
- * Returns the next matching sibling.
- * Returns the nearest element node sibling if no method provided.
- * @method next
- * @param {String | Function} fn A selector or boolean method for testing elements.
- * If a function is used, it receives the current node being tested as the only argument.
- * @param {Boolean} [all] Whether text nodes as well as element nodes should be returned, or
- * just element nodes will be returned(default)
- * @return {Node} Node instance or null if not found
- */
- next: function(fn, all) {
- return Y.one(Y_DOM.elementByAxis(this._node, 'nextSibling', _wrapFn(fn), all));
- },
-
- /**
- * Returns all matching siblings.
- * Returns all siblings if no method provided.
- * @method siblings
- * @param {String | Function} fn A selector or boolean method for testing elements.
- * If a function is used, it receives the current node being tested as the only argument.
- * @return {NodeList} NodeList instance bound to found siblings
- */
- siblings: function(fn) {
- return Y.all(Y_DOM.siblings(this._node, _wrapFn(fn)));
- },
-
- /**
- * Retrieves a single Node instance, the first element matching the given
- * CSS selector.
- * Returns null if no match found.
- * @method one
- *
- * @param {string} selector The CSS selector to test against.
- * @return {Node | null} A Node instance for the matching HTMLElement or null
- * if no match found.
- */
- one: function(selector) {
- return Y.one(Y.Selector.query(selector, this._node, true));
- },
-
- /**
- * Retrieves a NodeList based on the given CSS selector.
- * @method all
- *
- * @param {string} selector The CSS selector to test against.
- * @return {NodeList} A NodeList instance for the matching HTMLCollection/Array.
- */
- all: function(selector) {
- var nodelist;
-
- if (this._node) {
- nodelist = Y.all(Y.Selector.query(selector, this._node));
- nodelist._query = selector;
- nodelist._queryRoot = this._node;
- }
-
- return nodelist || Y.all([]);
- },
-
- // TODO: allow fn test
- /**
- * Test if the supplied node matches the supplied selector.
- * @method test
- *
- * @param {string} selector The CSS selector to test against.
- * @return {boolean} Whether or not the node matches the selector.
- */
- test: function(selector) {
- return Y.Selector.test(this._node, selector);
- },
-
- /**
- * Removes the node from its parent.
- * Shortcut for myNode.get('parentNode').removeChild(myNode);
- * @method remove
- * @param {Boolean} destroy whether or not to call destroy() on the node
- * after removal.
- * @chainable
- *
- */
- remove: function(destroy) {
- var node = this._node;
-
- if (node && node.parentNode) {
- node.parentNode.removeChild(node);
- }
-
- if (destroy) {
- this.destroy();
- }
-
- return this;
- },
-
- /**
- * Replace the node with the other node. This is a DOM update only
- * and does not change the node bound to the Node instance.
- * Shortcut for myNode.get('parentNode').replaceChild(newNode, myNode);
- * @method replace
- * @param {Node | HTMLElement} newNode Node to be inserted
- * @chainable
- *
- */
- replace: function(newNode) {
- var node = this._node;
- if (typeof newNode == 'string') {
- newNode = Y_Node.create(newNode);
- }
- node.parentNode.replaceChild(Y_Node.getDOMNode(newNode), node);
- return this;
- },
-
- /**
- * @method replaceChild
- * @for Node
- * @param {String | HTMLElement | Node} node Node to be inserted
- * @param {HTMLElement | Node} refNode Node to be replaced
- * @return {Node} The replaced node
- */
- replaceChild: function(node, refNode) {
- if (typeof node == 'string') {
- node = Y_DOM.create(node);
- }
-
- return Y.one(this._node.replaceChild(Y_Node.getDOMNode(node), Y_Node.getDOMNode(refNode)));
- },
-
- /**
- * Nulls internal node references, removes any plugins and event listeners.
- * Note that destroy() will not remove the node from its parent or from the DOM. For that
- * functionality, call remove(true).
- * @method destroy
- * @param {Boolean} recursivePurge (optional) Whether or not to remove listeners from the
- * node's subtree (default is false)
- *
- */
- destroy: function(recursive) {
- var UID = Y.config.doc.uniqueID ? 'uniqueID' : '_yuid',
- instance;
-
- this.purge(); // TODO: only remove events add via this Node
-
- if (this.unplug) { // may not be a PluginHost
- this.unplug();
- }
-
- this.clearData();
-
- if (recursive) {
- Y.NodeList.each(this.all('*'), function(node) {
- instance = Y_Node._instances[node[UID]];
- if (instance) {
- instance.destroy();
- } else { // purge in case added by other means
- Y.Event.purgeElement(node);
- }
- });
- }
-
- this._node = null;
- this._stateProxy = null;
-
- delete Y_Node._instances[this._yuid];
- },
-
- /**
- * Invokes a method on the Node instance
- * @method invoke
- * @param {String} method The name of the method to invoke
- * @param {any} [args*] Arguments to invoke the method with.
- * @return {any} Whatever the underly method returns.
- * DOM Nodes and Collections return values
- * are converted to Node/NodeList instances.
- *
- */
- invoke: function(method, a, b, c, d, e) {
- var node = this._node,
- ret;
-
- if (a && a._node) {
- a = a._node;
- }
-
- if (b && b._node) {
- b = b._node;
- }
-
- ret = node[method](a, b, c, d, e);
- return Y_Node.scrubVal(ret, this);
- },
-
- /**
- * @method swap
- * @description Swap DOM locations with the given node.
- * This does not change which DOM node each Node instance refers to.
- * @param {Node} otherNode The node to swap with
- * @chainable
- */
- swap: Y.config.doc.documentElement.swapNode ?
- function(otherNode) {
- this._node.swapNode(Y_Node.getDOMNode(otherNode));
- } :
- function(otherNode) {
- otherNode = Y_Node.getDOMNode(otherNode);
- var node = this._node,
- parent = otherNode.parentNode,
- nextSibling = otherNode.nextSibling;
-
- if (nextSibling === node) {
- parent.insertBefore(node, otherNode);
- } else if (otherNode === node.nextSibling) {
- parent.insertBefore(otherNode, node);
- } else {
- node.parentNode.replaceChild(otherNode, node);
- Y_DOM.addHTML(parent, node, nextSibling);
- }
- return this;
- },
-
-
- hasMethod: function(method) {
- var node = this._node;
- return !!(node && method in node &&
- typeof node[method] != 'unknown' &&
- (typeof node[method] == 'function' ||
- String(node[method]).indexOf('function') === 1)); // IE reports as object, prepends space
- },
-
- isFragment: function() {
- return (this.get('nodeType') === 11);
- },
-
- /**
- * Removes and destroys all of the nodes within the node.
- * @method empty
- * @chainable
- */
- empty: function() {
- this.get('childNodes').remove().destroy(true);
- return this;
- },
-
- /**
- * Returns the DOM node bound to the Node instance
- * @method getDOMNode
- * @return {HTMLElement}
- */
- getDOMNode: function() {
- return this._node;
- }
- }, true);
-
- Y.Node = Y_Node;
- Y.one = Y_Node.one;
-
-