nodelist.js revision e3bd011d5ae662803e1bb6970a2c841df0036b42
/**
* The NodeList module provides support for managing collections of Nodes.
* @module node
* @submodule nodelist
*/
/**
* 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
*/
Y.Array._diff = function(a, b) {
var removed = [],
present = false,
i, j, lenA, lenB;
outer:
for (i = 0, lenA = a.length; i < lenA; i++) {
present = false;
for (j = 0, lenB = b.length; j < lenB; j++) {
if (a[i] === b[j]) {
present = true;
continue outer;
}
}
if (!present) {
removed[removed.length] = a[i];
}
}
return removed;
};
Y.Array.diff = function(a, b) {
return {
added: Y.Array._diff(b, a),
removed: Y.Array._diff(a, b)
};
};
var NodeList = function(config) {
var nodes = config.nodes || [],
doc = config.doc || Y.config.doc;
if (typeof nodes === 'string') {
this._query = nodes;
nodes = Y.Selector.query(nodes, doc);
} else if (nodes.item) { // Live NodeList, copy to static Array
nodes = Y.Array(nodes, 0, true);
}
NodeList._instances[Y.stamp(this)] = this;
this._nodes = nodes;
};
// end "globals"
NodeList.NAME = 'NodeList';
/**
* Retrieves the DOM nodes bound to a NodeList instance
* @method NodeList.getDOMNodes
* @static
*
* @param {Y.NodeList} node The NodeList instance
* @return {Array} The array of DOM nodes bound to the NodeList
*/
NodeList.getDOMNodes = function(nodeList) {
return nodeList._nodes;
};
NodeList._instances = [];
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) {
var tmp = NodeList._getTempNode();
if (name && fn) {
NodeList.prototype[name] = function() {
var ret = [],
args = arguments;
Y.Array.each(this._nodes, function(node) {
var UID = '_yuid',
instance = Y.Node._instances[node[UID]],
ctx,
result;
if (!instance) {
instance = tmp;
tmp._node = 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, 'warn', 'Node');
}
};
NodeList.importMethod = function(host, name, altName) {
if (typeof name === 'string') {
altName = altName || name;
NodeList.addMethod(name, host[name]);
} else {
Y.each(name, function(n) {
NodeList.importMethod(host, n);
});
}
};
NodeList._getTempNode = function() {
var tmp = NodeList._tempNode;
if (!tmp) {
tmp = Y.Node.create('<div></div>');
NodeList._tempNode = tmp;
}
return tmp;
};
Y.mix(NodeList.prototype, {
/**
* 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.get((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.get(node);
return fn.call(context || node, node, index, instance);
});
return instance;
},
batch: function(fn, context) {
var nodelist = this,
tmp = NodeList._getTempNode();
Y.Array.each(this._nodes, function(node, index) {
var instance = Y.Node._instances[node[UID]];
if (!instance) {
instance = tmp;
tmp._node = 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.get(node);
context = context || node;
return fn.call(context, node, index, instance);
});
},
/**
* Creates a documenFragment from the nodes bound to the NodeList instance
* @method toDocFrag
* @return Node a Node instance bound to the documentFragment
*/
toFrag: function() {
return Y.get(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 {Y.Node || DOMNode} node the node to search for
* @return {Int} 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));
},
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() {
delete NodeList._instances[this[UID]];
},
refresh: function() {
var doc,
diff,
oldList = this._nodes;
if (this._query) {
if (oldList && oldList[0] && oldList[0].ownerDocument) {
doc = oldList[0].ownerDocument;
}
this._nodes = Y.Selector.query(this._query, doc || Y.config.doc);
diff = Y.Array.diff(oldList, this._nodes);
diff.added = diff.added ? Y.all(diff.added) : null;
diff.removed = diff.removed ? Y.all(diff.removed) : null;
this.fire('refresh', diff);
}
return this;
},
/**
* Applies an event listener to each Node bound to the NodeList.
* @method on
* @param {String} type The event being listened for
* @param {Function} fn The handler to call when the event fires
* @param {Object} context The context to call the handler with.
* Default is the NodeList instance.
* @return {Object} Returns an event handle that can later be use to detach().
* @see Event.on
*/
on: function(type, fn, context, etc) {
var args = g_slice(arguments);
args[2] = context || this;
this.batch(function(node) {
node.on.apply(node, args);
});
},
/**
* Applies an event listener to each Node bound to the NodeList.
* The handler is called only after all on() handlers are called
* and the event is not prevented.
* @method after
* @param {String} type The event being listened for
* @param {Function} fn The handler to call when the event fires
* @param {Object} context The context to call the handler with.
* Default is the NodeList instance.
* @return {Object} Returns an event handle that can later be use to detach().
* @see Event.on
*/
after: function(type, fn, context, etc) {
var args = g_slice(arguments);
args[2] = context || this;
this.batch(function(node) {
node.after.apply(node, args);
});
},
/**
* Returns the current number of items in the NodeList.
* @method size
* @return {Int} The number of items in the NodeList.
*/
size: function() {
return this._nodes.length;
},
/** Called on each Node instance
* @get
* @see Node
*/
// one-off because we cant import from Node due to undefined return values
get: function(name) {
var ret = [],
tmp = NodeList._getTempNode();
NodeList.each(this, function(node) {
var instance = Y.Node._instances[node[UID]];
if (!instance) {
instance = tmp;
tmp._node = node;
}
ret[ret.length] = instance.get(name);
});
return ret;
},
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;
}
}, true);
NodeList.importMethod(Y.Node.prototype, [
/**
* Called on each Node instance
* @for NodeList
* @method append
* @see Node.append
*/
'append',
/**
* Called on each Node instance
* @method detach
* @see Node.detach
*/
'detach',
/** Called on each Node instance
* @method detachAll
* @see Node.detachAll
*/
'detachAll',
/** Called on each Node instance
* @method insert
* @see NodeInsert
*/
'insert',
/** Called on each Node instance
* @method plug
* @see Node.plug
*/
'plug',
/** Called on each Node instance
* @method prepend
* @see Node.prepend
*/
'prepend',
/** Called on each Node instance
* @method remove
* @see Node.remove
*/
'remove',
/** Called on each Node instance
* @method set
* @see Node.set
*/
'set',
/** Called on each Node instance
* @method setContent
* @see Node.setContent
*/
'setContent',
/** Called on each Node instance
* @method unplug
* @see Node.unplug
*/
'unplug'
]);
Y.NodeList = NodeList;
Y.all = function(nodes, doc, restrict) {
// TODO: propagate restricted to nodes?
var nodeList = new NodeList({
nodes: nodes,
doc: doc
});
// zero-length result returns null
return nodeList;
};
Y.Node.all = Y.all; // TODO: deprecated