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 {String} tag The tag to search for.
94
* @param {Function} fn optional An optional boolean test to apply.
95
* The optional function is passed the current HTMLElement being tested as its only argument.
96
* If no function is given, the first found is returned.
97
* @return {HTMLElement | null} The first matching child html element.
99
lastChild: function(element, fn) {
100
return Y.DOM._childBy(element, null, fn, true);
103
lastChildByTag: function(element, tag, fn) {
104
return Y.DOM._childBy(element, tag, fn, true);
108
* Finds all HTMLElement childNodes matching the given tag.
109
* @method childrenByTag
110
* @param {HTMLElement} element The html element.
111
* @param {String} tag The tag to search for.
112
* @param {Function} fn optional An optional boolean test to apply.
113
* The optional function is passed the current HTMLElement being tested as its only argument.
114
* If no function is given, all children with the given tag are collected.
115
* @return {Array} The collection of child elements.
117
childrenByTag: function() {
118
if (document[DOCUMENT_ELEMENT].children) {
119
return function(element, tag, fn, toArray) { // TODO: keep toArray option?
120
tag = (tag && tag !== '*') ? tag : null;
123
elements = (tag) ? element.children.tags(tag) : element.children;
126
elements = Y.DOM.filterElementsBy(elements, fn);
133
return function(element, tag, fn) {
134
tag = (tag && tag !== '*') ? tag.toUpperCase() : null;
139
elements = element.childNodes;
140
if (tag) { // wrap fn and add tag test TODO: allow tag in filterElementsBy?
141
wrapFn = function(el) {
142
return el[TAG_NAME].toUpperCase() === tag && (!fn || fn(el));
146
elements = Y.DOM.filterElementsBy(elements, wrapFn);
154
* Finds all HTMLElement childNodes.
156
* @param {HTMLElement} element The html element.
157
* @param {Function} fn optional An optional boolean test to apply.
158
* The optional function is passed the current HTMLElement being tested as its only argument.
159
* If no function is given, all children are collected.
160
* @return {Array} The collection of child elements.
162
children: function(element, fn) {
163
return Y.DOM.childrenByTag(element, null, fn);
167
* Finds the previous sibling of the element.
169
* @param {HTMLElement} element The html element.
170
* @param {Function} fn optional An optional boolean test to apply.
171
* The optional function is passed the current DOM node being tested as its only argument.
172
* If no function is given, the first sibling is returned.
173
* @param {Boolean} all optional Whether all node types should be scanned, or just element nodes.
174
* @return {HTMLElement | null} The matching DOM node or null if none found.
176
previous: function(element, fn) {
177
return Y.DOM.elementByAxis(element, PREVIOUS_SIBLING, fn);
181
* Finds the next sibling of the element.
183
* @param {HTMLElement} element The html element.
184
* @param {Function} fn optional An optional boolean test to apply.
185
* The optional function is passed the current DOM node being tested as its only argument.
186
* If no function is given, the first sibling is returned.
187
* @param {Boolean} all optional Whether all node types should be scanned, or just element nodes.
188
* @return {HTMLElement | null} The matching DOM node or null if none found.
190
next: function(element, fn) {
191
return Y.DOM.elementByAxis(element, NEXT_SIBLING, fn);
195
* Finds the ancestor of the element.
197
* @param {HTMLElement} element The html element.
198
* @param {Function} fn optional An optional boolean test to apply.
199
* The optional function is passed the current DOM node being tested as its only argument.
200
* If no function is given, the parentNode is returned.
201
* @param {Boolean} all optional Whether all node types should be scanned, or just element nodes.
202
* @return {HTMLElement | null} The matching DOM node or null if none found.
204
ancestor: function(element, fn) {
205
return Y.DOM.elementByAxis(element, PARENT_NODE, fn);
209
* Searches the element by the given axis for the first matching element.
210
* @method elementByAxis
211
* @param {HTMLElement} element The html element.
212
* @param {String} axis The axis to search (parentNode, nextSibling, previousSibling).
213
* @param {Function} fn optional An optional boolean test to apply.
214
* @param {Boolean} all optional Whether all node types should be returned, or just element nodes.
215
* The optional function is passed the current HTMLElement being tested as its only argument.
216
* If no function is given, the first element is returned.
217
* @return {HTMLElement | null} The matching element or null if none found.
219
elementByAxis: function(element, axis, fn, all) {
220
while (element && (element = element[axis])) { // NOTE: assignment
221
if ( (all || element[TAG_NAME]) && (!fn || fn(element)) ) {
229
* Finds all elements with the given tag.
231
* @param {String} tag The tag being search for.
232
* @param {HTMLElement} root optional An optional root element to start from.
233
* @param {Function} fn optional An optional boolean test to apply.
234
* The optional function is passed the current HTMLElement being tested as its only argument.
235
* If no function is given, all elements with the given tag are returned.
236
* @return {Array} The collection of matching elements.
238
byTag: function(tag, root, fn) {
239
root = root || Y.config.doc;
241
var elements = root.getElementsByTagName(tag),
244
for (var i = 0, len = elements[LENGTH]; i < len; ++i) {
245
if ( !fn || fn(elements[i]) ) {
246
retNodes[retNodes[LENGTH]] = elements[i];
253
* Finds the first element with the given tag.
255
* @param {String} tag The tag being search for.
256
* @param {HTMLElement} root optional An optional root element to start from.
257
* @param {Function} fn optional An optional boolean test to apply.
258
* The optional function is passed the current HTMLElement being tested as its only argument.
259
* If no function is given, the first match is returned.
260
* @return {HTMLElement} The matching element.
262
firstByTag: function(tag, root, fn) {
263
root = root || Y.config.doc;
265
var elements = root.getElementsByTagName(tag),
268
for (var i = 0, len = elements[LENGTH]; i < len; ++i) {
269
if ( !fn || fn(elements[i]) ) {
278
* Filters a collection of HTMLElements by the given attributes.
279
* @method filterElementsBy
280
* @param {Array} elements The collection of HTMLElements to filter.
281
* @param {Function} fn A boolean test to apply.
282
* The function is passed the current HTMLElement being tested as its only argument.
283
* If no function is given, all HTMLElements are kept.
284
* @return {Array} The filtered collection of HTMLElements.
286
filterElementsBy: function(elements, fn, firstOnly) {
287
var ret = (firstOnly) ? null : [];
288
for (var i = 0, len = elements[LENGTH]; i < len; ++i) {
289
if (elements[i][TAG_NAME] && (!fn || fn(elements[i]))) {
294
ret[ret[LENGTH]] = elements[i];
303
* Determines whether or not one HTMLElement is or contains another HTMLElement.
305
* @param {HTMLElement} element The containing html element.
306
* @param {HTMLElement} needle The html element that may be contained.
307
* @return {Boolean} Whether or not the element is or contains the needle.
309
contains: function(element, needle) {
312
if ( !needle || !element || !needle[NODE_TYPE] || !element[NODE_TYPE]) {
314
} else if (element[CONTAINS]) {
315
if (Y.UA.opera || needle[NODE_TYPE] === 1) { // IE & SAF contains fail if needle not an ELEMENT_NODE
316
ret = element[CONTAINS](needle);
318
ret = Y.DOM._bruteContains(element, needle);
320
} else if (element[COMPARE_DOCUMENT_POSITION]) { // gecko
321
if (element === needle || !!(element[COMPARE_DOCUMENT_POSITION](needle) & 16)) {
330
* Determines whether or not the HTMLElement is part of the document.
332
* @param {HTMLElement} element The containing html element.
333
* @param {HTMLElement} doc optional The document to check.
334
* @return {Boolean} Whether or not the element is attached to the document.
336
inDoc: function(element, doc) {
337
doc = doc || Y.config.doc;
338
return Y.DOM.contains(doc.documentElement, element);
341
create: function(html, doc) {
342
doc = doc || Y.config.doc;
343
var m = re_tag.exec(html);
344
var create = Y.DOM._create,
345
custom = Y.DOM.creators,
348
if (m && custom[m[1]]) {
349
if (typeof custom[m[1]] === 'function') {
350
create = custom[m[1]];
355
ret = create(html, doc, tag);
356
return (ret.childNodes.length > 1) ? ret.childNodes : ret.childNodes[0]; // collection or item
357
//return ret.firstChild;
360
_create: function(html, doc, tag) {
362
var frag = templateCache[tag] || doc.createElement(tag);
363
frag.innerHTML = Y.Lang.trim(html);
368
* Brute force version of contains.
369
* Used for browsers without contains support for non-HTMLElement Nodes (textNodes, etc).
370
* @method _bruteContains
372
* @param {HTMLElement} element The containing html element.
373
* @param {HTMLElement} needle The html element that may be contained.
374
* @return {Boolean} Whether or not the element is or contains the needle.
376
_bruteContains: function(element, needle) {
378
if (element === needle) {
381
needle = needle.parentNode;
387
* Memoizes dynamic regular expressions to boost runtime performance.
390
* @param {String} str The string to convert to a regular expression.
391
* @param {String} flags optional An optinal string of flags.
392
* @return {RegExp} An instance of RegExp
394
_getRegExp: function(str, flags) {
396
Y.DOM._regexCache = Y.DOM._regexCache || {};
397
if (!Y.DOM._regexCache[str + flags]) {
398
Y.DOM._regexCache[str + flags] = new RegExp(str, flags);
400
return Y.DOM._regexCache[str + flags];
404
* returns the appropriate document.
407
* @param {HTMLElement} element optional Target element.
408
* @return {Object} The document for the given element or the default document.
410
_getDoc: function(element) {
411
element = element || {};
412
return (element[NODE_TYPE] === 9) ? element : element[OWNER_DOCUMENT] ||
417
* returns the appropriate window.
420
* @param {HTMLElement} element optional Target element.
421
* @return {Object} The window for the given element or the default window.
423
_getWin: function(element) {
424
var doc = Y.DOM._getDoc(element);
425
return (element.document) ? element : doc[DEFAULT_VIEW] ||
426
doc[PARENT_WINDOW] || Y.config.win;
429
_childBy: function(element, tag, fn, rev) {
435
root = element[LAST_CHILD];
436
axis = PREVIOUS_SIBLING;
438
root = element[FIRST_CHILD];
442
if (Y.DOM._testElement(root, tag, fn)) { // is the matching element
444
} else { // need to scan nextSibling axis of firstChild to find matching element
445
ret = Y.DOM.elementByAxis(root, axis, fn);
452
_testElement: function(element, tag, fn) {
453
tag = (tag && tag !== '*') ? tag.toUpperCase() : null;
454
return (element && element[TAG_NAME] &&
455
(!tag || element[TAG_NAME].toUpperCase() === tag) &&
456
(!fn || fn(element)));
461
_IESimpleCreate: function(html, doc) {
462
doc = doc || Y.config.doc;
463
return doc.createElement(html);
469
var creators = Y.DOM.creators,
470
create = Y.DOM.create,
471
re_tbody = /(?:\/(?:thead|tfoot|tbody|caption|col|colgroup)>)+\s*<tbody/;
473
var TABLE_OPEN = '<table>',
474
TABLE_CLOSE = '</table>';
476
if (Y.UA.gecko || Y.UA.ie) { // require custom creation code for certain element types
478
option: function(html, doc) {
479
var frag = create('<select>' + html + '</select>');
483
tr: function(html, doc) {
484
var frag = creators.tbody('<tbody>' + html + '</tbody>', doc);
485
return frag.firstChild;
488
td: function(html, doc) {
489
var frag = creators.tr('<tr>' + html + '</tr>', doc);
490
return frag.firstChild;
493
tbody: function(html, doc) {
494
var frag = create(TABLE_OPEN + html + TABLE_CLOSE, doc);
501
creators.col = creators.tbody; // IE wraps in colgroup
505
// TODO: allow multiples ("<link><link>")
506
creators.col = creators.script = creators.link = Y.DOM._IESimpleCreate;
508
// TODO: thead/tfoot with nested tbody
509
creators.tbody = function(html, doc) {
510
var frag = create(TABLE_OPEN + html + TABLE_CLOSE, doc);
511
var tb = frag.children.tags('tbody')[0];
512
if (frag.children.length > 1 && tb && !re_tbody.test(html)) {
513
tb.parentNode.removeChild(tb);
520
if (Y.UA.gecko || Y.UA.ie) { // require custom creation code for certain element types
523
thead: creators.tbody,
524
tfoot: creators.tbody,
525
caption: creators.tbody,
526
colgroup: creators.tbody,
528
optgroup: creators.option
534
* The DOM utility provides a cross-browser abtraction layer
535
* normalizing DOM tasks, and adds extra helper functionality
536
* for other common tasks.
538
* @submodule dom-base
542
var CLASS_NAME = 'className';
546
* Determines whether a DOM element has the given className.
548
* @param {HTMLElement} element The DOM element.
549
* @param {String} className the class name to search for
550
* @return {Boolean} Whether or not the element has the given class.
552
hasClass: function(node, className) {
553
var re = Y.DOM._getRegExp('(?:^|\\s+)' + className + '(?:\\s+|$)');
554
return re.test(node[CLASS_NAME]);
558
* Adds a class name to a given DOM element.
560
* @param {HTMLElement} element The DOM element.
561
* @param {String} className the class name to add to the class attribute
563
addClass: function(node, className) {
564
if (!Y.DOM.hasClass(node, className)) { // skip if already present
565
node[CLASS_NAME] = Y.Lang.trim([node[CLASS_NAME], className].join(' '));
570
* Removes a class name from a given element.
571
* @method removeClass
572
* @param {HTMLElement} element The DOM element.
573
* @param {String} className the class name to remove from the class attribute
575
removeClass: function(node, className) {
576
if (className && Y.DOM.hasClass(node, className)) {
577
node[CLASS_NAME] = Y.Lang.trim(node[CLASS_NAME].replace(Y.DOM._getRegExp('(?:^|\\s+)' +
578
className + '(?:\\s+|$)'), ' '));
580
if ( Y.DOM.hasClass(node, className) ) { // in case of multiple adjacent
581
Y.DOM.removeClass(node, className);
587
* Replace a class with another class for a given element.
588
* If no oldClassName is present, the newClassName is simply added.
589
* @method replaceClass
590
* @param {HTMLElement} element The DOM element.
591
* @param {String} oldClassName the class name to be replaced
592
* @param {String} newClassName the class name that will be replacing the old class name
594
replaceClass: function(node, oldC, newC) {
595
//Y.log('replaceClass replacing ' + oldC + ' with ' + newC, 'info', 'Node');
596
Y.DOM.addClass(node, newC);
597
Y.DOM.removeClass(node, oldC);
601
* If the className exists on the node it is removed, if it doesn't exist it is added.
602
* @method toggleClass
603
* @param {HTMLElement} element The DOM element.
604
* @param {String} className the class name to be toggled
606
toggleClass: function(node, className) {
607
if (Y.DOM.hasClass(node, className)) {
608
Y.DOM.removeClass(node, className);
610
Y.DOM.addClass(node, className);
617
* Add style management functionality to DOM.
619
* @submodule dom-style
623
var DOCUMENT_ELEMENT = 'documentElement',
624
DEFAULT_VIEW = 'defaultView',
625
OWNER_DOCUMENT = 'ownerDocument',
628
CSS_FLOAT = 'cssFloat',
629
STYLE_FLOAT = 'styleFloat',
630
TRANSPARENT = 'transparent',
634
BORDER_TOP_WIDTH = 'borderTopWidth',
635
BORDER_RIGHT_WIDTH = 'borderRightWidth',
636
BORDER_BOTTOM_WIDTH = 'borderBottomWidth',
637
BORDER_LEFT_WIDTH = 'borderLeftWidth',
638
GET_COMPUTED_STYLE = 'getComputedStyle',
640
DOCUMENT = Y.config.doc,
641
UNDEFINED = undefined,
643
re_color = /color$/i;
651
* Sets a style property for a given element.
653
* @param {HTMLElement} An HTMLElement to apply the style to.
654
* @param {String} att The style property to set.
655
* @param {String|Number} val The value.
657
setStyle: function(node, att, val, style) {
659
CUSTOM_STYLES = Y.DOM.CUSTOM_STYLES;
662
if (att in CUSTOM_STYLES) {
663
if (CUSTOM_STYLES[att].set) {
664
CUSTOM_STYLES[att].set(node, val, style);
665
return; // NOTE: return
666
} else if (typeof CUSTOM_STYLES[att] === 'string') {
667
att = CUSTOM_STYLES[att];
675
* Returns the current style value for the given property.
677
* @param {HTMLElement} An HTMLElement to get the style from.
678
* @param {String} att The style property to get.
680
getStyle: function(node, att) {
681
var style = node[STYLE],
682
CUSTOM_STYLES = Y.DOM.CUSTOM_STYLES,
686
if (att in CUSTOM_STYLES) {
687
if (CUSTOM_STYLES[att].get) {
688
return CUSTOM_STYLES[att].get(node, att, style); // NOTE: return
689
} else if (typeof CUSTOM_STYLES[att] === 'string') {
690
att = CUSTOM_STYLES[att];
694
if (val === '') { // TODO: is empty string sufficient?
695
val = Y.DOM[GET_COMPUTED_STYLE](node, att);
703
* Sets multiple style properties.
705
* @param {HTMLElement} node An HTMLElement to apply the styles to.
706
* @param {Object} hash An object literal of property:value pairs.
708
'setStyles': function(node, hash) {
709
Y.each(hash, function(v, n) {
710
Y.DOM.setStyle(node, n, v);
715
* Returns the computed style for the given node.
716
* @method getComputedStyle
717
* @param {HTMLElement} An HTMLElement to get the style from.
718
* @param {String} att The style property to get.
719
* @return {String} The computed value of the style property.
721
getComputedStyle: function(node, att) {
723
doc = node[OWNER_DOCUMENT];
726
val = doc[DEFAULT_VIEW][GET_COMPUTED_STYLE](node, '')[att];
732
if (DOCUMENT[DOCUMENT_ELEMENT][STYLE][CSS_FLOAT] !== UNDEFINED) {
733
Y.DOM.CUSTOM_STYLES[FLOAT] = CSS_FLOAT;
734
} else if (DOCUMENT[DOCUMENT_ELEMENT][STYLE][STYLE_FLOAT] !== UNDEFINED) {
735
Y.DOM.CUSTOM_STYLES[FLOAT] = STYLE_FLOAT;
738
if (Y.UA.opera) { // opera defaults to hex instead of RGB
739
Y.DOM[GET_COMPUTED_STYLE] = function(node, att) {
740
var view = node[OWNER_DOCUMENT][DEFAULT_VIEW],
741
val = view[GET_COMPUTED_STYLE](node, '')[att];
743
if (re_color.test(att)) {
744
val = Y.Color.toRGB(val);
752
if (Y.UA.webkit) { // safari converts transparent to rgba()
753
Y.DOM[GET_COMPUTED_STYLE] = function(node, att) {
754
var view = node[OWNER_DOCUMENT][DEFAULT_VIEW],
755
val = view[GET_COMPUTED_STYLE](node, '')[att];
757
if (val === 'rgba(0, 0, 0, 0)') {
769
* Adds position and region management functionality to DOM.
771
* @submodule dom-screen
775
var OFFSET_TOP = 'offsetTop',
777
DOCUMENT_ELEMENT = 'documentElement',
778
COMPAT_MODE = 'compatMode',
779
OFFSET_LEFT = 'offsetLeft',
780
OFFSET_PARENT = 'offsetParent',
781
POSITION = 'position',
783
RELATIVE = 'relative',
786
SCROLL_LEFT = 'scrollLeft',
787
SCROLL_TOP = 'scrollTop',
788
_BACK_COMPAT = 'BackCompat',
792
BORDER_LEFT_WIDTH = 'borderLeftWidth',
793
BORDER_TOP_WIDTH = 'borderTopWidth',
794
GET_BOUNDING_CLIENT_RECT = 'getBoundingClientRect',
795
GET_COMPUTED_STYLE = 'getComputedStyle',
796
RE_TABLE = /^t(?:able|d|h)$/i;
802
* Returns the inner height of the viewport (exludes scrollbar).
806
winHeight: function(node) {
807
var h = Y.DOM._getWinSize(node)[HEIGHT];
808
Y.log('winHeight returning ' + h, 'info', 'DOM');
813
* Returns the inner width of the viewport (exludes scrollbar).
817
winWidth: function(node) {
818
var w = Y.DOM._getWinSize(node)[WIDTH];
819
Y.log('winWidth returning ' + w, 'info', 'DOM');
828
docHeight: function(node) {
829
var h = Y.DOM._getDocSize(node)[HEIGHT];
830
Y.log('docHeight returning ' + h, 'info', 'DOM');
831
return Math.max(h, Y.DOM._getWinSize(node)[HEIGHT]);
839
docWidth: function(node) {
840
var w = Y.DOM._getDocSize(node)[WIDTH];
841
Y.log('docWidth returning ' + w, 'info', 'DOM');
842
return Math.max(w, Y.DOM._getWinSize(node)[WIDTH]);
846
* Amount page has been scroll vertically
850
docScrollX: function(node) {
851
var doc = Y.DOM._getDoc();
852
return Math.max(doc[DOCUMENT_ELEMENT][SCROLL_LEFT], doc.body[SCROLL_LEFT]);
856
* Amount page has been scroll horizontally
860
docScrollY: function(node) {
861
var doc = Y.DOM._getDoc();
862
return Math.max(doc[DOCUMENT_ELEMENT][SCROLL_TOP], doc.body[SCROLL_TOP]);
866
* Gets the current position of an element based on page coordinates.
867
* Element must be part of the DOM tree to have page coordinates
868
* (display:none or elements not appended return false).
870
* @param element The target element
871
* @return {Array} The XY position of the element
873
TODO: test inDocument/display
880
if (document[DOCUMENT_ELEMENT][GET_BOUNDING_CLIENT_RECT]) {
881
return function(node) {
885
var scrollLeft = Y.DOM.docScrollX(node),
886
scrollTop = Y.DOM.docScrollY(node),
887
box = node[GET_BOUNDING_CLIENT_RECT](),
888
doc = Y.DOM._getDoc(node),
889
//Round the numbers so we get sane data back
890
xy = [Math.floor(box[LEFT]), Math.floor(box[TOP])];
893
var off1 = 2, off2 = 2,
894
mode = doc[COMPAT_MODE],
895
bLeft = Y.DOM[GET_COMPUTED_STYLE](doc[DOCUMENT_ELEMENT], BORDER_LEFT_WIDTH),
896
bTop = Y.DOM[GET_COMPUTED_STYLE](doc[DOCUMENT_ELEMENT], BORDER_TOP_WIDTH);
899
if (mode !== _BACK_COMPAT) {
905
if ((mode == _BACK_COMPAT)) {
906
if (bLeft !== MEDIUM) {
907
off1 = parseInt(bLeft, 10);
909
if (bTop !== MEDIUM) {
910
off2 = parseInt(bTop, 10);
922
if ((scrollTop || scrollLeft)) {
927
// gecko may return sub-pixel (non-int) values
928
xy[0] = Math.floor(xy[0]);
929
xy[1] = Math.floor(xy[1]);
934
return function(node) { // manually calculate by crawling up offsetParents
935
//Calculate the Top and Left border sizes (assumes pixels)
936
var xy = [node[OFFSET_LEFT], node[OFFSET_TOP]],
938
bCheck = ((Y.UA.gecko || (Y.UA.webkit > 519)) ? true : false);
940
while ((parentNode = parentNode[OFFSET_PARENT])) {
941
xy[0] += parentNode[OFFSET_LEFT];
942
xy[1] += parentNode[OFFSET_TOP];
944
xy = Y.DOM._calcBorders(parentNode, xy);
948
// account for any scrolled ancestors
949
if (Y.DOM.getStyle(node, POSITION) != FIXED) {
951
var scrollTop, scrollLeft;
953
while ((parentNode = parentNode.parentNode)) {
954
scrollTop = parentNode[SCROLL_TOP];
955
scrollLeft = parentNode[SCROLL_LEFT];
957
//Firefox does something funky with borders when overflow is not visible.
958
if (Y.UA.gecko && (Y.DOM.getStyle(parentNode, 'overflow') !== 'visible')) {
959
xy = Y.DOM._calcBorders(parentNode, xy);
963
if (scrollTop || scrollLeft) {
968
xy[0] += Y.DOM.docScrollX(node);
969
xy[1] += Y.DOM.docScrollY(node);
972
//Fix FIXED position -- add scrollbars
974
xy[0] -= Y.DOM.docScrollX(node);
975
xy[1] -= Y.DOM.docScrollY(node);
976
} else if (Y.UA.webkit || Y.UA.gecko) {
977
xy[0] += Y.DOM.docScrollX(node);
978
xy[1] += Y.DOM.docScrollY(node);
981
//Round the numbers so we get sane data back
982
xy[0] = Math.floor(xy[0]);
983
xy[1] = Math.floor(xy[1]);
988
}(),// NOTE: Executing for loadtime branching
991
* Gets the current X position of an element based on page coordinates.
992
* Element must be part of the DOM tree to have page coordinates
993
* (display:none or elements not appended return false).
995
* @param element The target element
996
* @return {Int} The X position of the element
999
getX: function(node) {
1000
return Y.DOM.getXY(node)[0];
1004
* Gets the current Y position of an element based on page coordinates.
1005
* Element must be part of the DOM tree to have page coordinates
1006
* (display:none or elements not appended return false).
1008
* @param element The target element
1009
* @return {Int} The Y position of the element
1012
getY: function(node) {
1013
return Y.DOM.getXY(node)[1];
1017
* Set the position of an html element in page coordinates.
1018
* The element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
1020
* @param element The target element
1021
* @param {Array} xy Contains X & Y values for new position (coordinates are page-based)
1022
* @param {Boolean} noRetry By default we try and set the position a second time if the first fails
1024
setXY: function(node, xy, noRetry) {
1025
var pos = Y.DOM.getStyle(node, POSITION),
1026
setStyle = Y.DOM.setStyle,
1028
delta = [ // assuming pixels; if not we will have to retry
1029
parseInt( Y.DOM[GET_COMPUTED_STYLE](node, LEFT), 10 ),
1030
parseInt( Y.DOM[GET_COMPUTED_STYLE](node, TOP), 10 )
1033
if (pos == 'static') { // default to relative
1035
setStyle(node, POSITION, pos);
1038
var currentXY = Y.DOM.getXY(node);
1043
if (currentXY === false) { // has to be part of doc to have xy
1049
Y.log('xy failed: node not available', 'error', 'Node');
1053
if ( isNaN(delta[0]) ) {// in case of 'auto'
1054
delta[0] = (pos == RELATIVE) ? 0 : node[OFFSET_LEFT];
1056
if ( isNaN(delta[1]) ) { // in case of 'auto'
1057
delta[1] = (pos == RELATIVE) ? 0 : node[OFFSET_TOP];
1060
if (xy[0] !== null) {
1061
setStyle(node, LEFT, xy[0] - currentXY[0] + delta[0] + 'px');
1064
if (xy[1] !== null) {
1065
setStyle(node, TOP, xy[1] - currentXY[1] + delta[1] + 'px');
1069
var newXY = Y.DOM.getXY(node);
1071
// if retry is true, try one more time if we miss
1072
if ( (xy[0] !== null && newXY[0] != xy[0]) ||
1073
(xy[1] !== null && newXY[1] != xy[1]) ) {
1074
Y.DOM.setXY(node, xy, true);
1078
Y.log('setXY setting position to ' + xy, 'info', 'Node');
1082
* Set the X position of an html element in page coordinates, regardless of how the element is positioned.
1083
* The element(s) must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
1085
* @param element The target element
1086
* @param {Int} x The X values for new position (coordinates are page-based)
1088
setX: function(node, x) {
1089
return Y.DOM.setXY(node, [x, null]);
1093
* Set the Y position of an html element in page coordinates, regardless of how the element is positioned.
1094
* The element(s) must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
1096
* @param element The target element
1097
* @param {Int} y The Y values for new position (coordinates are page-based)
1099
setY: function(node, y) {
1100
return Y.DOM.setXY(node, [null, y]);
1103
_calcBorders: function(node, xy2) {
1104
var t = parseInt(Y.DOM[GET_COMPUTED_STYLE](node, BORDER_TOP_WIDTH), 10) || 0,
1105
l = parseInt(Y.DOM[GET_COMPUTED_STYLE](node, BORDER_LEFT_WIDTH), 10) || 0;
1107
if (RE_TABLE.test(node.tagName)) {
1117
_getWinSize: function(node) {
1118
var doc = Y.DOM._getDoc(),
1119
win = doc.defaultView || doc.parentWindow,
1120
mode = doc[COMPAT_MODE],
1121
h = win.innerHeight,
1123
root = doc[DOCUMENT_ELEMENT];
1125
if ( mode && !Y.UA.opera ) { // IE, Gecko
1126
if (mode != 'CSS1Compat') { // Quirks
1129
h = root.clientHeight;
1130
w = root.clientWidth;
1132
return { height: h, width: w };
1135
_getDocSize: function(node) {
1136
var doc = Y.DOM._getDoc(),
1137
root = doc[DOCUMENT_ELEMENT];
1139
if (doc[COMPAT_MODE] != 'CSS1Compat') {
1143
return { height: root.scrollHeight, width: root.scrollWidth };
1150
* Adds position and region management functionality to DOM.
1152
* @submodule dom-screen
1156
var OFFSET_WIDTH = 'offsetWidth',
1157
OFFSET_HEIGHT = 'offsetHeight',
1162
TAG_NAME = 'tagName';
1164
var getOffsets = function(r1, r2) {
1165
var t = Math.max(r1[TOP], r2[TOP]),
1166
r = Math.min(r1[RIGHT], r2[RIGHT]),
1167
b = Math.min(r1[BOTTOM], r2[BOTTOM]),
1168
l = Math.max(r1[LEFT], r2[LEFT]),
1178
var DOM = DOM || Y.DOM;
1181
* Returns an Object literal containing the following about this element: (top, right, bottom, left)
1183
* @param {HTMLElement} element The DOM element.
1184
@return {Object} Object literal containing the following about this element: (top, right, bottom, left)
1186
region: function(node) {
1187
var x = DOM.getXY(node),
1195
right: x[0] + node[OFFSET_WIDTH],
1196
bottom: x[1] + node[OFFSET_HEIGHT],
1198
height: node[OFFSET_HEIGHT],
1199
width: node[OFFSET_WIDTH]
1207
* Find the intersect information for the passes nodes.
1209
* @param {HTMLElement} element The first element
1210
* @param {HTMLElement | Object} element2 The element or region to check the interect with
1211
* @param {Object} altRegion An object literal containing the region for the first element if we already have the data (for performance i.e. DragDrop)
1212
@return {Object} Object literal containing the following intersection data: (top, right, bottom, left, area, yoff, xoff, inRegion)
1214
intersect: function(node, node2, altRegion) {
1215
var r = altRegion || DOM.region(node), region = {};
1219
region = DOM.region(n);
1220
} else if (Y.Lang.isObject(node2)) {
1226
var off = getOffsets(region, r);
1230
bottom: off[BOTTOM],
1232
area: ((off[BOTTOM] - off[TOP]) * (off[RIGHT] - off[LEFT])),
1233
yoff: ((off[BOTTOM] - off[TOP])),
1234
xoff: (off[RIGHT] - off[LEFT]),
1235
inRegion: DOM.inRegion(node, node2, false, altRegion)
1240
* Check if any part of this node is in the passed region
1242
* @param {Object} node2 The node to get the region from or an Object literal of the region
1243
* $param {Boolean} all Should all of the node be inside the region
1244
* @param {Object} altRegion An object literal containing the region for this node if we already have the data (for performance i.e. DragDrop)
1245
* @return {Boolean} True if in region, false if not.
1247
inRegion: function(node, node2, all, altRegion) {
1249
r = altRegion || DOM.region(node);
1253
region = DOM.region(n);
1254
} else if (Y.Lang.isObject(node2)) {
1262
r[LEFT] >= region[LEFT] &&
1263
r[RIGHT] <= region[RIGHT] &&
1264
r[TOP] >= region[TOP] &&
1265
r[BOTTOM] <= region[BOTTOM] );
1267
var off = getOffsets(region, r);
1268
if (off[BOTTOM] >= off[TOP] && off[RIGHT] >= off[LEFT]) {
1278
* Check if any part of this element is in the viewport
1279
* @method inViewportRegion
1280
* @param {HTMLElement} element The DOM element.
1281
* @param {Boolean} all Should all of the node be inside the region
1282
* @param {Object} altRegion An object literal containing the region for this node if we already have the data (for performance i.e. DragDrop)
1283
* @return {Boolean} True if in region, false if not.
1285
inViewportRegion: function(node, all, altRegion) {
1286
return DOM.inRegion(node, DOM.viewportRegion(node), all, altRegion);
1291
* Returns an Object literal containing the following about the visible region of viewport: (top, right, bottom, left)
1292
* @method viewportRegion
1293
@return {Object} Object literal containing the following about the visible region of the viewport: (top, right, bottom, left)
1295
viewportRegion: function(node) {
1296
node = node || Y.config.doc.documentElement;
1298
r[TOP] = DOM.docScrollY(node);
1299
r[RIGHT] = DOM.winWidth(node) + DOM.docScrollX(node);
1300
r[BOTTOM] = (DOM.docScrollY(node) + DOM.winHeight(node));
1301
r[LEFT] = DOM.docScrollX(node);
1308
* Add style management functionality to DOM.
1310
* @submodule dom-style
1314
var CLIENT_TOP = 'clientTop',
1315
CLIENT_LEFT = 'clientLeft',
1316
PARENT_NODE = 'parentNode',
1318
HAS_LAYOUT = 'hasLayout',
1321
FILTERS = 'filters',
1322
OPACITY = 'opacity',
1324
CURRENT_STYLE = 'currentStyle';
1326
// use alpha filter for IE opacity
1327
if (document[DOCUMENT_ELEMENT][STYLE][OPACITY] === UNDEFINED &&
1328
document[DOCUMENT_ELEMENT][FILTERS]) {
1329
Y.DOM.CUSTOM_STYLES[OPACITY] = {
1330
get: function(node) {
1332
try { // will error if no DXImageTransform
1333
val = node[FILTERS]['DXImageTransform.Microsoft.Alpha'][OPACITY];
1336
try { // make sure its in the document
1337
val = node[FILTERS]('alpha')[OPACITY];
1339
Y.log('getStyle: IE opacity filter not found; returning 1', 'warn', 'DOM');
1345
set: function(node, val, style) {
1346
if (typeof style[FILTER] == 'string') { // in case not appended
1347
style[FILTER] = 'alpha(' + OPACITY + '=' + val * 100 + ')';
1349
if (!node[CURRENT_STYLE] || !node[CURRENT_STYLE][HAS_LAYOUT]) {
1350
style.zoom = 1; // needs layout
1357
// IE getComputedStyle
1358
// TODO: unit-less lineHeight (e.g. 1.22)
1359
var re_size = /^width|height$/,
1360
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;
1362
var ComputedStyle = {
1365
get: function(el, property) {
1367
current = el[CURRENT_STYLE][property];
1369
if (property === OPACITY) {
1370
value = Y.DOM.CUSTOM_STYLES[OPACITY].get(el);
1371
} else if (!current || (current.indexOf && current.indexOf(PX) > -1)) { // no need to convert
1373
} else if (Y.DOM.IE.COMPUTED[property]) { // use compute function
1374
value = Y.DOM.IE.COMPUTED[property](el, property);
1375
} else if (re_unit.test(current)) { // convert to pixel
1376
value = Y.DOM.IE.ComputedStyle.getPixel(el, property);
1384
getOffset: function(el, prop) {
1385
var current = el[CURRENT_STYLE][prop], // value of "width", "top", etc.
1386
capped = prop.charAt(0).toUpperCase() + prop.substr(1), // "Width", "Top", etc.
1387
offset = 'offset' + capped, // "offsetWidth", "offsetTop", etc.
1388
pixel = 'pixel' + capped, // "pixelWidth", "pixelTop", etc.
1391
if (current == AUTO) {
1392
var actual = el[offset]; // offsetHeight/Top etc.
1393
if (actual === UNDEFINED) { // likely "right" or "bottom"
1398
if (re_size.test(prop)) { // account for box model diff
1399
el[STYLE][prop] = actual;
1400
if (el[offset] > actual) {
1401
// the difference is padding + border (works in Standards & Quirks modes)
1402
value = actual - (el[offset] - actual);
1404
el[STYLE][prop] = AUTO; // revert to auto
1406
} else { // convert units to px
1407
if (!el[STYLE][pixel] && !el[STYLE][prop]) { // need to map style.width to currentStyle (no currentStyle.pixelWidth)
1408
el[STYLE][prop] = current; // no style.pixelWidth if no style.width
1410
value = el[STYLE][pixel];
1415
getBorderWidth: function(el, property) {
1416
// clientHeight/Width = paddingBox (e.g. offsetWidth - borderWidth)
1417
// clientTop/Left = borderWidth
1419
if (!el[CURRENT_STYLE][HAS_LAYOUT]) { // TODO: unset layout?
1420
el[STYLE].zoom = 1; // need layout to measure client
1424
case BORDER_TOP_WIDTH:
1425
value = el[CLIENT_TOP];
1427
case BORDER_BOTTOM_WIDTH:
1428
value = el.offsetHeight - el.clientHeight - el[CLIENT_TOP];
1430
case BORDER_LEFT_WIDTH:
1431
value = el[CLIENT_LEFT];
1433
case BORDER_RIGHT_WIDTH:
1434
value = el.offsetWidth - el.clientWidth - el[CLIENT_LEFT];
1440
getPixel: function(node, att) {
1441
// use pixelRight to convert to px
1443
styleRight = node[CURRENT_STYLE][RIGHT],
1444
current = node[CURRENT_STYLE][att];
1446
node[STYLE][RIGHT] = current;
1447
val = node[STYLE].pixelRight;
1448
node[STYLE][RIGHT] = styleRight; // revert
1453
getMargin: function(node, att) {
1455
if (node[CURRENT_STYLE][att] == AUTO) {
1458
val = Y.DOM.IE.ComputedStyle.getPixel(node, att);
1463
getVisibility: function(node, att) {
1465
while ( (current = node[CURRENT_STYLE]) && current[att] == 'inherit') { // NOTE: assignment in test
1466
node = node[PARENT_NODE];
1468
return (current) ? current[att] : VISIBLE;
1471
getColor: function(node, att) {
1472
var current = node[CURRENT_STYLE][att];
1474
if (!current || current === TRANSPARENT) {
1475
Y.DOM.elementByAxis(node, PARENT_NODE, null, function(parent) {
1476
current = parent[CURRENT_STYLE][att];
1477
if (current && current !== TRANSPARENT) {
1484
return Y.Color.toRGB(current);
1487
getBorderColor: function(node, att) {
1488
var current = node[CURRENT_STYLE];
1489
var val = current[att] || current.color;
1490
return Y.Color.toRGB(Y.Color.toHex(val));
1495
//fontSize: getPixelFont,
1496
var IEComputed = {};
1498
IEComputed[WIDTH] = IEComputed[HEIGHT] = ComputedStyle.getOffset;
1500
IEComputed.color = IEComputed.backgroundColor = ComputedStyle.getColor;
1502
IEComputed[BORDER_TOP_WIDTH] = IEComputed[BORDER_RIGHT_WIDTH] =
1503
IEComputed[BORDER_BOTTOM_WIDTH] = IEComputed[BORDER_LEFT_WIDTH] =
1504
ComputedStyle.getBorderWidth;
1506
IEComputed.marginTop = IEComputed.marginRight = IEComputed.marginBottom =
1507
IEComputed.marginLeft = ComputedStyle.getMargin;
1509
IEComputed.visibility = ComputedStyle.getVisibility;
1510
IEComputed.borderColor = IEComputed.borderTopColor =
1511
IEComputed.borderRightColor = IEComputed.borderBottomColor =
1512
IEComputed.borderLeftColor = ComputedStyle.getBorderColor;
1514
if (!Y.config.win[GET_COMPUTED_STYLE]) {
1515
Y.DOM[GET_COMPUTED_STYLE] = ComputedStyle.get;
1518
Y.namespace('DOM.IE');
1519
Y.DOM.IE.COMPUTED = IEComputed;
1520
Y.DOM.IE.ComputedStyle = ComputedStyle;
1524
* Provides helper methods for collecting and filtering DOM elements.
1526
* @submodule selector
1530
* Provides helper methods for collecting and filtering DOM elements.
1536
PARENT_NODE = 'parentNode',
1537
PREVIOUS_SIBLING = 'previousSibling',
1539
NODE_TYPE = 'nodeType',
1540
TAG_NAME = 'tagName',
1541
ATTRIBUTES = 'attributes',
1542
PSEUDOS = 'pseudos',
1543
COMBINATOR = 'combinator';
1545
var reNth = /^(?:([\-]?\d*)(n){1}|(odd|even)$)*([\-+]?\d*)$/;
1548
tag: /^((?:-?[_a-z]+[\w\-]*)|\*)/i,
1549
attributes: /^\[([a-z]+\w*)+([~\|\^\$\*!=]=?)?['"]?([^\]]*?)['"]?\]/i,
1550
pseudos: /^:([\-\w]+)(?:\(['"]?(.+)['"]?\))*/i,
1551
combinator: /^\s*([>+~]|\s)\s*/
1556
* Default document for use queries
1557
* @property document
1559
* @default window.document
1561
document: Y.config.doc,
1563
* Mapping of attributes to aliases, normally to work around HTMLAttributes
1564
* that conflict with JS reserved words.
1565
* @property attrAliases
1571
* Mapping of shorthand tokens to corresponding attribute selector
1572
* @property shorthand
1576
'\\#(-?[_a-z]+[-\\w]*)': '[id=$1]',
1577
'\\.(-?[_a-z]+[-\\w]*)': '[class~=$1]'
1581
* List of operators and corresponding boolean functions.
1582
* These functions are passed the attribute and the current node's value of the attribute.
1583
* @property operators
1587
'=': function(attr, val) { return attr === val; }, // Equality
1588
'!=': function(attr, val) { return attr !== val; }, // Inequality
1589
'~=': function(attr, val) { // Match one of space seperated words
1591
return (s + attr + s).indexOf((s + val + s)) > -1;
1593
'|=': function(attr, val) { return Y.DOM._getRegExp('^' + val + '[-]?').test(attr); }, // Match start with value followed by optional hyphen
1594
'^=': function(attr, val) { return attr.indexOf(val) === 0; }, // Match starts with value
1595
'$=': function(attr, val) { return attr.lastIndexOf(val) === attr[LENGTH] - val[LENGTH]; }, // Match ends with value
1596
'*=': function(attr, val) { return attr.indexOf(val) > -1; }, // Match contains value as substring
1597
'': function(attr, val) { return attr; } // Just test for existence of attribute
1601
* List of pseudo-classes and corresponding boolean functions.
1602
* These functions are called with the current node, and any value that was parsed with the pseudo regex.
1607
'root': function(node) {
1608
return node === node.ownerDocument.documentElement;
1611
'nth-child': function(node, val) {
1612
return Selector.getNth(node, val);
1615
'nth-last-child': function(node, val) {
1616
return Selector.getNth(node, val, null, true);
1619
'nth-of-type': function(node, val) {
1620
return Selector.getNth(node, val, node[TAG_NAME]);
1623
'nth-last-of-type': function(node, val) {
1624
return Selector.getNth(node, val, node[TAG_NAME], true);
1627
'first-child': function(node) {
1628
return Y.DOM.firstChild(node[PARENT_NODE]) === node;
1631
'last-child': function(node) {
1632
return Y.DOM.lastChild(node[PARENT_NODE]) === node;
1635
'first-of-type': function(node, val) {
1636
return Y.DOM.firstChildByTag(node[PARENT_NODE], node[TAG_NAME]) === node;
1639
'last-of-type': function(node, val) {
1640
return Y.DOM.lastChildByTag(node[PARENT_NODE], node[TAG_NAME]) === node;
1643
'only-child': function(node) {
1644
var children = Y.DOM.children(node[PARENT_NODE]);
1645
return children[LENGTH] === 1 && children[0] === node;
1648
'only-of-type': function(node) {
1649
return Y.DOM.childrenByTag(node[PARENT_NODE], node[TAG_NAME])[LENGTH] === 1;
1652
'empty': function(node) {
1653
return node.childNodes[LENGTH] === 0;
1656
'not': function(node, simple) {
1657
return !Selector.test(node, simple);
1660
'contains': function(node, str) {
1661
var text = node.innerText || node.textContent || '';
1662
return text.indexOf(str) > -1;
1664
'checked': function(node) {
1665
return node.checked === true;
1670
* Test if the supplied node matches the supplied selector.
1673
* @param {HTMLElement | String} node An id or node reference to the HTMLElement being tested.
1674
* @param {string} selector The CSS Selector to test the node against.
1675
* @return{boolean} Whether or not the node matches the selector.
1679
test: function(node, selector) {
1684
var groups = selector ? selector.split(',') : [];
1685
if (groups[LENGTH]) {
1686
for (var i = 0, len = groups[LENGTH]; i < len; ++i) {
1687
if ( Selector._testNode(node, groups[i]) ) { // passes if ANY group matches
1693
return Selector._testNode(node, selector);
1697
* Filters a set of nodes based on a given CSS selector.
1700
* @param {array} nodes A set of nodes/ids to filter.
1701
* @param {string} selector The selector used to test each node.
1702
* @return{array} An array of nodes from the supplied array that match the given selector.
1705
filter: function(nodes, selector) {
1706
nodes = nodes || [];
1708
var result = Selector._filter(nodes, Selector._tokenize(selector)[0]);
1709
Y.log('filter: returning:' + result[LENGTH], 'info', 'Selector');
1714
* Retrieves a set of nodes based on a given CSS selector.
1717
* @param {string} selector The CSS Selector to test the node against.
1718
* @param {HTMLElement | String} root optional An id or HTMLElement to start the query from. Defaults to Selector.document.
1719
* @param {Boolean} firstOnly optional Whether or not to return only the first match.
1720
* @return {Array} An array of nodes that match the given selector.
1723
query: function(selector, root, firstOnly) {
1724
var result = Selector._query(selector, root, firstOnly);
1725
//Y.log('query: ' + selector + ' returning ' + result, 'info', 'Selector');
1729
_query: function(selector, root, firstOnly, deDupe) {
1730
var result = (firstOnly) ? null : [];
1735
root = root || Selector.document;
1736
var groups = selector.split(','); // TODO: handle comma in attribute/pseudo
1738
if (groups[LENGTH] > 1) {
1740
for (var i = 0, len = groups[LENGTH]; i < len; ++i) {
1741
found = arguments.callee(groups[i], root, firstOnly, true);
1742
result = firstOnly ? found : result.concat(found);
1744
Selector._clearFoundCache();
1748
var tokens = Selector._tokenize(selector);
1749
var idToken = tokens[Selector._getIdTokenIndex(tokens)],
1753
token = tokens.pop() || {};
1756
id = Selector._getId(idToken[ATTRIBUTES]);
1759
// use id shortcut when possible
1761
node = Selector.document.getElementById(id);
1763
if (node && (root[NODE_TYPE] === 9 || Y.DOM.contains(root, node))) {
1764
if ( Selector._testNode(node, null, idToken) ) {
1765
if (idToken === token) {
1766
nodes = [node]; // simple selector
1768
root = node; // start from here
1776
if (root && !nodes[LENGTH]) {
1777
nodes = root.getElementsByTagName(token[TAG]);
1780
if (nodes[LENGTH]) {
1781
result = Selector._filter(nodes, token, firstOnly, deDupe);
1786
_filter: function(nodes, token, firstOnly, deDupe) {
1787
var result = firstOnly ? null : [];
1789
result = Y.DOM.filterElementsBy(nodes, function(node) {
1790
if (! Selector._testNode(node, '', token, deDupe)) {
1799
Selector._foundCache[Selector._foundCache[LENGTH]] = node;
1807
_testNode: function(node, selector, token, deDupe) {
1808
token = token || Selector._tokenize(selector).pop() || {};
1809
var ops = Selector.operators,
1810
pseudos = Selector.pseudos,
1811
prev = token.previous,
1814
if (!node[TAG_NAME] ||
1815
(token[TAG] !== '*' && node[TAG_NAME].toUpperCase() !== token[TAG]) ||
1816
(deDupe && node._found) ) {
1820
if (token[ATTRIBUTES][LENGTH]) {
1822
for (i = 0, len = token[ATTRIBUTES][LENGTH]; i < len; ++i) {
1823
attribute = node.getAttribute(token[ATTRIBUTES][i][0], 2);
1824
if (attribute === undefined) {
1827
if ( ops[token[ATTRIBUTES][i][1]] &&
1828
!ops[token[ATTRIBUTES][i][1]](attribute, token[ATTRIBUTES][i][2])) {
1834
if (token[PSEUDOS][LENGTH]) {
1835
for (i = 0, len = token[PSEUDOS][LENGTH]; i < len; ++i) {
1836
if (pseudos[token[PSEUDOS][i][0]] &&
1837
!pseudos[token[PSEUDOS][i][0]](node, token[PSEUDOS][i][1])) {
1842
return (prev && prev[COMBINATOR] !== ',') ?
1843
Selector.combinators[prev[COMBINATOR]](node, token) :
1851
_clearFoundCache: function() {
1852
Y.log('getBySelector: clearing found cache of ' + Selector._foundCache[LENGTH] + ' elements');
1853
for (var i = 0, len = Selector._foundCache[LENGTH]; i < len; ++i) {
1854
try { // IE no like delete
1855
delete Selector._foundCache[i]._found;
1857
Selector._foundCache[i].removeAttribute('_found');
1860
Selector._foundCache = [];
1861
Y.log('getBySelector: done clearing Selector._foundCache');
1865
' ': function(node, token) {
1866
while ((node = node[PARENT_NODE])) {
1867
if (Selector._testNode(node, '', token.previous)) {
1874
'>': function(node, token) {
1875
return Selector._testNode(node[PARENT_NODE], null, token.previous);
1877
'+': function(node, token) {
1878
var sib = node[PREVIOUS_SIBLING];
1879
while (sib && sib[NODE_TYPE] !== 1) {
1880
sib = sib[PREVIOUS_SIBLING];
1883
if (sib && Selector._testNode(sib, null, token.previous)) {
1889
'~': function(node, token) {
1890
var sib = node[PREVIOUS_SIBLING];
1892
if (sib[NODE_TYPE] === 1 && Selector._testNode(sib, null, token.previous)) {
1895
sib = sib[PREVIOUS_SIBLING];
1904
an+b = get every _a_th node starting at the _b_th
1905
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
1906
1n+b = get every element starting from b ("1" may may be omitted, e.g. "1n+0" or "n+0" or "n")
1907
an+0 = get every _a_th element, "0" may be omitted
1909
getNth: function(node, expr, tag, reverse) {
1912
var a = parseInt(RegExp.$1, 10), // include every _a_ elements (zero means no repeat, just first _a_)
1913
n = RegExp.$2, // "n"
1914
oddeven = RegExp.$3, // "odd" or "even"
1915
b = parseInt(RegExp.$4, 10) || 0, // start scan from element _b_
1916
op, i, len, siblings;
1919
siblings = Y.DOM.childrenByTag(node[PARENT_NODE], tag);
1921
siblings = Y.DOM.children(node[PARENT_NODE]);
1925
a = 2; // always every other
1928
b = (oddeven === 'odd') ? 1 : 0;
1929
} else if ( isNaN(a) ) {
1930
a = (n) ? 1 : 0; // start from the first or no repeat
1933
if (a === 0) { // just the first
1935
b = siblings[LENGTH] - b + 1;
1938
if (siblings[b - 1] === node) {
1945
reverse = !!reverse;
1950
for (i = b - 1, len = siblings[LENGTH]; i < len; i += a) {
1951
if ( i >= 0 && siblings[i] === node ) {
1956
for (i = siblings[LENGTH] - b, len = siblings[LENGTH]; i >= 0; i -= a) {
1957
if ( i < len && siblings[i] === node ) {
1965
_getId: function(attr) {
1966
for (var i = 0, len = attr[LENGTH]; i < len; ++i) {
1967
if (attr[i][0] == 'id' && attr[i][1] === '=') {
1973
_getIdTokenIndex: function(tokens) {
1974
for (var i = 0, len = tokens[LENGTH]; i < len; ++i) {
1975
if (Selector._getId(tokens[i][ATTRIBUTES])) {
1983
Break selector into token units per simple selector.
1984
Combinator is attached to left-hand selector.
1986
_tokenize: function(selector) {
1987
var token = {}, // one token per simple selector (left selector holds combinator)
1988
tokens = [], // array of tokens
1989
found = false, // whether or not any matches were found this pass
1990
match; // the regex match
1992
selector = Selector._replaceShorthand(selector); // convert ID and CLASS shortcuts to attributes
1995
Search for selector patterns, store, and strip them from the selector string
1996
until no patterns match (invalid selector) or we run out of chars.
1998
Multiple attributes and pseudos are allowed, in any order.
2000
'form:first-child[type=button]:not(button)[lang|=en]'
2003
found = false; // reset after full pass
2004
for (var re in patterns) {
2005
if (patterns.hasOwnProperty(re)) {
2006
if (re != TAG && re != COMBINATOR) { // only one allowed
2007
token[re] = token[re] || [];
2009
if ((match = patterns[re].exec(selector))) { // note assignment
2011
if (re != TAG && re != COMBINATOR) { // only one allowed
2012
//token[re] = token[re] || [];
2014
// capture ID for fast path to element
2015
if (re === ATTRIBUTES && match[1] === 'id') {
2016
token.id = match[3];
2019
token[re].push(match.slice(1));
2020
} else { // single selector (tag, combinator)
2021
token[re] = match[1];
2023
selector = selector.replace(match[0], ''); // strip current match from selector
2024
if (re === COMBINATOR || !selector[LENGTH]) { // next token or done
2025
token[ATTRIBUTES] = Selector._fixAttributes(token[ATTRIBUTES]);
2026
token[PSEUDOS] = token[PSEUDOS] || [];
2027
token[TAG] = token[TAG] ? token[TAG].toUpperCase() : '*';
2030
token = { // prep next token
2042
_fixAttributes: function(attr) {
2043
var aliases = Selector.attrAliases;
2045
for (var i = 0, len = attr[LENGTH]; i < len; ++i) {
2046
if (aliases[attr[i][0]]) { // convert reserved words, etc
2047
attr[i][0] = aliases[attr[i][0]];
2049
if (!attr[i][1]) { // use exists operator
2056
_replaceShorthand: function(selector) {
2057
var shorthand = Selector.shorthand;
2058
var attrs = selector.match(patterns[ATTRIBUTES]); // pull attributes to avoid false pos on "." and "#"
2060
selector = selector.replace(patterns[ATTRIBUTES], 'REPLACED_ATTRIBUTE');
2062
for (var re in shorthand) {
2063
if (shorthand.hasOwnProperty(re)) {
2064
selector = selector.replace(Y.DOM._getRegExp(re, 'gi'), shorthand[re]);
2069
for (var i = 0, len = attrs[LENGTH]; i < len; ++i) {
2070
selector = selector.replace('REPLACED_ATTRIBUTE', attrs[i]);
2078
if (Y.UA.ie && Y.UA.ie < 8) { // rewrite class for IE (others use getAttribute('class')
2079
Selector.attrAliases['class'] = 'className';
2080
Selector.attrAliases['for'] = 'htmlFor';
2083
Y.Selector = Selector;
2084
Y.Selector.patterns = patterns;
2088
* Add style management functionality to DOM.
2090
* @submodule dom-style
2094
var TO_STRING = 'toString',
2095
PARSE_INT = parseInt,
2118
re_RGB: /^rgb\(([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\)$/i,
2119
re_hex: /^#?([0-9A-F]{2})([0-9A-F]{2})([0-9A-F]{2})$/i,
2120
re_hex3: /([0-9A-F])/gi,
2122
toRGB: function(val) {
2123
if (!Y.Color.re_RGB.test(val)) {
2124
val = Y.Color.toHex(val);
2127
if(Y.Color.re_hex.exec(val)) {
2129
PARSE_INT(RE.$1, 16),
2130
PARSE_INT(RE.$2, 16),
2131
PARSE_INT(RE.$3, 16)
2137
toHex: function(val) {
2138
val = Y.Color.KEYWORDS[val] || val;
2139
if (Y.Color.re_RGB.exec(val)) {
2140
var r = (RE.$1.length === 1) ? '0' + RE.$1 : Number(RE.$1),
2141
g = (RE.$2.length === 1) ? '0' + RE.$2 : Number(RE.$2),
2142
b = (RE.$3.length === 1) ? '0' + RE.$3 : Number(RE.$3);
2151
if (val.length < 6) {
2152
val = val.replace(Y.Color.re_hex3, '$1$1');
2155
if (val !== 'transparent' && val.indexOf('#') < 0) {
2159
return val.toLowerCase();
2166
}, '3.0.0pr2' ,{skinnable:false});