2
Copyright (c) 2008, Yahoo! Inc. All rights reserved.
3
Code licensed under the BSD License:
4
http://developer.yahoo.net/yui/license.txt
7
YUI.add('node-base', function(Y) {
10
* The Node Utility provides a DOM-like interface for interacting with DOM nodes.
12
* @submodule node-base
16
* The Node class provides a wrapper for manipulating DOM Nodes.
17
* Node properties can be accessed via the set/get methods.
18
* Use Y.get() to retrieve Node instances.
20
* <strong>NOTE:</strong> Node properties are accessed using
21
* the <code>set</code> and <code>get</code> methods.
27
var OWNER_DOCUMENT = 'ownerDocument',
29
NODE_NAME = 'nodeName',
30
NODE_TYPE = 'nodeType',
32
Selector = Y.Selector,
37
// used with previous/next/ancestor tests
38
var _wrapFn = function(fn) {
41
ret = (typeof fn === 'string') ?
43
return Y.Selector.test(n, fn);
53
var _getDoc = function(node) {
54
var doc = Y.config.doc;
57
if (node[NODE_TYPE]) {
58
if (node[NODE_TYPE] === 9) { // already a document node
61
doc = node[OWNER_DOCUMENT];
63
} else if (Node[node._yuid]) { // Node instance document
64
doc = Node[node._yuid]()[0];
71
var _getDOMNode = function(node) {
72
if (node && !node[NODE_TYPE] && node._yuid) {
73
node = Node[node._yuid]()[0];
79
var Node = function() {
80
this.init.apply(this, arguments);
85
Node._deepGet = function (path, val) {
90
for (i = 0; val !== undefined && i < pl; ++i) {
98
Node._deepSet = function(path, val, subval) {
99
var leafIdx = path.length-1,
106
for (i = 0; o !== undefined && i < leafIdx; ++i) {
110
if (o !== undefined && o[path[i]] !== undefined) {
116
Node.scrubVal = function(val, node, depth) {
117
if (val !== undefined) {
118
if (typeof val === 'object' || typeof val === 'function') { // safari nodeList === function
119
if (val !== null && (
120
NODE_TYPE in val || // dom node
121
val.item || // dom collection or Node instance
122
(val[0] && val[0][NODE_TYPE]) || // assume array of nodes
123
val.document) // window TODO: restrict?
125
if (node && _restrict && _restrict[node._yuid] && !node.contains(val)) {
126
val = null; // not allowed to go outside of root node
128
if (val[NODE_TYPE] || val.document) { // node or window
135
depth = (depth === undefined) ? 4 : depth;
138
if (val.hasOwnProperty && val.hasOwnProperty(i)) {
139
val[i] = Node.scrubVal(val[i], node, --depth);
147
val = node; // for chaining
156
* Normalizes nodeInnerText and textContent.
160
'text': function(node) {
161
return Y.DOM.getText(node);
164
'options': function(node) {
165
return (node) ? node.getElementsByTagName('option') : [];
169
* Returns a NodeList instance.
173
'children': function(node) {
174
var children = node.children;
176
if (children === undefined) {
177
var childNodes = node.childNodes;
180
for (var i = 0, len = childNodes.length; i < len; ++i) {
181
if (childNodes[i][TAG_NAME]) {
182
children[children.length] = childNodes[i];
190
Node.methods = function(name, fn) {
191
if (typeof name == 'string') {
192
Node.prototype[name] = function() {
193
var args = _slice.call(arguments, 0),
195
getAll = (_instances[this._yuid]) ? false : true, // return array of vals for lists
196
ret = (getAll) ? [] : null,
199
var getValue = function(node) {
201
val = Node.scrubVal(fn.apply(instance, args), instance);
203
ret[ret.length] = val;
210
Node[instance._yuid](getValue);
213
} else { // assume object
214
Y.each(name, function(fn, name) {
215
Node.methods(name, fn);
221
Node.getDOMNode = _getDOMNode;
223
Node.wrapDOMMethod = function(name) {
225
return Y.DOM[name].apply(Y.DOM, arguments);
230
Node.addDOMMethods = function(methods) {
232
Y.each(methods, function(v, n) {
233
add[v] = Y.Node.wrapDOMMethod(v);
240
init: function(nodes, doc, isRoot, getAll) {
245
this.getId = function() {
249
var _all = function(fn, i) {
252
for (var node; node = nodes[i++];) {
259
// uid = selector || Y.guid(); // to cache queryAll results
262
if (nodes) { // zero length collection returns null
263
if (nodes[NODE_TYPE] || nodes.document) { // node or window
270
if (!getAll && nodes.length) { // stamp the dom node
271
try { // IE only allows ID on Element
272
if (nodes[0].uniqueID) {
273
uid = nodes[0].uniqueID;
275
nodes[0]._yuid = uid;
276
} catch(e) { // not cacheable
277
Y.log(nodes[0] + ' is not cacheable', 'warn', 'Node');
282
Node[uid] = _all; // for applying/returning dom nodes
285
_instances[uid] = this;
291
initPlugins: function() {
292
Y.each(Node.PLUGINS, function(config, fn) {
293
this.plug(fn, config);
298
* Filters the NodeList instance down to only nodes matching the given selector.
300
* @param {String} selector The selector to filter against
301
* @return {NodeList} NodeList containing the updated collection
304
filter: function(selector) {
305
return Node.scrubVal(Selector.filter(Node[this._yuid](), selector), this);
309
* Applies the given function to each Node in the NodeList.
311
* @param {Function} fn The function to apply
312
* @param {Object} context optional An optional context to apply the function with
313
* Default context is the NodeList instance
314
* @return {NodeList} NodeList containing the updated collection
317
each: function(fn, context) {
318
context = context || this;
319
Node[this._yuid](function(node) {
320
fn.call(context, Node.get(node));
325
* Returns the current number of items in the NodeList.
327
* @return {Int} The number of items in the NodeList.
330
return Node[this._yuid]().length;
334
* Retrieves the Node instance at the given index.
337
* @param {Number} index The index of the target Node.
338
* @return {Node} The Node instance at the given index.
340
item: function(index) {
341
var node = Node[this._yuid]()[index];
342
return Node.get(node);
346
* Attaches a DOM event handler.
348
* @param {String} type The type of DOM Event to listen for
349
* @param {Function} fn The handler to call when the event fires
350
* @param {Object} arg An argument object to pass to the handler
353
attach: function(type, fn, arg) {
354
var args = _slice.call(arguments, 0);
355
args.splice(2, 0, Node[this._yuid]());
356
return Y.Event.attach.apply(Y.Event, args);
362
* @param {String} type The type of DOM Event to listen for
363
* @param {Function} fn The handler to call when the event fires
364
* @param {Object} arg An argument object to pass to the handler
367
on: function(type, fn, arg) {
368
return this.attach.apply(this, arguments);
372
* Detaches a DOM event handler.
374
* @param {String} type The type of DOM Event
375
* @param {Function} fn The handler to call when the event fires
377
detach: function(type, fn) {
378
var args = _slice.call(arguments, 0);
379
args.splice(2, 0, Node[this._yuid]());
380
return Y.Event.detach.apply(Y.Event, args);
384
* Creates a Node instance from HTML string
386
* @param {String|Array} html The string of html to create
387
* @return {Node} A new Node instance
389
create: function(html) {
390
return Y.Node.create(html);
394
* Applies the supplied plugin to the node.
396
* @param {Function} The plugin Class to apply
397
* @param {Object} config An optional config to pass to the constructor
400
plug: function(PluginClass, config) {
401
config = config || {};
403
if (PluginClass && PluginClass.NS) {
404
this[PluginClass.NS] = new PluginClass(config);
409
//normalize: function() {},
410
//isSupported: function(feature, version) {},
411
toString: function() {
413
node = Node[this._yuid]()[0] || {};
416
str += node[NODE_NAME];
418
str += '#' + node.id;
421
if (node.className) {
422
str += '.' + node.className.replace(' ', '.');
425
'no nodes for ' + this._yuid;
432
addEventListener: function() {
433
return Y.Event.nativeAdd.apply(Y.Event, arguments);
436
removeEventListener: function() {
437
return Y.Event.nativeRemove.apply(Y.Event, arguments);
441
* Set the value of the property/attribute on the HTMLElement bound to this Node.
442
* Only strings/numbers/booleans are passed through unless a SETTER exists.
444
* @param {String} prop Property to set
445
* @param {any} val Value to apply to the given property
448
// TODO: document.location.href
449
set: function(node, prop, val) {
450
if (prop.indexOf('.') < 0) {
451
if (prop in Node.setters) { // use custom setter
452
Node.setters[prop](this, prop, val); // passing Node instance
453
} else if (node[prop] !== undefined) { // no expandos
456
Y.log(prop + ' not in ' + node, 'warn', 'Node');
459
Node._deepSet(prop.split('.'), node, val);
464
* Get the value of the property/attribute on the HTMLElement bound to this Node.
465
* Only strings/numbers/booleans are passed through unless a GETTER exists.
467
* @param {String} prop Property to get
468
* @return {any} Current value of the property
470
get: function(node, prop) {
472
if (prop.indexOf('.') < 0) {
473
if (prop in Node.getters) { // use custom getter
474
val = Node.getters[prop].call(this, node, prop);
479
// method wrapper uses undefined in chaining test
480
if (val === undefined) {
484
val = Node._deepGet(prop.split('.'), node);
490
invoke: function(node, method, a, b, c, d, e) {
493
if (a) { // first 2 may be Node instances
494
a = (a[NODE_TYPE]) ? a : _getDOMNode(a);
496
b = (b[NODE_TYPE]) ? b : _getDOMNode(b);
500
ret = node[method](a, b, c, d, e);
504
hasMethod: function(node, method) {
505
return !! node[method];
509
* Retrieves a Node instance of nodes based on the given CSS selector.
512
* @param {string} selector The CSS selector to test against.
513
* @return {Node} A Node instance for the matching HTMLElement.
515
query: function(node, selector) {
516
var ret = Selector.query(selector, node, true);
525
* Retrieves a nodeList based on the given CSS selector.
527
* @deprecated Use query() which returns all matches
529
* @param {string} selector The CSS selector to test against.
530
* @return {NodeList} A NodeList instance for the matching HTMLCollection/Array.
532
queryAll: function(node, selector) {
533
var ret = Selector.query(selector, node);
542
* Test if the supplied node matches the supplied selector.
545
* @param {string} selector The CSS selector to test against.
546
* @return {boolean} Whether or not the node matches the selector.
548
test: function(node, selector) {
549
return Selector.test(node, selector);
553
* Compares nodes to determine if they match.
554
* Node instances can be compared to each other and/or HTMLElements.
556
* @param {HTMLElement | Node} refNode The reference node to compare to the node.
557
* @return {Boolean} True if the nodes match, false if they do not.
559
compareTo: function(node, refNode) {
560
refNode = _getDOMNode(refNode) || node;
561
return node === refNode;
565
* Returns the nearest ancestor that passes the test applied by supplied boolean method.
567
* @param {String | Function} fn A selector or boolean method for testing elements.
568
* If a function is used, it receives the current node being tested as the only argument.
569
* @return {Node} The matching Node instance or null if not found
571
ancestor: function(node, fn) {
572
return Y.DOM.elementByAxis(node, 'parentNode', _wrapFn(fn));
576
* Returns the previous matching sibling.
577
* Returns the nearest element node sibling if no method provided.
579
* @param {String | Function} fn A selector or boolean method for testing elements.
580
* If a function is used, it receives the current node being tested as the only argument.
581
* @param {Boolean} all optional Whether all node types should be returned, or just element nodes.
582
* @return {Node} Node instance or null if not found
584
previous: function(node, fn, all) {
585
return Y.DOM.elementByAxis(node, 'previousSibling', _wrapFn(fn), all);
589
* Returns the next matching sibling.
590
* Returns the nearest element node sibling if no method provided.
592
* @param {String | Function} fn A selector or boolean method for testing elements.
593
* If a function is used, it receives the current node being tested as the only argument.
594
* @param {Boolean} all optional Whether all node types should be returned, or just element nodes.
595
* @return {Node} Node instance or null if not found
597
next: function(node, fn, all) {
598
return Y.DOM.elementByAxis(node, 'nextSibling', _wrapFn(fn), all);
602
* Determines whether the ndoe is an ancestor of another HTML element in the DOM hierarchy.
604
* @param {Node | HTMLElement} needle The possible node or descendent
605
* @return {Boolean} Whether or not this node is the needle its ancestor
607
contains: function(node, needle) {
608
return Y.DOM.contains(node, _getDOMNode(needle));
612
* Determines whether the node is appended to the document.
614
* @param {Node|HTMLElement} doc optional An optional document to check against.
615
* Defaults to current document.
616
* @return {Boolean} Whether or not this node is appended to the document.
618
inDoc: function(node, doc) {
619
doc = (doc) ? _getDoc(doc) : node.ownerDocument;
620
if (doc.documentElement) {
621
return Y.DOM.contains(doc.documentElement, node);
625
byId: function(node, id) {
626
var ret = node[OWNER_DOCUMENT].getElementById(id);
627
if (!ret || !Y.DOM.contains(node, ret)) {
636
* Creates a Node instance from an HTML string
638
* @param {String} html HTML string
640
Node.create = function(html) {
641
return Node.get(Y.DOM.create(html));
644
Node.getById = function(id, doc) {
645
doc = (doc && doc[NODE_TYPE]) ? doc : Y.config.doc;
646
return Node.get(doc.getElementById(id));
650
* Retrieves a Node instance for the given query or nodes.
651
* Note: Use 'document' string to retrieve document Node instance from string
654
* @param {document|HTMLElement|HTMLCollection|Array|String} node The object to wrap.
655
* @param {document|Node} doc optional The document containing the node. Defaults to current document.
656
* @param {boolean} isRoot optional Whether or not this node should be treated as a root node. Root nodes
657
* aren't allowed to traverse outside their DOM tree.
658
* @return {Node} A Node instance bound to the supplied node(s).
660
Node.get = function(node, doc, isRoot, getAll) {
664
if (typeof node === 'string') {
665
if (node === 'document') { // TODO: allow 'doc'? 'window'?
666
node = [Y.config.doc];
668
node = Selector.query(node, doc, !getAll); // Selector arg is getFirst
673
if (!getAll) { // reuse single element node instances
675
// verify IE's uniqueID in case the node was cloned
676
if (!node.uniqueID || (node.uniqueID === node._yuid)) {
677
instance = _instances[node._yuid];
683
instance = new Node(node, doc, isRoot, getAll);
686
if (instance && isRoot && !getAll) {
687
_restrict[instance._yuid] = true;
692
// zero length collection returns null
693
return (instance && instance.size()) ? instance : null;
697
* Retrieves a NodeList instance for the given object/string.
700
* @param {HTMLCollection|Array|String} node The object to wrap.
701
* @param {document|Node} doc optional The document containing the node. Defaults to current document.
702
* @param {boolean} isRoot optional Whether or not this node should be treated as a root node. Root nodes
703
* @return {NodeList} A NodeList instance for the supplied nodes.
705
Node.all = function(node, doc, isRoot) {
706
return Node.get.call(Node, node, doc, isRoot, true);
711
* Passes through to DOM method.
712
* @method replaceChild
713
* @param {HTMLElement | Node} node Node to be inserted
714
* @param {HTMLElement | Node} refNode Node to be replaced
715
* @return {Node} The replaced node
720
* Passes through to DOM method.
721
* @method appendChild
722
* @param {HTMLElement | Node} node Node to be appended
723
* @return {Node} The appended node
728
* Passes through to DOM method.
729
* @method insertBefore
730
* @param {HTMLElement | Node} newNode Node to be appended
731
* @param {HTMLElement | Node} refNode Node to be inserted before
732
* @return {Node} The inserted node
737
* Passes through to DOM method.
738
* @method removeChild
739
* @param {HTMLElement | Node} node Node to be removed
740
* @return {Node} The removed node
745
* Passes through to DOM method.
746
* @method hasChildNodes
747
* @return {Boolean} Whether or not the node has any childNodes
752
* Passes through to DOM method.
754
* @param {HTMLElement | Node} node Node to be cloned
755
* @return {Node} The clone
760
* Passes through to DOM method.
761
* @method getAttribute
762
* @param {String} attribute The attribute to retrieve
763
* @return {String} The current value of the attribute
768
* Passes through to DOM method.
769
* @method setAttribute
770
* @param {String} attribute The attribute to set
771
* @param {String} The value to apply to the attribute
777
* Passes through to DOM method.
778
* @method hasAttribute
779
* @param {String} attribute The attribute to test for
780
* @return {Boolean} Whether or not the attribute is present
785
* Passes through to DOM method.
786
* @method removeAttribute
787
* @param {String} attribute The attribute to be removed
793
* Passes through to DOM method.
794
* @method scrollIntoView
800
* Passes through to DOM method.
801
* @method getElementsByTagName
802
* @param {String} tagName The tagName to collect
803
* @return {NodeList} A NodeList representing the HTMLCollection
805
'getElementsByTagName',
808
* Passes through to DOM method.
815
* Passes through to DOM method.
822
* Passes through to DOM method.
823
* Only valid on FORM elements
830
* Passes through to DOM method.
831
* Only valid on FORM elements
838
* Passes through to DOM method.
843
], function(method) {
844
Node.prototype[method] = function(arg1, arg2, arg3) {
845
var ret = this.invoke(method, arg1, arg2, arg3);
850
if (!document.documentElement.hasAttribute) {
852
'hasAttribute': function(node, att) {
853
return !! node.getAttribute(att);
858
// used to call Node methods against NodeList nodes
863
* Extended Node interface for managing classNames.
869
Y.Node.addDOMMethods([
871
* Determines whether the node has the given className.
873
* @param {String} className the class name to search for
874
* @return {Boolean} Whether or not the node has the given class.
879
* Adds a class name to the node.
881
* @param {String} className the class name to add to the node's class attribute
887
* Removes a class name from the node.
888
* @method removeClass
889
* @param {String} className the class name to remove from the node's class attribute
895
* Replace a class with another class.
896
* If no oldClassName is present, the newClassName is simply added.
897
* @method replaceClass
898
* @param {String} oldClassName the class name to be replaced
899
* @param {String} newClassName the class name that will be replacing the old class name
905
* If the className exists on the node it is removed, if it doesn't exist it is added.
906
* @method toggleClass
907
* @param {String} className the class name to be toggled
914
}, '3.0.0pr2' ,{requires:['dom-base', 'selector']});