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('dom', function(Y) {
10
* The DOM utility provides a cross-browser abtraction layer
11
* normalizing DOM tasks, and adds extra helper functionality
12
* for other common tasks.
19
* Provides DOM helper methods.
23
var NODE_TYPE = 'nodeType',
24
OWNER_DOCUMENT = 'ownerDocument',
25
DOCUMENT_ELEMENT = 'documentElement',
26
DEFAULT_VIEW = 'defaultView',
27
PARENT_WINDOW = 'parentWindow',
29
PARENT_NODE = 'parentNode',
30
FIRST_CHILD = 'firstChild',
31
LAST_CHILD = 'lastChild',
32
PREVIOUS_SIBLING = 'previousSibling',
33
NEXT_SIBLING = 'nextSibling',
34
CONTAINS = 'contains',
35
COMPARE_DOCUMENT_POSITION = 'compareDocumentPosition',
36
INNER_TEXT = 'innerText',
37
TEXT_CONTENT = 'textContent',
40
UNDEFINED = undefined;
42
var re_tag = /<([a-z]+)/i;
44
var templateCache = {};
48
* Returns the HTMLElement with the given ID (Wrapper for document.getElementById).
50
* @param {String} id the id attribute
51
* @param {Object} doc optional The document to search. Defaults to current document
52
* @return {HTMLElement | null} The HTMLElement with the id, or null if none found.
54
byId: function(id, doc) {
55
return Y.DOM._getDoc(doc).getElementById(id);
59
* Returns the text content of the HTMLElement.
61
* @param {HTMLElement} element The html element.
62
* @return {String} The text content of the element (includes text of any descending elements).
64
getText: function(element) {
65
var text = element ? element[TEXT_CONTENT] : '';
66
if (text === UNDEFINED && INNER_TEXT in element) {
67
text = element[INNER_TEXT];
73
* Finds the firstChild of the given HTMLElement.
75
* @param {HTMLElement} element The html element.
76
* @param {Function} fn optional An optional boolean test to apply.
77
* The optional function is passed the current HTMLElement being tested as its only argument.
78
* If no function is given, the first found is returned.
79
* @return {HTMLElement | null} The first matching child html element.
81
firstChild: function(element, fn) {
82
return Y.DOM._childBy(element, null, fn);
85
firstChildByTag: function(element, tag, fn) {
86
return Y.DOM._childBy(element, tag, fn);
90
* Finds the lastChild of the given HTMLElement.
92
* @param {HTMLElement} element The html element.
93
* @param {Function} fn optional An optional boolean test to apply.
94
* The optional function is passed the current HTMLElement being tested as its only argument.
95
* If no function is given, the first found is returned.
96
* @return {HTMLElement | null} The first matching child html element.
98
lastChild: function(element, fn) {
99
return Y.DOM._childBy(element, null, fn, true);
102
lastChildByTag: function(element, tag, fn) {
103
return Y.DOM._childBy(element, tag, fn, true);
107
* Finds all HTMLElement childNodes matching the given tag.
108
* @method childrenByTag
109
* @param {HTMLElement} element The html element.
110
* @param {String} tag The tag to search for.
111
* @param {Function} fn optional An optional boolean test to apply.
112
* The optional function is passed the current HTMLElement being tested as its only argument.
113
* If no function is given, all children with the given tag are collected.
114
* @return {Array} The collection of child elements.
116
childrenByTag: function() {
117
if (document[DOCUMENT_ELEMENT].children) {
118
return function(element, tag, fn, toArray) { // TODO: keep toArray option?
119
tag = (tag && tag !== '*') ? tag : null;
122
elements = (tag) ? element.children.tags(tag) : element.children;
125
elements = Y.DOM.filterElementsBy(elements, fn);
132
return function(element, tag, fn) {
133
tag = (tag && tag !== '*') ? tag.toUpperCase() : null;
138
elements = element.childNodes;
139
if (tag) { // wrap fn and add tag test TODO: allow tag in filterElementsBy?
140
wrapFn = function(el) {
141
return el[TAG_NAME].toUpperCase() === tag && (!fn || fn(el));
145
elements = Y.DOM.filterElementsBy(elements, wrapFn);
153
* Finds all HTMLElement childNodes.
155
* @param {HTMLElement} element The html element.
156
* @param {Function} fn optional An optional boolean test to apply.
157
* The optional function is passed the current HTMLElement being tested as its only argument.
158
* If no function is given, all children are collected.
159
* @return {Array} The collection of child elements.
161
children: function(element, fn) {
162
return Y.DOM.childrenByTag(element, null, fn);
166
* Finds the previous sibling of the element.
168
* @param {HTMLElement} element The html element.
169
* @param {Function} fn optional An optional boolean test to apply.
170
* The optional function is passed the current DOM node being tested as its only argument.
171
* If no function is given, the first sibling is returned.
172
* @param {Boolean} all optional Whether all node types should be scanned, or just element nodes.
173
* @return {HTMLElement | null} The matching DOM node or null if none found.
175
previous: function(element, fn, all) {
176
return Y.DOM.elementByAxis(element, PREVIOUS_SIBLING, fn);
180
* Finds the next sibling of the element.
182
* @param {HTMLElement} element The html element.
183
* @param {Function} fn optional An optional boolean test to apply.
184
* The optional function is passed the current DOM node being tested as its only argument.
185
* If no function is given, the first sibling is returned.
186
* @param {Boolean} all optional Whether all node types should be scanned, or just element nodes.
187
* @return {HTMLElement | null} The matching DOM node or null if none found.
189
next: function(element, fn) {
190
return Y.DOM.elementByAxis(element, NEXT_SIBLING, fn);
194
* Finds the ancestor of the element.
196
* @param {HTMLElement} element The html element.
197
* @param {Function} fn optional An optional boolean test to apply.
198
* The optional function is passed the current DOM node being tested as its only argument.
199
* If no function is given, the parentNode is returned.
200
* @param {Boolean} all optional Whether all node types should be scanned, or just element nodes.
201
* @return {HTMLElement | null} The matching DOM node or null if none found.
203
ancestor: function(element, fn) {
204
return Y.DOM.elementByAxis(element, PARENT_NODE, fn);
208
* Searches the element by the given axis for the first matching element.
209
* @method elementByAxis
210
* @param {HTMLElement} element The html element.
211
* @param {String} axis The axis to search (parentNode, nextSibling, previousSibling).
212
* @param {Function} fn optional An optional boolean test to apply.
213
* @param {Boolean} all optional Whether all node types should be returned, or just element nodes.
214
* The optional function is passed the current DOM node being tested as its only argument.
215
* If no function is given, the first element is returned.
216
* @return {HTMLElement | null} The matching element or null if none found.
218
elementByAxis: function(element, axis, fn, all) {
219
while (element && (element = element[axis])) { // NOTE: assignment
220
if ( (all || element[TAG_NAME]) && (!fn || fn(element)) ) {
228
* Finds all elements with the given tag.
230
* @param {String} tag The tag being search for.
231
* @param {HTMLElement} root optional An optional root element to start from.
232
* @param {Function} fn optional An optional boolean test to apply.
233
* The optional function is passed the current HTMLElement being tested as its only argument.
234
* If no function is given, all elements with the given tag are returned.
235
* @return {Array} The collection of matching elements.
237
byTag: function(tag, root, fn) {
238
root = root || Y.config.doc;
240
var elements = root.getElementsByTagName(tag),
243
for (var i = 0, len = elements[LENGTH]; i < len; ++i) {
244
if ( !fn || fn(elements[i]) ) {
245
retNodes[retNodes[LENGTH]] = elements[i];
252
* Finds the first element with the given tag.
254
* @param {String} tag The tag being search for.
255
* @param {HTMLElement} root optional An optional root element to start from.
256
* @param {Function} fn optional An optional boolean test to apply.
257
* The optional function is passed the current HTMLElement being tested as its only argument.
258
* If no function is given, the first match is returned.
259
* @return {HTMLElement} The matching element.
261
firstByTag: function(tag, root, fn) {
262
root = root || Y.config.doc;
264
var elements = root.getElementsByTagName(tag),
267
for (var i = 0, len = elements[LENGTH]; i < len; ++i) {
268
if ( !fn || fn(elements[i]) ) {
277
* Filters a collection of HTMLElements by the given attributes.
278
* @method filterElementsBy
279
* @param {Array} elements The collection of HTMLElements to filter.
280
* @param {Function} fn A boolean test to apply.
281
* The function is passed the current HTMLElement being tested as its only argument.
282
* If no function is given, all HTMLElements are kept.
283
* @return {Array} The filtered collection of HTMLElements.
285
filterElementsBy: function(elements, fn, firstOnly) {
286
var ret = (firstOnly) ? null : [];
287
for (var i = 0, el; el = elements[i++];) {
288
if ( el[TAG_NAME] && (!fn || fn(el)) ) {
293
ret[ret[LENGTH]] = el;
302
* Determines whether or not one HTMLElement is or contains another HTMLElement.
304
* @param {HTMLElement} element The containing html element.
305
* @param {HTMLElement} needle The html element that may be contained.
306
* @return {Boolean} Whether or not the element is or contains the needle.
308
contains: function(element, needle) {
311
if (!needle || !needle[NODE_TYPE] || !element || !element[NODE_TYPE]) {
313
} else if (element[CONTAINS]) {
314
if (Y.UA.opera || needle[NODE_TYPE] === 1) { // IE & SAF contains fail if needle not an ELEMENT_NODE
315
ret = element[CONTAINS](needle);
317
ret = Y.DOM._bruteContains(element, needle);
319
} else if (element[COMPARE_DOCUMENT_POSITION]) { // gecko
320
if (element === needle || !!(element[COMPARE_DOCUMENT_POSITION](needle) & 16)) {
329
* Returns an HTMLElement for the given string of HTML.
331
* @param {String} html The string of HTML to convert to a DOM element.
332
* @param {Document} document An optional document to create the node with.
333
* @return {HTMLElement} The newly created element.
335
create: function(html, doc) {
336
doc = doc || Y.config.doc;
337
var m = re_tag.exec(html);
338
var create = Y.DOM._create,
339
custom = Y.DOM.creators,
342
if (m && custom[m[1]]) {
343
if (typeof custom[m[1]] === 'function') {
344
create = custom[m[1]];
349
ret = create(html, doc, tag);
350
return (ret.childNodes.length > 1) ? ret.childNodes : ret.childNodes[0]; // collection or item
351
//return ret.firstChild;
354
_create: function(html, doc, tag) {
356
var frag = templateCache[tag] || doc.createElement(tag);
357
frag.innerHTML = Y.Lang.trim(html);
362
* Brute force version of contains.
363
* Used for browsers without contains support for non-HTMLElement Nodes (textNodes, etc).
364
* @method _bruteContains
366
* @param {HTMLElement} element The containing html element.
367
* @param {HTMLElement} needle The html element that may be contained.
368
* @return {Boolean} Whether or not the element is or contains the needle.
370
_bruteContains: function(element, needle) {
372
if (element === needle) {
375
needle = needle.parentNode;
381
* Memoizes dynamic regular expressions to boost runtime performance.
384
* @param {String} str The string to convert to a regular expression.
385
* @param {String} flags optional An optinal string of flags.
386
* @return {RegExp} An instance of RegExp
388
_getRegExp: function(str, flags) {
390
Y.DOM._regexCache = Y.DOM._regexCache || {};
391
if (!Y.DOM._regexCache[str + flags]) {
392
Y.DOM._regexCache[str + flags] = new RegExp(str, flags);
394
return Y.DOM._regexCache[str + flags];
398
* returns the appropriate document.
401
* @param {HTMLElement} element optional Target element.
402
* @return {Object} The document for the given element or the default document.
404
_getDoc: function(element) {
405
element = element || {};
406
return (element[NODE_TYPE] === 9) ? element : element[OWNER_DOCUMENT] ||
411
* returns the appropriate window.
414
* @param {HTMLElement} element optional Target element.
415
* @return {Object} The window for the given element or the default window.
417
_getWin: function(element) {
418
var doc = Y.DOM._getDoc(element);
419
return (element.document) ? element : doc[DEFAULT_VIEW] ||
420
doc[PARENT_WINDOW] || Y.config.win;
423
_childBy: function(element, tag, fn, rev) {
429
root = element[LAST_CHILD];
430
axis = PREVIOUS_SIBLING;
432
root = element[FIRST_CHILD];
436
if (Y.DOM._testElement(root, tag, fn)) { // is the matching element
438
} else { // need to scan nextSibling axis of firstChild to find matching element
439
ret = Y.DOM.elementByAxis(root, axis, fn);
446
_testElement: function(element, tag, fn) {
447
tag = (tag && tag !== '*') ? tag.toUpperCase() : null;
448
return (element && element[TAG_NAME] &&
449
(!tag || element[TAG_NAME].toUpperCase() === tag) &&
450
(!fn || fn(element)));
455
_IESimpleCreate: function(html, doc) {
456
doc = doc || Y.config.doc;
457
return doc.createElement(html);
463
var creators = Y.DOM.creators,
464
create = Y.DOM.create,
465
re_tbody = /(?:\/(?:thead|tfoot|tbody|caption|col|colgroup)>)+\s*<tbody/;
467
var TABLE_OPEN = '<table>',
468
TABLE_CLOSE = '</table>';
470
if (Y.UA.gecko || Y.UA.ie) { // require custom creation code for certain element types
472
option: function(html, doc) {
473
var frag = create('<select>' + html + '</select>');
477
tr: function(html, doc) {
478
var frag = creators.tbody('<tbody>' + html + '</tbody>', doc);
479
return frag.firstChild;
482
td: function(html, doc) {
483
var frag = creators.tr('<tr>' + html + '</tr>', doc);
484
return frag.firstChild;
487
tbody: function(html, doc) {
488
var frag = create(TABLE_OPEN + html + TABLE_CLOSE, doc);
495
creators.col = creators.tbody; // IE wraps in colgroup
499
// TODO: allow multiples ("<link><link>")
500
creators.col = creators.script = creators.link = Y.DOM._IESimpleCreate;
502
// TODO: thead/tfoot with nested tbody
503
creators.tbody = function(html, doc) {
504
var frag = create(TABLE_OPEN + html + TABLE_CLOSE, doc);
505
var tb = frag.children.tags('tbody')[0];
506
if (frag.children.length > 1 && tb && !re_tbody.test(html)) {
507
tb.parentNode.removeChild(tb);
514
if (Y.UA.gecko || Y.UA.ie) { // require custom creation code for certain element types
517
thead: creators.tbody,
518
tfoot: creators.tbody,
519
caption: creators.tbody,
520
colgroup: creators.tbody,
522
optgroup: creators.option
527
* The DOM utility provides a cross-browser abtraction layer
528
* normalizing DOM tasks, and adds extra helper functionality
529
* for other common tasks.
531
* @submodule dom-base
535
var CLASS_NAME = 'className';
539
* Determines whether a DOM element has the given className.
541
* @param {HTMLElement} element The DOM element.
542
* @param {String} className the class name to search for
543
* @return {Boolean} Whether or not the element has the given class.
545
hasClass: function(node, className) {
546
var re = Y.DOM._getRegExp('(?:^|\\s+)' + className + '(?:\\s+|$)');
547
return re.test(node[CLASS_NAME]);
551
* Adds a class name to a given DOM element.
553
* @param {HTMLElement} element The DOM element.
554
* @param {String} className the class name to add to the class attribute
556
addClass: function(node, className) {
557
if (!Y.DOM.hasClass(node, className)) { // skip if already present
558
node[CLASS_NAME] = Y.Lang.trim([node[CLASS_NAME], className].join(' '));
563
* Removes a class name from a given element.
564
* @method removeClass
565
* @param {HTMLElement} element The DOM element.
566
* @param {String} className the class name to remove from the class attribute
568
removeClass: function(node, className) {
569
if (className && Y.DOM.hasClass(node, className)) {
570
node[CLASS_NAME] = Y.Lang.trim(node[CLASS_NAME].replace(Y.DOM._getRegExp('(?:^|\\s+)' +
571
className + '(?:\\s+|$)'), ' '));
573
if ( Y.DOM.hasClass(node, className) ) { // in case of multiple adjacent
574
Y.DOM.removeClass(node, className);
580
* Replace a class with another class for a given element.
581
* If no oldClassName is present, the newClassName is simply added.
582
* @method replaceClass
583
* @param {HTMLElement} element The DOM element.
584
* @param {String} oldClassName the class name to be replaced
585
* @param {String} newClassName the class name that will be replacing the old class name
587
replaceClass: function(node, oldC, newC) {
588
Y.DOM.addClass(node, newC);
589
Y.DOM.removeClass(node, oldC);
593
* If the className exists on the node it is removed, if it doesn't exist it is added.
594
* @method toggleClass
595
* @param {HTMLElement} element The DOM element.
596
* @param {String} className the class name to be toggled
598
toggleClass: function(node, className) {
599
if (Y.DOM.hasClass(node, className)) {
600
Y.DOM.removeClass(node, className);
602
Y.DOM.addClass(node, className);
608
* Add style management functionality to DOM.
610
* @submodule dom-style
614
var DOCUMENT_ELEMENT = 'documentElement',
615
DEFAULT_VIEW = 'defaultView',
616
OWNER_DOCUMENT = 'ownerDocument',
619
CSS_FLOAT = 'cssFloat',
620
STYLE_FLOAT = 'styleFloat',
621
TRANSPARENT = 'transparent',
625
BORDER_TOP_WIDTH = 'borderTopWidth',
626
BORDER_RIGHT_WIDTH = 'borderRightWidth',
627
BORDER_BOTTOM_WIDTH = 'borderBottomWidth',
628
BORDER_LEFT_WIDTH = 'borderLeftWidth',
629
GET_COMPUTED_STYLE = 'getComputedStyle',
631
DOCUMENT = Y.config.doc,
632
UNDEFINED = undefined,
634
re_color = /color$/i;
642
* Sets a style property for a given element.
644
* @param {HTMLElement} An HTMLElement to apply the style to.
645
* @param {String} att The style property to set.
646
* @param {String|Number} val The value.
648
setStyle: function(node, att, val) {
649
var style = node[STYLE],
650
CUSTOM_STYLES = Y.DOM.CUSTOM_STYLES;
653
if (att in CUSTOM_STYLES) {
654
if (CUSTOM_STYLES[att].set) {
655
CUSTOM_STYLES[att].set(node, val, style);
656
return; // NOTE: return
657
} else if (typeof CUSTOM_STYLES[att] === 'string') {
658
att = CUSTOM_STYLES[att];
666
* Returns the current style value for the given property.
668
* @param {HTMLElement} An HTMLElement to get the style from.
669
* @param {String} att The style property to get.
671
getStyle: function(node, att) {
672
var style = node[STYLE],
673
CUSTOM_STYLES = Y.DOM.CUSTOM_STYLES,
677
if (att in CUSTOM_STYLES) {
678
if (CUSTOM_STYLES[att].get) {
679
return CUSTOM_STYLES[att].get(node, att, style); // NOTE: return
680
} else if (typeof CUSTOM_STYLES[att] === 'string') {
681
att = CUSTOM_STYLES[att];
685
if (val === '') { // TODO: is empty string sufficient?
686
val = Y.DOM[GET_COMPUTED_STYLE](node, att);
694
* Sets multiple style properties.
696
* @param {HTMLElement} node An HTMLElement to apply the styles to.
697
* @param {Object} hash An object literal of property:value pairs.
699
'setStyles': function(node, hash) {
700
Y.each(hash, function(v, n) {
701
Y.DOM.setStyle(node, n, v);
706
* Returns the computed style for the given node.
707
* @method getComputedStyle
708
* @param {HTMLElement} An HTMLElement to get the style from.
709
* @param {String} att The style property to get.
710
* @return {String} The computed value of the style property.
712
getComputedStyle: function(node, att) {
714
doc = node[OWNER_DOCUMENT];
717
val = doc[DEFAULT_VIEW][GET_COMPUTED_STYLE](node, '')[att];
723
if (DOCUMENT[DOCUMENT_ELEMENT][STYLE][CSS_FLOAT] !== UNDEFINED) {
724
Y.DOM.CUSTOM_STYLES[FLOAT] = CSS_FLOAT;
725
} else if (DOCUMENT[DOCUMENT_ELEMENT][STYLE][STYLE_FLOAT] !== UNDEFINED) {
726
Y.DOM.CUSTOM_STYLES[FLOAT] = STYLE_FLOAT;
729
if (Y.UA.opera) { // opera defaults to hex instead of RGB
730
Y.DOM[GET_COMPUTED_STYLE] = function(node, att) {
731
var view = node[OWNER_DOCUMENT][DEFAULT_VIEW],
732
val = view[GET_COMPUTED_STYLE](node, '')[att];
734
if (re_color.test(att)) {
735
val = Y.Color.toRGB(val);
743
if (Y.UA.webkit) { // safari converts transparent to rgba()
744
Y.DOM[GET_COMPUTED_STYLE] = function(node, att) {
745
var view = node[OWNER_DOCUMENT][DEFAULT_VIEW],
746
val = view[GET_COMPUTED_STYLE](node, '')[att];
748
if (val === 'rgba(0, 0, 0, 0)') {
758
* Adds position and region management functionality to DOM.
760
* @submodule dom-screen
764
var OFFSET_TOP = 'offsetTop',
765
DOCUMENT_ELEMENT = 'documentElement',
766
COMPAT_MODE = 'compatMode',
767
OFFSET_LEFT = 'offsetLeft',
768
OFFSET_PARENT = 'offsetParent',
769
POSITION = 'position',
771
RELATIVE = 'relative',
774
SCROLL_LEFT = 'scrollLeft',
775
SCROLL_TOP = 'scrollTop',
776
_BACK_COMPAT = 'BackCompat',
780
BORDER_LEFT_WIDTH = 'borderLeftWidth',
781
BORDER_TOP_WIDTH = 'borderTopWidth',
782
GET_BOUNDING_CLIENT_RECT = 'getBoundingClientRect',
783
GET_COMPUTED_STYLE = 'getComputedStyle',
784
RE_TABLE = /^t(?:able|d|h)$/i;
788
* Returns the inner height of the viewport (exludes scrollbar).
790
* @return {Int} The pixel height of the viewport.
792
winHeight: function(node) {
793
var h = Y.DOM._getWinSize(node)[HEIGHT];
798
* Returns the inner width of the viewport (exludes scrollbar).
800
* @return {Int} The pixel width of the viewport.
802
winWidth: function(node) {
803
var w = Y.DOM._getWinSize(node)[WIDTH];
810
* @return {Int} The pixel height of the document.
812
docHeight: function(node) {
813
var h = Y.DOM._getDocSize(node)[HEIGHT];
814
return Math.max(h, Y.DOM._getWinSize(node)[HEIGHT]);
820
* @return {Int} The pixel width of the document.
822
docWidth: function(node) {
823
var w = Y.DOM._getDocSize(node)[WIDTH];
824
return Math.max(w, Y.DOM._getWinSize(node)[WIDTH]);
828
* Amount page has been scroll vertically
830
* @return {Int} The scroll amount in pixels.
832
docScrollX: function(node) {
833
var doc = Y.DOM._getDoc();
834
return Math.max(doc[DOCUMENT_ELEMENT][SCROLL_LEFT], doc.body[SCROLL_LEFT]);
838
* Amount page has been scroll horizontally
840
* @return {Int} The scroll amount in pixels.
842
docScrollY: function(node) {
843
var doc = Y.DOM._getDoc();
844
return Math.max(doc[DOCUMENT_ELEMENT][SCROLL_TOP], doc.body[SCROLL_TOP]);
848
* Gets the current position of an element based on page coordinates.
849
* Element must be part of the DOM tree to have page coordinates
850
* (display:none or elements not appended return false).
852
* @param element The target element
853
* @return {Array} The XY position of the element
855
TODO: test inDocument/display
858
if (document[DOCUMENT_ELEMENT][GET_BOUNDING_CLIENT_RECT]) {
859
return function(node) {
863
var scrollLeft = Y.DOM.docScrollX(node),
864
scrollTop = Y.DOM.docScrollY(node),
865
box = node[GET_BOUNDING_CLIENT_RECT](),
866
doc = Y.DOM._getDoc(node),
867
//Round the numbers so we get sane data back
868
xy = [Math.floor(box[LEFT]), Math.floor(box[TOP])];
871
var off1 = 2, off2 = 2,
872
mode = doc[COMPAT_MODE],
873
bLeft = Y.DOM[GET_COMPUTED_STYLE](doc[DOCUMENT_ELEMENT], BORDER_LEFT_WIDTH),
874
bTop = Y.DOM[GET_COMPUTED_STYLE](doc[DOCUMENT_ELEMENT], BORDER_TOP_WIDTH);
877
if (mode !== _BACK_COMPAT) {
883
if ((mode == _BACK_COMPAT)) {
884
if (bLeft !== MEDIUM) {
885
off1 = parseInt(bLeft, 10);
887
if (bTop !== MEDIUM) {
888
off2 = parseInt(bTop, 10);
896
if ((scrollTop || scrollLeft)) {
901
// gecko may return sub-pixel (non-int) values
902
xy[0] = Math.floor(xy[0]);
903
xy[1] = Math.floor(xy[1]);
908
return function(node) { // manually calculate by crawling up offsetParents
909
//Calculate the Top and Left border sizes (assumes pixels)
910
var xy = [node[OFFSET_LEFT], node[OFFSET_TOP]],
912
bCheck = ((Y.UA.gecko || (Y.UA.webkit > 519)) ? true : false);
914
while ((parentNode = parentNode[OFFSET_PARENT])) {
915
xy[0] += parentNode[OFFSET_LEFT];
916
xy[1] += parentNode[OFFSET_TOP];
918
xy = Y.DOM._calcBorders(parentNode, xy);
922
// account for any scrolled ancestors
923
if (Y.DOM.getStyle(node, POSITION) != FIXED) {
925
var scrollTop, scrollLeft;
927
while ((parentNode = parentNode.parentNode)) {
928
scrollTop = parentNode[SCROLL_TOP];
929
scrollLeft = parentNode[SCROLL_LEFT];
931
//Firefox does something funky with borders when overflow is not visible.
932
if (Y.UA.gecko && (Y.DOM.getStyle(parentNode, 'overflow') !== 'visible')) {
933
xy = Y.DOM._calcBorders(parentNode, xy);
937
if (scrollTop || scrollLeft) {
942
xy[0] += Y.DOM.docScrollX(node);
943
xy[1] += Y.DOM.docScrollY(node);
946
//Fix FIXED position -- add scrollbars
948
xy[0] -= Y.DOM.docScrollX(node);
949
xy[1] -= Y.DOM.docScrollY(node);
950
} else if (Y.UA.webkit || Y.UA.gecko) {
951
xy[0] += Y.DOM.docScrollX(node);
952
xy[1] += Y.DOM.docScrollY(node);
955
//Round the numbers so we get sane data back
956
xy[0] = Math.floor(xy[0]);
957
xy[1] = Math.floor(xy[1]);
962
}(),// NOTE: Executing for loadtime branching
965
* Gets the current X position of an element based on page coordinates.
966
* Element must be part of the DOM tree to have page coordinates
967
* (display:none or elements not appended return false).
969
* @param element The target element
970
* @return {Int} The X position of the element
973
getX: function(node) {
974
return Y.DOM.getXY(node)[0];
978
* Gets the current Y position of an element based on page coordinates.
979
* Element must be part of the DOM tree to have page coordinates
980
* (display:none or elements not appended return false).
982
* @param element The target element
983
* @return {Int} The Y position of the element
986
getY: function(node) {
987
return Y.DOM.getXY(node)[1];
991
* Set the position of an html element in page coordinates.
992
* The element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
994
* @param element The target element
995
* @param {Array} xy Contains X & Y values for new position (coordinates are page-based)
996
* @param {Boolean} noRetry By default we try and set the position a second time if the first fails
998
setXY: function(node, xy, noRetry) {
999
var pos = Y.DOM.getStyle(node, POSITION),
1000
setStyle = Y.DOM.setStyle,
1001
delta = [ // assuming pixels; if not we will have to retry
1002
parseInt( Y.DOM[GET_COMPUTED_STYLE](node, LEFT), 10 ),
1003
parseInt( Y.DOM[GET_COMPUTED_STYLE](node, TOP), 10 )
1006
if (pos == 'static') { // default to relative
1008
setStyle(node, POSITION, pos);
1011
var currentXY = Y.DOM.getXY(node);
1013
if (currentXY === false) { // has to be part of doc to have xy
1017
if ( isNaN(delta[0]) ) {// in case of 'auto'
1018
delta[0] = (pos == RELATIVE) ? 0 : node[OFFSET_LEFT];
1020
if ( isNaN(delta[1]) ) { // in case of 'auto'
1021
delta[1] = (pos == RELATIVE) ? 0 : node[OFFSET_TOP];
1024
if (xy[0] !== null) {
1025
setStyle(node, LEFT, xy[0] - currentXY[0] + delta[0] + 'px');
1028
if (xy[1] !== null) {
1029
setStyle(node, TOP, xy[1] - currentXY[1] + delta[1] + 'px');
1033
var newXY = Y.DOM.getXY(node);
1035
// if retry is true, try one more time if we miss
1036
if ( (xy[0] !== null && newXY[0] != xy[0]) ||
1037
(xy[1] !== null && newXY[1] != xy[1]) ) {
1038
Y.DOM.setXY(node, xy, true);
1045
* Set the X position of an html element in page coordinates, regardless of how the element is positioned.
1046
* The element(s) must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
1048
* @param element The target element
1049
* @param {Int} x The X values for new position (coordinates are page-based)
1051
setX: function(node, x) {
1052
return Y.DOM.setXY(node, [x, null]);
1056
* Set the Y position of an html element in page coordinates, regardless of how the element is positioned.
1057
* The element(s) must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
1059
* @param element The target element
1060
* @param {Int} y The Y values for new position (coordinates are page-based)
1062
setY: function(node, y) {
1063
return Y.DOM.setXY(node, [null, y]);
1066
_calcBorders: function(node, xy2) {
1067
var t = parseInt(Y.DOM[GET_COMPUTED_STYLE](node, BORDER_TOP_WIDTH), 10) || 0,
1068
l = parseInt(Y.DOM[GET_COMPUTED_STYLE](node, BORDER_LEFT_WIDTH), 10) || 0;
1070
if (RE_TABLE.test(node.tagName)) {
1080
_getWinSize: function(node) {
1081
var doc = Y.DOM._getDoc(),
1082
win = doc.defaultView || doc.parentWindow,
1083
mode = doc[COMPAT_MODE],
1084
h = win.innerHeight,
1086
root = doc[DOCUMENT_ELEMENT];
1088
if ( mode && !Y.UA.opera ) { // IE, Gecko
1089
if (mode != 'CSS1Compat') { // Quirks
1092
h = root.clientHeight;
1093
w = root.clientWidth;
1095
return { height: h, width: w };
1098
_getDocSize: function(node) {
1099
var doc = Y.DOM._getDoc(),
1100
root = doc[DOCUMENT_ELEMENT];
1102
if (doc[COMPAT_MODE] != 'CSS1Compat') {
1106
return { height: root.scrollHeight, width: root.scrollWidth };
1111
* Adds position and region management functionality to DOM.
1113
* @submodule dom-screen
1117
var OFFSET_WIDTH = 'offsetWidth',
1118
OFFSET_HEIGHT = 'offsetHeight',
1119
TAG_NAME = 'tagName';
1121
var getOffsets = function(r1, r2) {
1123
var t = Math.max(r1.top, r2.top ),
1124
r = Math.min(r1.right, r2.right ),
1125
b = Math.min(r1.bottom, r2.bottom),
1126
l = Math.max(r1.left, r2.left );
1138
* Returns an Object literal containing the following about this element: (top, right, bottom, left)
1140
* @param {HTMLElement} element The DOM element.
1141
@return {Object} Object literal containing the following about this element: (top, right, bottom, left)
1143
region: function(node) {
1144
var x = Y.DOM.getXY(node),
1152
right: x[0] + node[OFFSET_WIDTH],
1153
bottom: x[1] + node[OFFSET_HEIGHT],
1155
height: node[OFFSET_HEIGHT],
1156
width: node[OFFSET_WIDTH]
1164
* Find the intersect information for the passes nodes.
1166
* @param {HTMLElement} element The first element
1167
* @param {HTMLElement | Object} element2 The element or region to check the interect with
1168
* @param {Object} altRegion An object literal containing the region for the first element if we already have the data (for performance i.e. DragDrop)
1169
@return {Object} Object literal containing the following intersection data: (top, right, bottom, left, area, yoff, xoff, inRegion)
1171
intersect: function(node, node2, altRegion) {
1172
var r = altRegion || Y.DOM.region(node), region = {};
1176
region = Y.DOM.region(n);
1177
} else if (Y.Lang.isObject(node2)) {
1183
var off = getOffsets(region, r);
1189
area: ((off.bottom - off.top) * (off.right - off.left)),
1190
yoff: ((off.bottom - off.top)),
1191
xoff: (off.right - off.left),
1192
inRegion: Y.DOM.inRegion(node, node2, false, altRegion)
1197
* Check if any part of this node is in the passed region
1199
* @param {Object} node2 The node to get the region from or an Object literal of the region
1200
* $param {Boolean} all Should all of the node be inside the region
1201
* @param {Object} altRegion An object literal containing the region for this node if we already have the data (for performance i.e. DragDrop)
1202
* @return {Boolean} True if in region, false if not.
1204
inRegion: function(node, node2, all, altRegion) {
1206
r = altRegion || Y.DOM.region(node);
1210
region = Y.DOM.region(n);
1211
} else if (Y.Lang.isObject(node2)) {
1218
return ( r.left >= region.left &&
1219
r.right <= region.right &&
1220
r.top >= region.top &&
1221
r.bottom <= region.bottom );
1223
var off = getOffsets(region, r);
1224
if (off.bottom >= off.top && off.right >= off.left) {
1234
* Check if any part of this element is in the viewport
1235
* @method inViewportRegion
1236
* @param {HTMLElement} element The DOM element.
1237
* @param {Boolean} all Should all of the node be inside the region
1238
* @param {Object} altRegion An object literal containing the region for this node if we already have the data (for performance i.e. DragDrop)
1239
* @return {Boolean} True if in region, false if not.
1241
inViewportRegion: function(node, all, altRegion) {
1242
return Y.DOM.inRegion(node, Y.DOM.viewportRegion(node), all, altRegion);
1247
* Returns an Object literal containing the following about the visible region of viewport: (top, right, bottom, left)
1248
* @method viewportRegion
1249
@return {Object} Object literal containing the following about the visible region of the viewport: (top, right, bottom, left)
1251
viewportRegion: function(node) {
1252
node = node || Y.config.doc.documentElement;
1254
top: Y.DOM.docScrollY(node),
1255
right: Y.DOM.winWidth(node) + Y.DOM.docScrollX(node),
1256
bottom: (Y.DOM.docScrollY(node) + Y.DOM.winHeight(node)),
1257
left: Y.DOM.docScrollX(node)
1264
* Add style management functionality to DOM.
1266
* @submodule dom-style
1270
var CLIENT_TOP = 'clientTop',
1271
CLIENT_LEFT = 'clientLeft',
1272
PARENT_NODE = 'parentNode',
1274
HAS_LAYOUT = 'hasLayout',
1277
FILTERS = 'filters',
1278
OPACITY = 'opacity',
1280
CURRENT_STYLE = 'currentStyle';
1282
// use alpha filter for IE opacity
1283
if (document[DOCUMENT_ELEMENT][STYLE][OPACITY] === UNDEFINED &&
1284
document[DOCUMENT_ELEMENT][FILTERS]) {
1285
Y.DOM.CUSTOM_STYLES[OPACITY] = {
1286
get: function(node) {
1288
try { // will error if no DXImageTransform
1289
val = node[FILTERS]['DXImageTransform.Microsoft.Alpha'][OPACITY];
1292
try { // make sure its in the document
1293
val = node[FILTERS]('alpha')[OPACITY];
1300
set: function(node, val, style) {
1301
if (typeof style[FILTER] == 'string') { // in case not appended
1302
style[FILTER] = 'alpha(' + OPACITY + '=' + val * 100 + ')';
1304
if (!node[CURRENT_STYLE] || !node[CURRENT_STYLE][HAS_LAYOUT]) {
1305
style.zoom = 1; // needs layout
1312
// IE getComputedStyle
1313
// TODO: unit-less lineHeight (e.g. 1.22)
1314
var re_size = /^width|height$/,
1315
re_unit = /^(\d[.\d]*)+(em|ex|px|gd|rem|vw|vh|vm|ch|mm|cm|in|pt|pc|deg|rad|ms|s|hz|khz|%){1}?/i;
1317
var ComputedStyle = {
1320
get: function(el, property) {
1322
current = el[CURRENT_STYLE][property];
1324
if (property === OPACITY) {
1325
value = Y.DOM.CUSTOM_STYLES[OPACITY].get(el);
1326
} else if (!current || current.indexOf(PX) > -1) { // no need to convert
1328
} else if (Y.DOM.IE.COMPUTED[property]) { // use compute function
1329
value = Y.DOM.IE.COMPUTED[property](el, property);
1330
} else if (re_unit.test(current)) { // convert to pixel
1331
value = Y.DOM.IE.ComputedStyle.getPixel(el, property);
1339
getOffset: function(el, prop) {
1340
var current = el[CURRENT_STYLE][prop], // value of "width", "top", etc.
1341
capped = prop.charAt(0).toUpperCase() + prop.substr(1), // "Width", "Top", etc.
1342
offset = 'offset' + capped, // "offsetWidth", "offsetTop", etc.
1343
pixel = 'pixel' + capped, // "pixelWidth", "pixelTop", etc.
1346
if (current == AUTO) {
1347
var actual = el[offset]; // offsetHeight/Top etc.
1348
if (actual === UNDEFINED) { // likely "right" or "bottom"
1353
if (re_size.test(prop)) { // account for box model diff
1354
el[STYLE][prop] = actual;
1355
if (el[offset] > actual) {
1356
// the difference is padding + border (works in Standards & Quirks modes)
1357
value = actual - (el[offset] - actual);
1359
el[STYLE][prop] = AUTO; // revert to auto
1361
} else { // convert units to px
1362
if (!el[STYLE][pixel] && !el[STYLE][prop]) { // need to map style.width to currentStyle (no currentStyle.pixelWidth)
1363
el[STYLE][prop] = current; // no style.pixelWidth if no style.width
1365
value = el[STYLE][pixel];
1370
getBorderWidth: function(el, property) {
1371
// clientHeight/Width = paddingBox (e.g. offsetWidth - borderWidth)
1372
// clientTop/Left = borderWidth
1374
if (!el[CURRENT_STYLE][HAS_LAYOUT]) { // TODO: unset layout?
1375
el[STYLE].zoom = 1; // need layout to measure client
1379
case BORDER_TOP_WIDTH:
1380
value = el[CLIENT_TOP];
1382
case BORDER_BOTTOM_WIDTH:
1383
value = el.offsetHeight - el.clientHeight - el[CLIENT_TOP];
1385
case BORDER_LEFT_WIDTH:
1386
value = el[CLIENT_LEFT];
1388
case BORDER_RIGHT_WIDTH:
1389
value = el.offsetWidth - el.clientWidth - el[CLIENT_LEFT];
1395
getPixel: function(node, att) {
1396
// use pixelRight to convert to px
1398
styleRight = node[CURRENT_STYLE][RIGHT],
1399
current = node[CURRENT_STYLE][att];
1401
node[STYLE][RIGHT] = current;
1402
val = node[STYLE].pixelRight;
1403
node[STYLE][RIGHT] = styleRight; // revert
1408
getMargin: function(node, att) {
1410
if (node[CURRENT_STYLE][att] == AUTO) {
1413
val = Y.DOM.IE.ComputedStyle.getPixel(node, att);
1418
getVisibility: function(node, att) {
1420
while ( (current = node[CURRENT_STYLE]) && current[att] == 'inherit') { // NOTE: assignment in test
1421
node = node[PARENT_NODE];
1423
return (current) ? current[att] : VISIBLE;
1426
getColor: function(node, att) {
1427
var current = node[CURRENT_STYLE][att];
1429
if (!current || current === TRANSPARENT) {
1430
Y.DOM.elementByAxis(node, PARENT_NODE, null, function(parent) {
1431
current = parent[CURRENT_STYLE][att];
1432
if (current && current !== TRANSPARENT) {
1439
return Y.Color.toRGB(current);
1442
getBorderColor: function(node, att) {
1443
var current = node[CURRENT_STYLE];
1444
var val = current[att] || current.color;
1445
return Y.Color.toRGB(Y.Color.toHex(val));
1450
//fontSize: getPixelFont,
1451
var IEComputed = {};
1453
IEComputed[WIDTH] = IEComputed[HEIGHT] = ComputedStyle.getOffset;
1455
IEComputed.color = IEComputed.backgroundColor = ComputedStyle.getColor;
1457
IEComputed[BORDER_TOP_WIDTH] = IEComputed[BORDER_RIGHT_WIDTH] =
1458
IEComputed[BORDER_BOTTOM_WIDTH] = IEComputed[BORDER_LEFT_WIDTH] =
1459
ComputedStyle.getBorderWidth;
1461
IEComputed.marginTop = IEComputed.marginRight = IEComputed.marginBottom =
1462
IEComputed.marginLeft = ComputedStyle.getMargin;
1464
IEComputed.visibility = ComputedStyle.getVisibility;
1465
IEComputed.borderColor = IEComputed.borderTopColor =
1466
IEComputed.borderRightColor = IEComputed.borderBottomColor =
1467
IEComputed.borderLeftColor = ComputedStyle.getBorderColor;
1469
if (!Y.config.win[GET_COMPUTED_STYLE]) {
1470
Y.DOM[GET_COMPUTED_STYLE] = ComputedStyle.get;
1473
Y.namespace('DOM.IE');
1474
Y.DOM.IE.COMPUTED = IEComputed;
1475
Y.DOM.IE.ComputedStyle = ComputedStyle;
1478
* Provides helper methods for collecting and filtering DOM elements.
1480
* @submodule selector
1484
* Provides helper methods for collecting and filtering DOM elements.
1490
PARENT_NODE = 'parentNode',
1491
PREVIOUS_SIBLING = 'previousSibling',
1493
NODE_TYPE = 'nodeType',
1494
TAG_NAME = 'tagName',
1495
ATTRIBUTES = 'attributes',
1496
PSEUDOS = 'pseudos',
1497
COMBINATOR = 'combinator';
1499
var reNth = /^(?:([\-]?\d*)(n){1}|(odd|even)$)*([\-+]?\d*)$/;
1502
tag: /^((?:-?[_a-z]+[\w\-]*)|\*)/i,
1503
attributes: /^\[([a-z]+\w*)+([~\|\^\$\*!=]=?)?['"]?([^\]]*?)['"]?\]/i,
1504
pseudos: /^:([\-\w]+)(?:\(['"]?(.+)['"]?\))*/i,
1505
combinator: /^\s*([>+~]|\s)\s*/
1510
* Default document for use queries
1511
* @property document
1513
* @default window.document
1515
document: Y.config.doc,
1517
* Mapping of attributes to aliases, normally to work around HTMLAttributes
1518
* that conflict with JS reserved words.
1519
* @property attrAliases
1525
* Mapping of shorthand tokens to corresponding attribute selector
1526
* @property shorthand
1530
'\\#(-?[_a-z]+[-\\w]*)': '[id=$1]',
1531
'\\.(-?[_a-z]+[-\\w]*)': '[class~=$1]'
1535
* List of operators and corresponding boolean functions.
1536
* These functions are passed the attribute and the current node's value of the attribute.
1537
* @property operators
1541
'=': function(attr, val) { return attr === val; }, // Equality
1542
'!=': function(attr, val) { return attr !== val; }, // Inequality
1543
'~=': function(attr, val) { // Match one of space seperated words
1545
return (s + attr + s).indexOf((s + val + s)) > -1;
1547
'|=': function(attr, val) { return Y.DOM._getRegExp('^' + val + '[-]?').test(attr); }, // Match start with value followed by optional hyphen
1548
'^=': function(attr, val) { return attr.indexOf(val) === 0; }, // Match starts with value
1549
'$=': function(attr, val) { return attr.lastIndexOf(val) === attr[LENGTH] - val[LENGTH]; }, // Match ends with value
1550
'*=': function(attr, val) { return attr.indexOf(val) > -1; }, // Match contains value as substring
1551
'': function(attr, val) { return attr; } // Just test for existence of attribute
1555
* List of pseudo-classes and corresponding boolean functions.
1556
* These functions are called with the current node, and any value that was parsed with the pseudo regex.
1561
'root': function(node) {
1562
return node === node.ownerDocument.documentElement;
1565
'nth-child': function(node, val) {
1566
return Selector.getNth(node, val);
1569
'nth-last-child': function(node, val) {
1570
return Selector.getNth(node, val, null, true);
1573
'nth-of-type': function(node, val) {
1574
return Selector.getNth(node, val, node[TAG_NAME]);
1577
'nth-last-of-type': function(node, val) {
1578
return Selector.getNth(node, val, node[TAG_NAME], true);
1581
'first-child': function(node) {
1582
return Y.DOM.firstChild(node[PARENT_NODE]) === node;
1585
'last-child': function(node) {
1586
return Y.DOM.lastChild(node[PARENT_NODE]) === node;
1589
'first-of-type': function(node, val) {
1590
return Y.DOM.firstChildByTag(node[PARENT_NODE], node[TAG_NAME]) === node;
1593
'last-of-type': function(node, val) {
1594
return Y.DOM.lastChildByTag(node[PARENT_NODE], node[TAG_NAME]) === node;
1597
'only-child': function(node) {
1598
var children = Y.DOM.children(node[PARENT_NODE]);
1599
return children[LENGTH] === 1 && children[0] === node;
1602
'only-of-type': function(node) {
1603
return Y.DOM.childrenByTag(node[PARENT_NODE], node[TAG_NAME])[LENGTH] === 1;
1606
'empty': function(node) {
1607
return node.childNodes[LENGTH] === 0;
1610
'not': function(node, simple) {
1611
return !Selector.test(node, simple);
1614
'contains': function(node, str) {
1615
var text = node.innerText || node.textContent || '';
1616
return text.indexOf(str) > -1;
1618
'checked': function(node) {
1619
return node.checked === true;
1624
* Test if the supplied node matches the supplied selector.
1627
* @param {HTMLElement | String} node An id or node reference to the HTMLElement being tested.
1628
* @param {string} selector The CSS Selector to test the node against.
1629
* @return{boolean} Whether or not the node matches the selector.
1633
test: function(node, selector) {
1638
var groups = selector ? selector.split(',') : [];
1639
if (groups[LENGTH]) {
1640
for (var i = 0, len = groups[LENGTH]; i < len; ++i) {
1641
if ( Selector._testNode(node, groups[i]) ) { // passes if ANY group matches
1647
return Selector._testNode(node, selector);
1651
* Filters a set of nodes based on a given CSS selector.
1654
* @param {array} nodes A set of nodes/ids to filter.
1655
* @param {string} selector The selector used to test each node.
1656
* @return{array} An array of nodes from the supplied array that match the given selector.
1659
filter: function(nodes, selector) {
1660
nodes = nodes || [];
1662
var result = Selector._filter(nodes, Selector._tokenize(selector)[0]);
1667
* Retrieves a set of nodes based on a given CSS selector.
1670
* @param {string} selector The CSS Selector to test the node against.
1671
* @param {HTMLElement | String} root optional An id or HTMLElement to start the query from. Defaults to Selector.document.
1672
* @param {Boolean} firstOnly optional Whether or not to return only the first match.
1673
* @return {Array} An array of nodes that match the given selector.
1676
query: function(selector, root, firstOnly) {
1677
var result = Selector._query(selector, root, firstOnly);
1681
_query: function(selector, root, firstOnly, deDupe) {
1682
var result = (firstOnly) ? null : [];
1687
root = root || Selector.document;
1688
var groups = selector.split(','); // TODO: handle comma in attribute/pseudo
1690
if (groups[LENGTH] > 1) {
1692
for (var i = 0, len = groups[LENGTH]; i < len; ++i) {
1693
found = arguments.callee(groups[i], root, firstOnly, true);
1694
result = firstOnly ? found : result.concat(found);
1696
Selector._clearFoundCache();
1700
var tokens = Selector._tokenize(selector);
1701
var idToken = tokens[Selector._getIdTokenIndex(tokens)],
1705
token = tokens.pop() || {};
1708
id = Selector._getId(idToken[ATTRIBUTES]);
1711
// use id shortcut when possible
1713
node = Selector.document.getElementById(id);
1715
if (node && (root[NODE_TYPE] === 9 || Y.DOM.contains(root, node))) {
1716
if ( Selector._testNode(node, null, idToken) ) {
1717
if (idToken === token) {
1718
nodes = [node]; // simple selector
1720
root = node; // start from here
1728
if (root && !nodes[LENGTH]) {
1729
nodes = root.getElementsByTagName(token[TAG]);
1732
if (nodes[LENGTH]) {
1733
result = Selector._filter(nodes, token, firstOnly, deDupe);
1738
_filter: function(nodes, token, firstOnly, deDupe) {
1739
var result = firstOnly ? null : [];
1741
result = Y.DOM.filterElementsBy(nodes, function(node) {
1742
if (! Selector._testNode(node, '', token, deDupe)) {
1751
Selector._foundCache[Selector._foundCache[LENGTH]] = node;
1759
_testNode: function(node, selector, token, deDupe) {
1760
token = token || Selector._tokenize(selector).pop() || {};
1761
var ops = Selector.operators,
1762
pseudos = Selector.pseudos,
1763
prev = token.previous,
1766
if (!node[TAG_NAME] ||
1767
(token[TAG] !== '*' && node[TAG_NAME].toUpperCase() !== token[TAG]) ||
1768
(deDupe && node._found) ) {
1772
if (token[ATTRIBUTES][LENGTH]) {
1774
for (i = 0, len = token[ATTRIBUTES][LENGTH]; i < len; ++i) {
1775
attribute = node.getAttribute(token[ATTRIBUTES][i][0], 2);
1776
if (attribute === undefined) {
1777
attribute = node[token[ATTRIBUTES][i][0]];
1778
if (attribute === undefined) {
1782
if ( ops[token[ATTRIBUTES][i][1]] &&
1783
!ops[token[ATTRIBUTES][i][1]](attribute, token[ATTRIBUTES][i][2])) {
1789
if (token[PSEUDOS][LENGTH]) {
1790
for (i = 0, len = token[PSEUDOS][LENGTH]; i < len; ++i) {
1791
if (pseudos[token[PSEUDOS][i][0]] &&
1792
!pseudos[token[PSEUDOS][i][0]](node, token[PSEUDOS][i][1])) {
1797
return (prev && prev[COMBINATOR] !== ',') ?
1798
Selector.combinators[prev[COMBINATOR]](node, token) :
1806
_clearFoundCache: function() {
1807
for (var i = 0, len = Selector._foundCache[LENGTH]; i < len; ++i) {
1808
try { // IE no like delete
1809
delete Selector._foundCache[i]._found;
1811
Selector._foundCache[i].removeAttribute('_found');
1814
Selector._foundCache = [];
1818
' ': function(node, token) {
1819
while ((node = node[PARENT_NODE])) {
1820
if (Selector._testNode(node, '', token.previous)) {
1827
'>': function(node, token) {
1828
return Selector._testNode(node[PARENT_NODE], null, token.previous);
1830
'+': function(node, token) {
1831
var sib = node[PREVIOUS_SIBLING];
1832
while (sib && sib[NODE_TYPE] !== 1) {
1833
sib = sib[PREVIOUS_SIBLING];
1836
if (sib && Selector._testNode(sib, null, token.previous)) {
1842
'~': function(node, token) {
1843
var sib = node[PREVIOUS_SIBLING];
1845
if (sib[NODE_TYPE] === 1 && Selector._testNode(sib, null, token.previous)) {
1848
sib = sib[PREVIOUS_SIBLING];
1857
an+b = get every _a_th node starting at the _b_th
1858
0n+b = no repeat ("0" and "n" may both be omitted (together) , e.g. "0n+1" or "1", not "0+1"), return only the _b_th element
1859
1n+b = get every element starting from b ("1" may may be omitted, e.g. "1n+0" or "n+0" or "n")
1860
an+0 = get every _a_th element, "0" may be omitted
1862
getNth: function(node, expr, tag, reverse) {
1865
var a = parseInt(RegExp.$1, 10), // include every _a_ elements (zero means no repeat, just first _a_)
1866
n = RegExp.$2, // "n"
1867
oddeven = RegExp.$3, // "odd" or "even"
1868
b = parseInt(RegExp.$4, 10) || 0, // start scan from element _b_
1869
op, i, len, siblings;
1872
siblings = Y.DOM.childrenByTag(node[PARENT_NODE], tag);
1874
siblings = Y.DOM.children(node[PARENT_NODE]);
1878
a = 2; // always every other
1881
b = (oddeven === 'odd') ? 1 : 0;
1882
} else if ( isNaN(a) ) {
1883
a = (n) ? 1 : 0; // start from the first or no repeat
1886
if (a === 0) { // just the first
1888
b = siblings[LENGTH] - b + 1;
1891
if (siblings[b - 1] === node) {
1898
reverse = !!reverse;
1903
for (i = b - 1, len = siblings[LENGTH]; i < len; i += a) {
1904
if ( i >= 0 && siblings[i] === node ) {
1909
for (i = siblings[LENGTH] - b, len = siblings[LENGTH]; i >= 0; i -= a) {
1910
if ( i < len && siblings[i] === node ) {
1918
_getId: function(attr) {
1919
for (var i = 0, len = attr[LENGTH]; i < len; ++i) {
1920
if (attr[i][0] == 'id' && attr[i][1] === '=') {
1926
_getIdTokenIndex: function(tokens) {
1927
for (var i = 0, len = tokens[LENGTH]; i < len; ++i) {
1928
if (Selector._getId(tokens[i][ATTRIBUTES])) {
1936
Break selector into token units per simple selector.
1937
Combinator is attached to left-hand selector.
1939
_tokenize: function(selector) {
1940
var token = {}, // one token per simple selector (left selector holds combinator)
1941
tokens = [], // array of tokens
1942
found = false, // whether or not any matches were found this pass
1943
match; // the regex match
1945
selector = Selector._replaceShorthand(selector); // convert ID and CLASS shortcuts to attributes
1948
Search for selector patterns, store, and strip them from the selector string
1949
until no patterns match (invalid selector) or we run out of chars.
1951
Multiple attributes and pseudos are allowed, in any order.
1953
'form:first-child[type=button]:not(button)[lang|=en]'
1956
found = false; // reset after full pass
1957
for (var re in patterns) {
1958
if (patterns.hasOwnProperty(re)) {
1959
if (re != TAG && re != COMBINATOR) { // only one allowed
1960
token[re] = token[re] || [];
1962
if ((match = patterns[re].exec(selector))) { // note assignment
1964
if (re != TAG && re != COMBINATOR) { // only one allowed
1965
//token[re] = token[re] || [];
1967
// capture ID for fast path to element
1968
if (re === ATTRIBUTES && match[1] === 'id') {
1969
token.id = match[3];
1972
token[re].push(match.slice(1));
1973
} else { // single selector (tag, combinator)
1974
token[re] = match[1];
1976
selector = selector.replace(match[0], ''); // strip current match from selector
1977
if (re === COMBINATOR || !selector[LENGTH]) { // next token or done
1978
token[ATTRIBUTES] = Selector._fixAttributes(token[ATTRIBUTES]);
1979
token[PSEUDOS] = token[PSEUDOS] || [];
1980
token[TAG] = token[TAG] ? token[TAG].toUpperCase() : '*';
1983
token = { // prep next token
1995
_fixAttributes: function(attr) {
1996
var aliases = Selector.attrAliases;
1998
for (var i = 0, len = attr[LENGTH]; i < len; ++i) {
1999
if (aliases[attr[i][0]]) { // convert reserved words, etc
2000
attr[i][0] = aliases[attr[i][0]];
2002
if (!attr[i][1]) { // use exists operator
2009
_replaceShorthand: function(selector) {
2010
var shorthand = Selector.shorthand;
2011
var attrs = selector.match(patterns[ATTRIBUTES]); // pull attributes to avoid false pos on "." and "#"
2013
selector = selector.replace(patterns[ATTRIBUTES], 'REPLACED_ATTRIBUTE');
2015
for (var re in shorthand) {
2016
if (shorthand.hasOwnProperty(re)) {
2017
selector = selector.replace(Y.DOM._getRegExp(re, 'gi'), shorthand[re]);
2022
for (var i = 0, len = attrs[LENGTH]; i < len; ++i) {
2023
selector = selector.replace('REPLACED_ATTRIBUTE', attrs[i]);
2031
if (Y.UA.ie) { // rewrite class for IE (others use getAttribute('class')
2032
Selector.attrAliases['class'] = 'className';
2033
Selector.attrAliases['for'] = 'htmlFor';
2036
Y.Selector = Selector;
2037
Y.Selector.patterns = patterns;
2040
* Add style management functionality to DOM.
2042
* @submodule dom-style
2046
var TO_STRING = 'toString',
2047
PARSE_INT = parseInt,
2070
re_RGB: /^rgb\(([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\)$/i,
2071
re_hex: /^#?([0-9A-F]{2})([0-9A-F]{2})([0-9A-F]{2})$/i,
2072
re_hex3: /([0-9A-F])/gi,
2074
toRGB: function(val) {
2075
if (!Y.Color.re_RGB.test(val)) {
2076
val = Y.Color.toHex(val);
2079
if(Y.Color.re_hex.exec(val)) {
2081
PARSE_INT(RE.$1, 16),
2082
PARSE_INT(RE.$2, 16),
2083
PARSE_INT(RE.$3, 16)
2089
toHex: function(val) {
2090
val = Y.Color.KEYWORDS[val] || val;
2091
if (Y.Color.re_RGB.exec(val)) {
2092
var r = (RE.$1.length === 1) ? '0' + RE.$1 : Number(RE.$1),
2093
g = (RE.$2.length === 1) ? '0' + RE.$2 : Number(RE.$2),
2094
b = (RE.$3.length === 1) ? '0' + RE.$3 : Number(RE.$3);
2103
if (val.length < 6) {
2104
val = val.replace(Y.Color.re_hex3, '$1$1');
2107
if (val !== 'transparent' && val.indexOf('#') < 0) {
2111
return val.toLowerCase();
2117
}, '3.0.0pr1' ,{skinnable:false});