/** * The NodeList module provides support for managing collections of Nodes. * @module node * @submodule node-core */ /** * The NodeList class provides a wrapper for manipulating DOM NodeLists. * NodeList properties can be accessed via the set/get methods. * Use Y.all() to retrieve NodeList instances. * * @class NodeList * @constructor * @param nodes {String|element|Node|Array} A selector, DOM element, Node, list of DOM elements, or list of Nodes with which to populate this NodeList. */ var NodeList = function(nodes) { var tmp = []; if (nodes) { if (typeof nodes === 'string') { // selector query this._query = nodes; nodes = Y.Selector.query(nodes); } else if (nodes.nodeType || Y_DOM.isWindow(nodes)) { // domNode || window nodes = [nodes]; } else if (nodes._node) { // Y.Node nodes = [nodes._node]; } else if (nodes[0] && nodes[0]._node) { // allow array of Y.Nodes Y.Array.each(nodes, function(node) { if (node._node) { tmp.push(node._node); } }); nodes = tmp; } else { // array of domNodes or domNodeList (no mixed array of Y.Node/domNodes) nodes = Y.Array(nodes, 0, true); } } /** * The underlying array of DOM nodes bound to the Y.NodeList instance * @property _nodes * @private */ this._nodes = nodes || []; }; NodeList.NAME = 'NodeList'; /** * Retrieves the DOM nodes bound to a NodeList instance * @method getDOMNodes * @static * * @param {NodeList} nodelist The NodeList instance * @return {Array} The array of DOM nodes bound to the NodeList */ NodeList.getDOMNodes = function(nodelist) { return (nodelist && nodelist._nodes) ? nodelist._nodes : nodelist; }; NodeList.each = function(instance, fn, context) { var nodes = instance._nodes; if (nodes && nodes.length) { Y.Array.each(nodes, fn, context || instance); } else { Y.log('no nodes bound to ' + this, 'warn', 'NodeList'); } }; NodeList.addMethod = function(name, fn, context) { if (name && fn) { NodeList.prototype[name] = function() { var ret = [], args = arguments; Y.Array.each(this._nodes, function(node) { var UID = (node.uniqueID && node.nodeType !== 9 ) ? 'uniqueID' : '_yuid', instance = Y.Node._instances[node[UID]], ctx, result; if (!instance) { instance = NodeList._getTempNode(node); } ctx = context || instance; result = fn.apply(ctx, args); if (result !== undefined && result !== instance) { ret[ret.length] = result; } }); // TODO: remove tmp pointer return ret.length ? ret : this; }; } else { Y.log('unable to add method: ' + name + ' to NodeList', 'warn', 'node'); } }; NodeList.importMethod = function(host, name, altName) { if (typeof name === 'string') { altName = altName || name; NodeList.addMethod(name, host[name]); } else { Y.Array.each(name, function(n) { NodeList.importMethod(host, n); }); } }; NodeList._getTempNode = function(node) { var tmp = NodeList._tempNode; if (!tmp) { tmp = Y.Node.create('<div></div>'); NodeList._tempNode = tmp; } tmp._node = node; tmp._stateProxy = node; return tmp; }; Y.mix(NodeList.prototype, { _invoke: function(method, args, getter) { var ret = (getter) ? [] : this; this.each(function(node) { var val = node[method].apply(node, args); if (getter) { ret.push(val); } }); return ret; }, /** * Retrieves the Node instance at the given index. * @method item * * @param {Number} index The index of the target Node. * @return {Node} The Node instance at the given index. */ item: function(index) { return Y.one((this._nodes || [])[index]); }, /** * Applies the given function to each Node in the NodeList. * @method each * @param {Function} fn The function to apply. It receives 3 arguments: * the current node instance, the node's index, and the NodeList instance * @param {Object} context optional An optional context to apply the function with * Default context is the current Node instance * @chainable */ each: function(fn, context) { var instance = this; Y.Array.each(this._nodes, function(node, index) { node = Y.one(node); return fn.call(context || node, node, index, instance); }); return instance; }, batch: function(fn, context) { var nodelist = this; Y.Array.each(this._nodes, function(node, index) { var instance = Y.Node._instances[node[UID]]; if (!instance) { instance = NodeList._getTempNode(node); } return fn.call(context || instance, instance, index, nodelist); }); return nodelist; }, /** * Executes the function once for each node until a true value is returned. * @method some * @param {Function} fn The function to apply. It receives 3 arguments: * the current node instance, the node's index, and the NodeList instance * @param {Object} context optional An optional context to execute the function from. * Default context is the current Node instance * @return {Boolean} Whether or not the function returned true for any node. */ some: function(fn, context) { var instance = this; return Y.Array.some(this._nodes, function(node, index) { node = Y.one(node); context = context || node; return fn.call(context, node, index, instance); }); }, /** * Creates a documenFragment from the nodes bound to the NodeList instance * @method toFrag * @return {Node} a Node instance bound to the documentFragment */ toFrag: function() { return Y.one(Y.DOM._nl2frag(this._nodes)); }, /** * Returns the index of the node in the NodeList instance * or -1 if the node isn't found. * @method indexOf * @param {Node | HTMLElement} node the node to search for * @return {Number} the index of the node value or -1 if not found */ indexOf: function(node) { return Y.Array.indexOf(this._nodes, Y.Node.getDOMNode(node)); }, /** * Filters the NodeList instance down to only nodes matching the given selector. * @method filter * @param {String} selector The selector to filter against * @return {NodeList} NodeList containing the updated collection * @see Selector */ filter: function(selector) { return Y.all(Y.Selector.filter(this._nodes, selector)); }, /** * Creates a new NodeList containing all nodes at every n indices, where * remainder n % index equals r. * (zero-based index). * @method modulus * @param {Number} n The offset to use (return every nth node) * @param {Number} r An optional remainder to use with the modulus operation (defaults to zero) * @return {NodeList} NodeList containing the updated collection */ modulus: function(n, r) { r = r || 0; var nodes = []; NodeList.each(this, function(node, i) { if (i % n === r) { nodes.push(node); } }); return Y.all(nodes); }, /** * Creates a new NodeList containing all nodes at odd indices * (zero-based index). * @method odd * @return {NodeList} NodeList containing the updated collection */ odd: function() { return this.modulus(2, 1); }, /** * Creates a new NodeList containing all nodes at even indices * (zero-based index), including zero. * @method even * @return {NodeList} NodeList containing the updated collection */ even: function() { return this.modulus(2); }, destructor: function() { }, /** * Reruns the initial query, when created using a selector query * @method refresh * @chainable */ refresh: function() { var doc, nodes = this._nodes, query = this._query, root = this._queryRoot; if (query) { if (!root) { if (nodes && nodes[0] && nodes[0].ownerDocument) { root = nodes[0].ownerDocument; } } this._nodes = Y.Selector.query(query, root); } return this; }, /** * Returns the current number of items in the NodeList. * @method size * @return {Number} The number of items in the NodeList. */ size: function() { return this._nodes.length; }, /** * Determines if the instance is bound to any nodes * @method isEmpty * @return {Boolean} Whether or not the NodeList is bound to any nodes */ isEmpty: function() { return this._nodes.length < 1; }, toString: function() { var str = '', errorMsg = this[UID] + ': not bound to any nodes', nodes = this._nodes, node; if (nodes && nodes[0]) { node = nodes[0]; str += node[NODE_NAME]; if (node.id) { str += '#' + node.id; } if (node.className) { str += '.' + node.className.replace(' ', '.'); } if (nodes.length > 1) { str += '...[' + nodes.length + ' items]'; } } return str || errorMsg; }, /** * Returns the DOM node bound to the Node instance * @method getDOMNodes * @return {Array} */ getDOMNodes: function() { return this._nodes; } }, true); NodeList.importMethod(Y.Node.prototype, [ /** * Called on each Node instance. Nulls internal node references, * removes any plugins and event listeners * @method destroy * @param {Boolean} recursivePurge (optional) Whether or not to * remove listeners from the node's subtree (default is false) * @see Node.destroy */ 'destroy', /** * Called on each Node instance. Removes and destroys all of the nodes * within the node * @method empty * @chainable * @see Node.empty */ 'empty', /** * Called on each Node instance. 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 * @see Node.remove */ 'remove', /** * Called on each Node instance. 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 * @see Node.set */ 'set' ]); // one-off implementation to convert array of Nodes to NodeList // e.g. Y.all('input').get('parentNode'); /** Called on each Node instance * @method get * @see Node */ NodeList.prototype.get = function(attr) { var ret = [], nodes = this._nodes, isNodeList = false, getTemp = NodeList._getTempNode, instance, val; if (nodes[0]) { instance = Y.Node._instances[nodes[0]._yuid] || getTemp(nodes[0]); val = instance._get(attr); if (val && val.nodeType) { isNodeList = true; } } Y.Array.each(nodes, function(node) { instance = Y.Node._instances[node._yuid]; if (!instance) { instance = getTemp(node); } val = instance._get(attr); if (!isNodeList) { // convert array of Nodes to NodeList val = Y.Node.scrubVal(val, instance); } ret.push(val); }); return (isNodeList) ? Y.all(ret) : ret; }; Y.NodeList = NodeList; Y.all = function(nodes) { return new NodeList(nodes); }; Y.Node.all = Y.all;