2 Copyright (c) 2009, Yahoo! Inc. All rights reserved.
3 Code licensed under the BSD License:
4 http://developer.yahoo.net/yui/license.txt
8 YUI.add('dom-base', function(Y) {
12 * The DOM utility provides a cross-browser abtraction layer
13 * normalizing DOM tasks, and adds extra helper functionality
14 * for other common tasks.
21 * Provides DOM helper methods.
25 var NODE_TYPE = 'nodeType',
26 OWNER_DOCUMENT = 'ownerDocument',
27 DEFAULT_VIEW = 'defaultView',
28 PARENT_WINDOW = 'parentWindow',
30 PARENT_NODE = 'parentNode',
31 FIRST_CHILD = 'firstChild',
32 PREVIOUS_SIBLING = 'previousSibling',
33 NEXT_SIBLING = 'nextSibling',
34 CONTAINS = 'contains',
35 COMPARE_DOCUMENT_POSITION = 'compareDocumentPosition',
37 documentElement = document.documentElement,
39 re_tag = /<([a-z]+)/i;
43 * Returns the HTMLElement with the given ID (Wrapper for document.getElementById).
45 * @param {String} id the id attribute
46 * @param {Object} doc optional The document to search. Defaults to current document
47 * @return {HTMLElement | null} The HTMLElement with the id, or null if none found.
49 byId: function(id, doc) {
50 doc = doc || Y.config.doc;
52 return doc.getElementById(id);
56 children: function(node, tag) {
60 ret = Y.Selector.query('> ' + tag, node);
66 firstByTag: function(tag, root) {
68 root = root || Y.config.doc;
70 if (tag && root.getElementsByTagName) {
71 ret = root.getElementsByTagName(tag)[0];
78 * Returns the text content of the HTMLElement.
80 * @param {HTMLElement} element The html element.
81 * @return {String} The text content of the element (includes text of any descending elements).
83 getText: (documentElement.textContent !== undefined) ?
87 ret = element.textContent;
90 } : function(element) {
93 ret = element.innerText;
99 * Sets the text content of the HTMLElement.
101 * @param {HTMLElement} element The html element.
102 * @param {String} content The content to add.
104 setText: (documentElement.textContent !== undefined) ?
105 function(element, content) {
107 element.textContent = content;
109 } : function(element, content) {
111 element.innerText = content;
116 * Finds the previous sibling of the element.
118 * @deprecated Use elementByAxis
119 * @param {HTMLElement} element The html element.
120 * @param {Function} fn optional An optional boolean test to apply.
121 * The optional function is passed the current DOM node being tested as its only argument.
122 * If no function is given, the first sibling is returned.
123 * @param {Boolean} all optional Whether all node types should be scanned, or just element nodes.
124 * @return {HTMLElement | null} The matching DOM node or null if none found.
126 previous: function(element, fn, all) {
127 return Y.DOM.elementByAxis(element, PREVIOUS_SIBLING, fn, all);
131 * Finds the next sibling of the element.
133 * @deprecated Use elementByAxis
134 * @param {HTMLElement} element The html element.
135 * @param {Function} fn optional An optional boolean test to apply.
136 * The optional function is passed the current DOM node being tested as its only argument.
137 * If no function is given, the first sibling is returned.
138 * @param {Boolean} all optional Whether all node types should be scanned, or just element nodes.
139 * @return {HTMLElement | null} The matching DOM node or null if none found.
141 next: function(element, fn, all) {
142 return Y.DOM.elementByAxis(element, NEXT_SIBLING, fn, all);
146 * Finds the ancestor of the element.
148 * @deprecated Use elementByAxis
149 * @param {HTMLElement} element The html element.
150 * @param {Function} fn optional An optional boolean test to apply.
151 * The optional function is passed the current DOM node being tested as its only argument.
152 * If no function is given, the parentNode is returned.
153 * @param {Boolean} all optional Whether all node types should be scanned, or just element nodes.
154 * @return {HTMLElement | null} The matching DOM node or null if none found.
156 // TODO: optional stopAt node?
157 ancestor: function(element, fn, all) {
158 return Y.DOM.elementByAxis(element, PARENT_NODE, fn, all);
162 * Searches the element by the given axis for the first matching element.
163 * @method elementByAxis
164 * @param {HTMLElement} element The html element.
165 * @param {String} axis The axis to search (parentNode, nextSibling, previousSibling).
166 * @param {Function} fn optional An optional boolean test to apply.
167 * @param {Boolean} all optional Whether all node types should be returned, or just element nodes.
168 * The optional function is passed the current HTMLElement being tested as its only argument.
169 * If no function is given, the first element is returned.
170 * @return {HTMLElement | null} The matching element or null if none found.
172 elementByAxis: function(element, axis, fn, all) {
173 while (element && (element = element[axis])) { // NOTE: assignment
174 if ( (all || element[TAG_NAME]) && (!fn || fn(element)) ) {
182 * Determines whether or not one HTMLElement is or contains another HTMLElement.
184 * @param {HTMLElement} element The containing html element.
185 * @param {HTMLElement} needle The html element that may be contained.
186 * @return {Boolean} Whether or not the element is or contains the needle.
188 contains: function(element, needle) {
191 if ( !needle || !element || !needle[NODE_TYPE] || !element[NODE_TYPE]) {
193 } else if (element[CONTAINS]) {
194 if (Y.UA.opera || needle[NODE_TYPE] === 1) { // IE & SAF contains fail if needle not an ELEMENT_NODE
195 ret = element[CONTAINS](needle);
197 ret = Y.DOM._bruteContains(element, needle);
199 } else if (element[COMPARE_DOCUMENT_POSITION]) { // gecko
200 if (element === needle || !!(element[COMPARE_DOCUMENT_POSITION](needle) & 16)) {
209 * Determines whether or not the HTMLElement is part of the document.
211 * @param {HTMLElement} element The containing html element.
212 * @param {HTMLElement} doc optional The document to check.
213 * @return {Boolean} Whether or not the element is attached to the document.
215 inDoc: function(element, doc) {
216 doc = doc || element[OWNER_DOCUMENT];
218 if (!id) { // TODO: remove when done?
219 id = element.id = Y.guid();
222 return !! (doc.getElementById(id));
226 * Creates a new dom node using the provided markup string.
228 * @param {String} html The markup used to create the element
229 * @param {HTMLDocument} doc An optional document context
231 create: function(html, doc) {
232 if (typeof html === 'string') {
233 html = Y.Lang.trim(html); // match IE which trims whitespace from innerHTML
236 if (!doc && Y.DOM._cloneCache[html]) {
237 return Y.DOM._cloneCache[html].cloneNode(true); // NOTE: return
240 doc = doc || Y.config.doc;
241 var m = re_tag.exec(html),
242 create = Y.DOM._create,
243 custom = Y.DOM.creators,
247 if (m && custom[m[1]]) {
248 if (typeof custom[m[1]] === 'function') {
249 create = custom[m[1]];
255 nodes = create(html, doc, tag).childNodes;
257 if (nodes.length === 1) { // return single node, breaking parentNode ref from "fragment"
258 ret = nodes[0].parentNode.removeChild(nodes[0]);
259 } else { // return multiple nodes as a fragment
260 ret = Y.DOM._nl2frag(nodes, doc);
264 Y.DOM._cloneCache[html] = ret.cloneNode(true);
269 _nl2frag: function(nodes, doc) {
273 if (nodes && (nodes.push || nodes.item) && nodes[0]) {
274 doc = doc || nodes[0].ownerDocument;
275 ret = doc.createDocumentFragment();
277 if (nodes.item) { // convert live list to static array
278 nodes = Y.Array(nodes, 0, true);
281 for (i = 0, len = nodes.length; i < len; i++) {
282 ret.appendChild(nodes[i]);
284 } // else inline with log for minification
289 CUSTOM_ATTRIBUTES: (!documentElement.hasAttribute) ? { // IE < 8
298 * Provides a normalized attribute interface.
299 * @method setAttibute
300 * @param {String | HTMLElement} el The target element for the attribute.
301 * @param {String} attr The attribute to set.
302 * @param {String} val The value of the attribute.
304 setAttribute: function(el, attr, val, ieAttr) {
305 if (el && el.setAttribute) {
306 attr = Y.DOM.CUSTOM_ATTRIBUTES[attr] || attr;
307 el.setAttribute(attr, val, ieAttr);
313 * Provides a normalized attribute interface.
314 * @method getAttibute
315 * @param {String | HTMLElement} el The target element for the attribute.
316 * @param {String} attr The attribute to get.
317 * @return {String} The current value of the attribute.
319 getAttribute: function(el, attr, ieAttr) {
320 ieAttr = (ieAttr !== undefined) ? ieAttr : 2;
322 if (el && el.getAttribute) {
323 attr = Y.DOM.CUSTOM_ATTRIBUTES[attr] || attr;
324 ret = el.getAttribute(attr, ieAttr);
327 ret = ''; // per DOM spec
333 isWindow: function(obj) {
334 return obj.alert && obj.document;
338 div: document.createElement('div')
341 _create: function(html, doc, tag) {
344 var frag = Y.DOM._fragClones[tag];
346 frag = frag.cloneNode(false);
348 frag = Y.DOM._fragClones[tag] = doc.createElement(tag);
350 frag.innerHTML = html;
354 _removeChildNodes: function(node) {
355 while (node.firstChild) {
356 node.removeChild(node.firstChild);
363 * Inserts content in a node at the given location
365 * @param {HTMLElement} node The node to insert into
366 * @param {String} content The content to be inserted
367 * @param {String} where Where to insert the content; default is after lastChild
369 addHTML: function(node, content, where) {
370 if (typeof content === 'string') {
371 content = Y.Lang.trim(content); // match IE which trims whitespace from innerHTML
374 var newNode = Y.DOM._cloneCache[content],
375 nodeParent = node.parentNode;
378 newNode = newNode.cloneNode(true);
380 if (content.nodeType) { // domNode
382 } else { // create from string and cache
383 newNode = Y.DOM.create(content);
388 if (where.nodeType) { // insert regardless of relationship to node
389 // TODO: check if node.contains(where)?
390 where.parentNode.insertBefore(newNode, where);
394 while (node.firstChild) {
395 node.removeChild(node.firstChild);
397 node.appendChild(newNode);
400 nodeParent.insertBefore(newNode, node);
403 if (node.nextSibling) { // IE errors if refNode is null
404 nodeParent.insertBefore(newNode, node.nextSibling);
406 nodeParent.appendChild(newNode);
410 node.appendChild(newNode);
414 node.appendChild(newNode);
424 getValue: function(node) {
425 var ret = '', // TODO: return null?
428 if (node && node[TAG_NAME]) {
429 getter = Y.DOM.VALUE_GETTERS[node[TAG_NAME].toLowerCase()];
438 return (typeof ret === 'string') ? ret : '';
441 setValue: function(node, val) {
444 if (node && node[TAG_NAME]) {
445 setter = Y.DOM.VALUE_SETTERS[node[TAG_NAME].toLowerCase()];
456 * Brute force version of contains.
457 * Used for browsers without contains support for non-HTMLElement Nodes (textNodes, etc).
458 * @method _bruteContains
460 * @param {HTMLElement} element The containing html element.
461 * @param {HTMLElement} needle The html element that may be contained.
462 * @return {Boolean} Whether or not the element is or contains the needle.
464 _bruteContains: function(element, needle) {
466 if (element === needle) {
469 needle = needle.parentNode;
474 // TODO: move to Lang?
476 * Memoizes dynamic regular expressions to boost runtime performance.
479 * @param {String} str The string to convert to a regular expression.
480 * @param {String} flags optional An optinal string of flags.
481 * @return {RegExp} An instance of RegExp
483 _getRegExp: function(str, flags) {
485 Y.DOM._regexCache = Y.DOM._regexCache || {};
486 if (!Y.DOM._regexCache[str + flags]) {
487 Y.DOM._regexCache[str + flags] = new RegExp(str, flags);
489 return Y.DOM._regexCache[str + flags];
492 // TODO: make getDoc/Win true privates?
494 * returns the appropriate document.
497 * @param {HTMLElement} element optional Target element.
498 * @return {Object} The document for the given element or the default document.
500 _getDoc: function(element) {
501 element = element || {};
503 return (element[NODE_TYPE] === 9) ? element : // element === document
504 element[OWNER_DOCUMENT] || // element === DOM node
505 element.document || // element === window
506 Y.config.doc; // default
510 * returns the appropriate window.
513 * @param {HTMLElement} element optional Target element.
514 * @return {Object} The window for the given element or the default window.
516 _getWin: function(element) {
517 var doc = Y.DOM._getDoc(element);
518 return doc[DEFAULT_VIEW] || doc[PARENT_WINDOW] || Y.config.win;
521 _batch: function(nodes, fn, arg1, arg2, arg3, etc) {
522 fn = (typeof name === 'string') ? Y.DOM[fn] : fn;
527 Y.each(nodes, function(node) {
528 if ((result = fn.call(Y.DOM, node, arg1, arg2, arg3, etc)) !== undefined) {
529 ret[ret.length] = result;
534 return ret.length ? ret : nodes;
537 _testElement: function(element, tag, fn) {
538 tag = (tag && tag !== '*') ? tag.toUpperCase() : null;
539 return (element && element[TAG_NAME] &&
540 (!tag || element[TAG_NAME].toUpperCase() === tag) &&
541 (!fn || fn(element)));
546 _IESimpleCreate: function(html, doc) {
547 doc = doc || Y.config.doc;
548 return doc.createElement(html);
554 var creators = Y.DOM.creators,
555 create = Y.DOM.create,
556 re_tbody = /(?:\/(?:thead|tfoot|tbody|caption|col|colgroup)>)+\s*<tbody/,
558 TABLE_OPEN = '<table>',
559 TABLE_CLOSE = '</table>';
563 // TODO: thead/tfoot with nested tbody
564 // IE adds TBODY when creating TABLE elements (which may share this impl)
565 tbody: function(html, doc) {
566 var frag = create(TABLE_OPEN + html + TABLE_CLOSE, doc),
567 tb = frag.children.tags('tbody')[0];
569 if (frag.children.length > 1 && tb && !re_tbody.test(html)) {
570 tb[PARENT_NODE].removeChild(tb); // strip extraneous tbody
575 script: function(html, doc) {
576 var frag = doc.createElement('div');
578 frag.innerHTML = '-' + html;
579 frag.removeChild(frag[FIRST_CHILD]);
585 Y.mix(Y.DOM.VALUE_GETTERS, {
586 button: function(node) {
587 return (node.attributes && node.attributes.value) ? node.attributes.value.value : '';
591 Y.mix(Y.DOM.VALUE_SETTERS, {
592 // IE: node.value changes the button text, which should be handled via innerHTML
593 button: function(node, val) {
594 var attr = node.attributes.value;
596 attr = node[OWNER_DOCUMENT].createAttribute('value');
597 node.setAttributeNode(attr);
605 if (Y.UA.gecko || Y.UA.ie) {
607 option: function(html, doc) {
608 return create('<select>' + html + '</select>', doc);
611 tr: function(html, doc) {
612 return create('<tbody>' + html + '</tbody>', doc);
615 td: function(html, doc) {
616 return create('<tr>' + html + '</tr>', doc);
619 tbody: function(html, doc) {
620 return create(TABLE_OPEN + html + TABLE_CLOSE, doc);
627 thead: creators.tbody,
628 tfoot: creators.tbody,
629 caption: creators.tbody,
630 colgroup: creators.tbody,
632 optgroup: creators.option
636 Y.mix(Y.DOM.VALUE_GETTERS, {
637 option: function(node) {
638 var attrs = node.attributes;
639 return (attrs.value && attrs.value.specified) ? node.value : node.text;
642 select: function(node) {
643 var val = node.value,
644 options = node.options;
646 if (options && val === '') {
649 val = Y.DOM.getValue(options[node.selectedIndex], 'value');
659 var addClass, hasClass, removeClass;
663 * Determines whether a DOM element has the given className.
665 * @param {HTMLElement} element The DOM element.
666 * @param {String} className the class name to search for
667 * @return {Boolean} Whether or not the element has the given class.
669 hasClass: function(node, className) {
670 var re = Y.DOM._getRegExp('(?:^|\\s+)' + className + '(?:\\s+|$)');
671 return re.test(node.className);
675 * Adds a class name to a given DOM element.
677 * @param {HTMLElement} element The DOM element.
678 * @param {String} className the class name to add to the class attribute
680 addClass: function(node, className) {
681 if (!Y.DOM.hasClass(node, className)) { // skip if already present
682 node.className = Y.Lang.trim([node.className, className].join(' '));
687 * Removes a class name from a given element.
688 * @method removeClass
689 * @param {HTMLElement} element The DOM element.
690 * @param {String} className the class name to remove from the class attribute
692 removeClass: function(node, className) {
693 if (className && hasClass(node, className)) {
694 node.className = Y.Lang.trim(node.className.replace(Y.DOM._getRegExp('(?:^|\\s+)' +
695 className + '(?:\\s+|$)'), ' '));
697 if ( hasClass(node, className) ) { // in case of multiple adjacent
698 removeClass(node, className);
704 * Replace a class with another class for a given element.
705 * If no oldClassName is present, the newClassName is simply added.
706 * @method replaceClass
707 * @param {HTMLElement} element The DOM element.
708 * @param {String} oldClassName the class name to be replaced
709 * @param {String} newClassName the class name that will be replacing the old class name
711 replaceClass: function(node, oldC, newC) {
712 addClass(node, newC);
713 removeClass(node, oldC);
717 * If the className exists on the node it is removed, if it doesn't exist it is added.
718 * @method toggleClass
719 * @param {HTMLElement} element The DOM element.
720 * @param {String} className the class name to be toggled
722 toggleClass: function(node, className) {
723 if (hasClass(node, className)) {
724 removeClass(node, className);
726 addClass(node, className);
731 hasClass = Y.DOM.hasClass;
732 removeClass = Y.DOM.removeClass;
733 addClass = Y.DOM.addClass;
737 }, '3.0.0' ,{requires:['oop']});
738 YUI.add('dom-style', function(Y) {
742 * Add style management functionality to DOM.
744 * @submodule dom-style
748 var DOCUMENT_ELEMENT = 'documentElement',
749 DEFAULT_VIEW = 'defaultView',
750 OWNER_DOCUMENT = 'ownerDocument',
753 CSS_FLOAT = 'cssFloat',
754 STYLE_FLOAT = 'styleFloat',
755 TRANSPARENT = 'transparent',
756 GET_COMPUTED_STYLE = 'getComputedStyle',
758 DOCUMENT = Y.config.doc,
759 UNDEFINED = undefined,
761 re_color = /color$/i;
770 * Sets a style property for a given element.
772 * @param {HTMLElement} An HTMLElement to apply the style to.
773 * @param {String} att The style property to set.
774 * @param {String|Number} val The value.
776 setStyle: function(node, att, val, style) {
777 style = style || node.style;
778 var CUSTOM_STYLES = Y.DOM.CUSTOM_STYLES;
782 val = ''; // normalize for unsetting
784 if (att in CUSTOM_STYLES) {
785 if (CUSTOM_STYLES[att].set) {
786 CUSTOM_STYLES[att].set(node, val, style);
787 return; // NOTE: return
788 } else if (typeof CUSTOM_STYLES[att] === 'string') {
789 att = CUSTOM_STYLES[att];
797 * Returns the current style value for the given property.
799 * @param {HTMLElement} An HTMLElement to get the style from.
800 * @param {String} att The style property to get.
802 getStyle: function(node, att) {
803 var style = node[STYLE],
804 CUSTOM_STYLES = Y.DOM.CUSTOM_STYLES,
808 if (att in CUSTOM_STYLES) {
809 if (CUSTOM_STYLES[att].get) {
810 return CUSTOM_STYLES[att].get(node, att, style); // NOTE: return
811 } else if (typeof CUSTOM_STYLES[att] === 'string') {
812 att = CUSTOM_STYLES[att];
816 if (val === '') { // TODO: is empty string sufficient?
817 val = Y.DOM[GET_COMPUTED_STYLE](node, att);
825 * Sets multiple style properties.
827 * @param {HTMLElement} node An HTMLElement to apply the styles to.
828 * @param {Object} hash An object literal of property:value pairs.
830 setStyles: function(node, hash) {
831 var style = node.style;
832 Y.each(hash, function(v, n) {
833 Y.DOM.setStyle(node, n, v, style);
838 * Returns the computed style for the given node.
839 * @method getComputedStyle
840 * @param {HTMLElement} An HTMLElement to get the style from.
841 * @param {String} att The style property to get.
842 * @return {String} The computed value of the style property.
844 getComputedStyle: function(node, att) {
846 doc = node[OWNER_DOCUMENT];
849 val = doc[DEFAULT_VIEW][GET_COMPUTED_STYLE](node, null)[att];
855 // normalize reserved word float alternatives ("cssFloat" or "styleFloat")
856 if (DOCUMENT[DOCUMENT_ELEMENT][STYLE][CSS_FLOAT] !== UNDEFINED) {
857 Y.DOM.CUSTOM_STYLES[FLOAT] = CSS_FLOAT;
858 } else if (DOCUMENT[DOCUMENT_ELEMENT][STYLE][STYLE_FLOAT] !== UNDEFINED) {
859 Y.DOM.CUSTOM_STYLES[FLOAT] = STYLE_FLOAT;
862 // fix opera computedStyle default color unit (convert to rgb)
864 Y.DOM[GET_COMPUTED_STYLE] = function(node, att) {
865 var view = node[OWNER_DOCUMENT][DEFAULT_VIEW],
866 val = view[GET_COMPUTED_STYLE](node, '')[att];
868 if (re_color.test(att)) {
869 val = Y.Color.toRGB(val);
877 // safari converts transparent to rgba(), others use "transparent"
879 Y.DOM[GET_COMPUTED_STYLE] = function(node, att) {
880 var view = node[OWNER_DOCUMENT][DEFAULT_VIEW],
881 val = view[GET_COMPUTED_STYLE](node, '')[att];
883 if (val === 'rgba(0, 0, 0, 0)') {
893 var PARSE_INT = parseInt,
916 re_RGB: /^rgb\(([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\)$/i,
917 re_hex: /^#?([0-9A-F]{2})([0-9A-F]{2})([0-9A-F]{2})$/i,
918 re_hex3: /([0-9A-F])/gi,
920 toRGB: function(val) {
921 if (!Y.Color.re_RGB.test(val)) {
922 val = Y.Color.toHex(val);
925 if(Y.Color.re_hex.exec(val)) {
927 PARSE_INT(RE.$1, 16),
928 PARSE_INT(RE.$2, 16),
935 toHex: function(val) {
936 val = Y.Color.KEYWORDS[val] || val;
937 if (Y.Color.re_RGB.exec(val)) {
939 Number(RE.$1).toString(16),
940 Number(RE.$2).toString(16),
941 Number(RE.$3).toString(16)
944 for (var i = 0; i < val.length; i++) {
945 if (val[i].length < 2) {
946 val[i] = val[i].replace(Y.Color.re_hex3, '$1$1');
950 val = '#' + val.join('');
953 if (val.length < 6) {
954 val = val.replace(Y.Color.re_hex3, '$1$1');
957 if (val !== 'transparent' && val.indexOf('#') < 0) {
961 return val.toLowerCase();
967 var HAS_LAYOUT = 'hasLayout',
974 BORDER_WIDTH = 'borderWidth',
975 BORDER_TOP_WIDTH = 'borderTopWidth',
976 BORDER_RIGHT_WIDTH = 'borderRightWidth',
977 BORDER_BOTTOM_WIDTH = 'borderBottomWidth',
978 BORDER_LEFT_WIDTH = 'borderLeftWidth',
981 TRANSPARENT = 'transparent',
983 GET_COMPUTED_STYLE = 'getComputedStyle',
984 UNDEFINED = undefined,
985 documentElement = document.documentElement,
987 // TODO: unit-less lineHeight (e.g. 1.22)
988 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,
990 _getStyleObj = function(node) {
991 return node.currentStyle || node.style;
997 get: function(el, property) {
1002 current = _getStyleObj(el)[property];
1004 if (property === OPACITY && Y.DOM.CUSTOM_STYLES[OPACITY]) {
1005 value = Y.DOM.CUSTOM_STYLES[OPACITY].get(el);
1006 } else if (!current || (current.indexOf && current.indexOf(PX) > -1)) { // no need to convert
1008 } else if (Y.DOM.IE.COMPUTED[property]) { // use compute function
1009 value = Y.DOM.IE.COMPUTED[property](el, property);
1010 } else if (re_unit.test(current)) { // convert to pixel
1011 value = ComputedStyle.getPixel(el, property) + PX;
1021 width: ['Left', 'Right'],
1022 height: ['Top', 'Bottom'],
1027 getOffset: function(el, prop) {
1028 var current = _getStyleObj(el)[prop], // value of "width", "top", etc.
1029 capped = prop.charAt(0).toUpperCase() + prop.substr(1), // "Width", "Top", etc.
1030 offset = 'offset' + capped, // "offsetWidth", "offsetTop", etc.
1031 pixel = 'pixel' + capped, // "pixelWidth", "pixelTop", etc.
1032 sizeOffsets = ComputedStyle.sizeOffsets[prop],
1035 // IE pixelWidth incorrect for percent
1036 // manually compute by subtracting padding and border from offset size
1037 // NOTE: clientWidth/Height (size minus border) is 0 when current === AUTO so offsetHeight is used
1038 // reverting to auto from auto causes position stacking issues (old impl)
1039 if (current === AUTO || current.indexOf('%') > -1) {
1040 value = el['offset' + capped];
1042 if (sizeOffsets[0]) {
1043 value -= ComputedStyle.getPixel(el, 'padding' + sizeOffsets[0]);
1044 value -= ComputedStyle.getBorderWidth(el, 'border' + sizeOffsets[0] + 'Width', 1);
1047 if (sizeOffsets[1]) {
1048 value -= ComputedStyle.getPixel(el, 'padding' + sizeOffsets[1]);
1049 value -= ComputedStyle.getBorderWidth(el, 'border' + sizeOffsets[1] + 'Width', 1);
1052 } else { // use style.pixelWidth, etc. to convert to pixels
1053 // need to map style.width to currentStyle (no currentStyle.pixelWidth)
1054 if (!el.style[pixel] && !el.style[prop]) {
1055 el.style[prop] = current;
1057 value = el.style[pixel];
1069 getBorderWidth: function(el, property, omitUnit) {
1070 var unit = omitUnit ? '' : PX,
1071 current = el.currentStyle[property];
1073 if (current.indexOf(PX) < 0) { // look up keywords
1074 if (ComputedStyle.borderMap[current]) {
1075 current = ComputedStyle.borderMap[current];
1079 return (omitUnit) ? parseFloat(current) : current;
1082 getPixel: function(node, att) {
1083 // use pixelRight to convert to px
1085 style = _getStyleObj(node),
1086 styleRight = style.right,
1087 current = style[att];
1089 node.style.right = current;
1090 val = node.style.pixelRight;
1091 node.style.right = styleRight; // revert
1096 getMargin: function(node, att) {
1098 style = _getStyleObj(node);
1100 if (style[att] == AUTO) {
1103 val = ComputedStyle.getPixel(node, att);
1108 getVisibility: function(node, att) {
1110 while ( (current = node.currentStyle) && current[att] == 'inherit') { // NOTE: assignment in test
1111 node = node.parentNode;
1113 return (current) ? current[att] : VISIBLE;
1116 getColor: function(node, att) {
1117 var current = _getStyleObj(node)[att];
1119 if (!current || current === TRANSPARENT) {
1120 Y.DOM.elementByAxis(node, 'parentNode', null, function(parent) {
1121 current = _getStyleObj(parent)[att];
1122 if (current && current !== TRANSPARENT) {
1129 return Y.Color.toRGB(current);
1132 getBorderColor: function(node, att) {
1133 var current = _getStyleObj(node),
1134 val = current[att] || current.color;
1135 return Y.Color.toRGB(Y.Color.toHex(val));
1139 //fontSize: getPixelFont,
1142 // use alpha filter for IE opacity
1144 if (documentElement.style[OPACITY] === UNDEFINED &&
1145 documentElement[FILTERS]) {
1146 Y.DOM.CUSTOM_STYLES[OPACITY] = {
1147 get: function(node) {
1149 try { // will error if no DXImageTransform
1150 val = node[FILTERS]['DXImageTransform.Microsoft.Alpha'][OPACITY];
1153 try { // make sure its in the document
1154 val = node[FILTERS]('alpha')[OPACITY];
1161 set: function(node, val, style) {
1165 if (val === '') { // normalize inline style behavior
1166 styleObj = _getStyleObj(node);
1167 current = (OPACITY in styleObj) ? styleObj[OPACITY] : 1; // revert to original opacity
1171 if (typeof style[FILTER] == 'string') { // in case not appended
1172 style[FILTER] = 'alpha(' + OPACITY + '=' + val * 100 + ')';
1174 if (!node.currentStyle || !node.currentStyle[HAS_LAYOUT]) {
1175 style.zoom = 1; // needs layout
1185 document.createElement('div').style.height = '-1px';
1186 } catch(e) { // IE throws error on invalid style set; trap common cases
1187 Y.DOM.CUSTOM_STYLES.height = {
1188 set: function(node, val, style) {
1189 var floatVal = parseFloat(val);
1190 if (isNaN(floatVal) || floatVal >= 0) {
1197 Y.DOM.CUSTOM_STYLES.width = {
1198 set: function(node, val, style) {
1199 var floatVal = parseFloat(val);
1200 if (isNaN(floatVal) || floatVal >= 0) {
1208 // TODO: top, right, bottom, left
1209 IEComputed[WIDTH] = IEComputed[HEIGHT] = ComputedStyle.getOffset;
1211 IEComputed.color = IEComputed.backgroundColor = ComputedStyle.getColor;
1213 IEComputed[BORDER_WIDTH] = IEComputed[BORDER_TOP_WIDTH] = IEComputed[BORDER_RIGHT_WIDTH] =
1214 IEComputed[BORDER_BOTTOM_WIDTH] = IEComputed[BORDER_LEFT_WIDTH] =
1215 ComputedStyle.getBorderWidth;
1217 IEComputed.marginTop = IEComputed.marginRight = IEComputed.marginBottom =
1218 IEComputed.marginLeft = ComputedStyle.getMargin;
1220 IEComputed.visibility = ComputedStyle.getVisibility;
1221 IEComputed.borderColor = IEComputed.borderTopColor =
1222 IEComputed.borderRightColor = IEComputed.borderBottomColor =
1223 IEComputed.borderLeftColor = ComputedStyle.getBorderColor;
1225 if (!Y.config.win[GET_COMPUTED_STYLE]) {
1226 Y.DOM[GET_COMPUTED_STYLE] = ComputedStyle.get;
1229 Y.namespace('DOM.IE');
1230 Y.DOM.IE.COMPUTED = IEComputed;
1231 Y.DOM.IE.ComputedStyle = ComputedStyle;
1236 }, '3.0.0' ,{requires:['dom-base']});
1237 YUI.add('dom-screen', function(Y) {
1242 * Adds position and region management functionality to DOM.
1244 * @submodule dom-screen
1248 var DOCUMENT_ELEMENT = 'documentElement',
1249 COMPAT_MODE = 'compatMode',
1250 POSITION = 'position',
1252 RELATIVE = 'relative',
1255 _BACK_COMPAT = 'BackCompat',
1257 BORDER_LEFT_WIDTH = 'borderLeftWidth',
1258 BORDER_TOP_WIDTH = 'borderTopWidth',
1259 GET_BOUNDING_CLIENT_RECT = 'getBoundingClientRect',
1260 GET_COMPUTED_STYLE = 'getComputedStyle',
1262 // TODO: how about thead/tbody/tfoot/tr?
1263 // TODO: does caption matter?
1264 RE_TABLE = /^t(?:able|d|h)$/i;
1268 * Returns the inner height of the viewport (exludes scrollbar).
1270 * @return {Number} The current height of the viewport.
1272 winHeight: function(node) {
1273 var h = Y.DOM._getWinSize(node).height;
1278 * Returns the inner width of the viewport (exludes scrollbar).
1280 * @return {Number} The current width of the viewport.
1282 winWidth: function(node) {
1283 var w = Y.DOM._getWinSize(node).width;
1290 * @return {Number} The current height of the document.
1292 docHeight: function(node) {
1293 var h = Y.DOM._getDocSize(node).height;
1294 return Math.max(h, Y.DOM._getWinSize(node).height);
1300 * @return {Number} The current width of the document.
1302 docWidth: function(node) {
1303 var w = Y.DOM._getDocSize(node).width;
1304 return Math.max(w, Y.DOM._getWinSize(node).width);
1308 * Amount page has been scroll horizontally
1309 * @method docScrollX
1310 * @return {Number} The current amount the screen is scrolled horizontally.
1312 docScrollX: function(node) {
1313 var doc = Y.DOM._getDoc(node);
1314 return Math.max(doc[DOCUMENT_ELEMENT].scrollLeft, doc.body.scrollLeft);
1318 * Amount page has been scroll vertically
1319 * @method docScrollY
1320 * @return {Number} The current amount the screen is scrolled vertically.
1322 docScrollY: function(node) {
1323 var doc = Y.DOM._getDoc(node);
1324 return Math.max(doc[DOCUMENT_ELEMENT].scrollTop, doc.body.scrollTop);
1328 * Gets the current position of an element based on page coordinates.
1329 * Element must be part of the DOM tree to have page coordinates
1330 * (display:none or elements not appended return false).
1332 * @param element The target element
1333 * @return {Array} The XY position of the element
1335 TODO: test inDocument/display?
1338 if (document[DOCUMENT_ELEMENT][GET_BOUNDING_CLIENT_RECT]) {
1339 return function(node) {
1350 if (Y.DOM.inDoc(node)) {
1351 scrollLeft = Y.DOM.docScrollX(node);
1352 scrollTop = Y.DOM.docScrollY(node);
1353 box = node[GET_BOUNDING_CLIENT_RECT]();
1354 doc = Y.DOM._getDoc(node);
1355 xy = [box.left, box.top];
1360 mode = doc[COMPAT_MODE];
1361 bLeft = Y.DOM[GET_COMPUTED_STYLE](doc[DOCUMENT_ELEMENT], BORDER_LEFT_WIDTH);
1362 bTop = Y.DOM[GET_COMPUTED_STYLE](doc[DOCUMENT_ELEMENT], BORDER_TOP_WIDTH);
1364 if (Y.UA.ie === 6) {
1365 if (mode !== _BACK_COMPAT) {
1371 if ((mode == _BACK_COMPAT)) {
1372 if (bLeft !== MEDIUM) {
1373 off1 = parseInt(bLeft, 10);
1375 if (bTop !== MEDIUM) {
1376 off2 = parseInt(bTop, 10);
1385 if ((scrollTop || scrollLeft)) {
1386 xy[0] += scrollLeft;
1389 } else { // default to current offsets
1390 xy = Y.DOM._getOffset(node);
1396 return function(node) { // manually calculate by crawling up offsetParents
1397 //Calculate the Top and Left border sizes (assumes pixels)
1405 if (Y.DOM.inDoc(node)) {
1406 xy = [node.offsetLeft, node.offsetTop];
1408 // TODO: refactor with !! or just falsey
1409 bCheck = ((Y.UA.gecko || Y.UA.webkit > 519) ? true : false);
1411 // TODO: worth refactoring for TOP/LEFT only?
1412 while ((parentNode = parentNode.offsetParent)) {
1413 xy[0] += parentNode.offsetLeft;
1414 xy[1] += parentNode.offsetTop;
1416 xy = Y.DOM._calcBorders(parentNode, xy);
1420 // account for any scrolled ancestors
1421 if (Y.DOM.getStyle(node, POSITION) != FIXED) {
1424 while ((parentNode = parentNode.parentNode)) {
1425 scrollTop = parentNode.scrollTop;
1426 scrollLeft = parentNode.scrollLeft;
1428 //Firefox does something funky with borders when overflow is not visible.
1429 if (Y.UA.gecko && (Y.DOM.getStyle(parentNode, 'overflow') !== 'visible')) {
1430 xy = Y.DOM._calcBorders(parentNode, xy);
1434 if (scrollTop || scrollLeft) {
1435 xy[0] -= scrollLeft;
1439 xy[0] += Y.DOM.docScrollX(node);
1440 xy[1] += Y.DOM.docScrollY(node);
1443 //Fix FIXED position -- add scrollbars
1444 xy[0] += Y.DOM.docScrollX(node);
1445 xy[1] += Y.DOM.docScrollY(node);
1448 xy = Y.DOM._getOffset(node);
1455 }(),// NOTE: Executing for loadtime branching
1457 _getOffset: function(node) {
1462 pos = Y.DOM.getStyle(node, POSITION);
1464 parseInt(Y.DOM[GET_COMPUTED_STYLE](node, LEFT), 10),
1465 parseInt(Y.DOM[GET_COMPUTED_STYLE](node, TOP), 10)
1468 if ( isNaN(xy[0]) ) { // in case of 'auto'
1469 xy[0] = parseInt(Y.DOM.getStyle(node, LEFT), 10); // try inline
1470 if ( isNaN(xy[0]) ) { // default to offset value
1471 xy[0] = (pos === RELATIVE) ? 0 : node.offsetLeft || 0;
1475 if ( isNaN(xy[1]) ) { // in case of 'auto'
1476 xy[1] = parseInt(Y.DOM.getStyle(node, TOP), 10); // try inline
1477 if ( isNaN(xy[1]) ) { // default to offset value
1478 xy[1] = (pos === RELATIVE) ? 0 : node.offsetTop || 0;
1488 * Gets the current X position of an element based on page coordinates.
1489 * Element must be part of the DOM tree to have page coordinates
1490 * (display:none or elements not appended return false).
1492 * @param element The target element
1493 * @return {Int} The X position of the element
1496 getX: function(node) {
1497 return Y.DOM.getXY(node)[0];
1501 * Gets the current Y position of an element based on page coordinates.
1502 * Element must be part of the DOM tree to have page coordinates
1503 * (display:none or elements not appended return false).
1505 * @param element The target element
1506 * @return {Int} The Y position of the element
1509 getY: function(node) {
1510 return Y.DOM.getXY(node)[1];
1514 * Set the position of an html element in page coordinates.
1515 * The element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
1517 * @param element The target element
1518 * @param {Array} xy Contains X & Y values for new position (coordinates are page-based)
1519 * @param {Boolean} noRetry By default we try and set the position a second time if the first fails
1521 setXY: function(node, xy, noRetry) {
1522 var setStyle = Y.DOM.setStyle,
1529 pos = Y.DOM.getStyle(node, POSITION);
1531 delta = Y.DOM._getOffset(node);
1533 if (pos == 'static') { // default to relative
1535 setStyle(node, POSITION, pos);
1538 currentXY = Y.DOM.getXY(node);
1540 if (xy[0] !== null) {
1541 setStyle(node, LEFT, xy[0] - currentXY[0] + delta[0] + 'px');
1544 if (xy[1] !== null) {
1545 setStyle(node, TOP, xy[1] - currentXY[1] + delta[1] + 'px');
1549 newXY = Y.DOM.getXY(node);
1550 if (newXY[0] !== xy[0] || newXY[1] !== xy[1]) {
1551 Y.DOM.setXY(node, xy, true);
1560 * Set the X position of an html element in page coordinates, regardless of how the element is positioned.
1561 * The element(s) must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
1563 * @param element The target element
1564 * @param {Int} x The X values for new position (coordinates are page-based)
1566 setX: function(node, x) {
1567 return Y.DOM.setXY(node, [x, null]);
1571 * Set the Y position of an html element in page coordinates, regardless of how the element is positioned.
1572 * The element(s) must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
1574 * @param element The target element
1575 * @param {Int} y The Y values for new position (coordinates are page-based)
1577 setY: function(node, y) {
1578 return Y.DOM.setXY(node, [null, y]);
1581 _calcBorders: function(node, xy2) {
1582 var t = parseInt(Y.DOM[GET_COMPUTED_STYLE](node, BORDER_TOP_WIDTH), 10) || 0,
1583 l = parseInt(Y.DOM[GET_COMPUTED_STYLE](node, BORDER_LEFT_WIDTH), 10) || 0;
1585 if (RE_TABLE.test(node.tagName)) {
1595 _getWinSize: function(node) {
1596 var doc = Y.DOM._getDoc(),
1597 win = doc.defaultView || doc.parentWindow,
1598 mode = doc[COMPAT_MODE],
1599 h = win.innerHeight,
1601 root = doc[DOCUMENT_ELEMENT];
1603 if ( mode && !Y.UA.opera ) { // IE, Gecko
1604 if (mode != 'CSS1Compat') { // Quirks
1607 h = root.clientHeight;
1608 w = root.clientWidth;
1610 return { height: h, width: w };
1613 _getDocSize: function(node) {
1614 var doc = Y.DOM._getDoc(),
1615 root = doc[DOCUMENT_ELEMENT];
1617 if (doc[COMPAT_MODE] != 'CSS1Compat') {
1621 return { height: root.scrollHeight, width: root.scrollWidth };
1631 getOffsets = function(r1, r2) {
1632 var t = Math.max(r1[TOP], r2[TOP]),
1633 r = Math.min(r1[RIGHT], r2[RIGHT]),
1634 b = Math.min(r1[BOTTOM], r2[BOTTOM]),
1635 l = Math.max(r1[LEFT], r2[LEFT]),
1649 * Returns an Object literal containing the following about this element: (top, right, bottom, left)
1651 * @param {HTMLElement} element The DOM element.
1652 @return {Object} Object literal containing the following about this element: (top, right, bottom, left)
1654 region: function(node) {
1655 var xy = DOM.getXY(node),
1659 ret = DOM._getRegion(
1661 xy[0] + node.offsetWidth, // right
1662 xy[1] + node.offsetHeight, // bottom
1671 * Find the intersect information for the passes nodes.
1673 * @param {HTMLElement} element The first element
1674 * @param {HTMLElement | Object} element2 The element or region to check the interect with
1675 * @param {Object} altRegion An object literal containing the region for the first element if we already have the data (for performance i.e. DragDrop)
1676 @return {Object} Object literal containing the following intersection data: (top, right, bottom, left, area, yoff, xoff, inRegion)
1678 intersect: function(node, node2, altRegion) {
1679 var r = altRegion || DOM.region(node), region = {},
1684 region = DOM.region(n);
1685 } else if (Y.Lang.isObject(node2)) {
1691 off = getOffsets(region, r);
1695 bottom: off[BOTTOM],
1697 area: ((off[BOTTOM] - off[TOP]) * (off[RIGHT] - off[LEFT])),
1698 yoff: ((off[BOTTOM] - off[TOP])),
1699 xoff: (off[RIGHT] - off[LEFT]),
1700 inRegion: DOM.inRegion(node, node2, false, altRegion)
1705 * Check if any part of this node is in the passed region
1707 * @param {Object} node2 The node to get the region from or an Object literal of the region
1708 * $param {Boolean} all Should all of the node be inside the region
1709 * @param {Object} altRegion An object literal containing the region for this node if we already have the data (for performance i.e. DragDrop)
1710 * @return {Boolean} True if in region, false if not.
1712 inRegion: function(node, node2, all, altRegion) {
1714 r = altRegion || DOM.region(node),
1719 region = DOM.region(n);
1720 } else if (Y.Lang.isObject(node2)) {
1728 r[LEFT] >= region[LEFT] &&
1729 r[RIGHT] <= region[RIGHT] &&
1730 r[TOP] >= region[TOP] &&
1731 r[BOTTOM] <= region[BOTTOM] );
1733 off = getOffsets(region, r);
1734 if (off[BOTTOM] >= off[TOP] && off[RIGHT] >= off[LEFT]) {
1744 * Check if any part of this element is in the viewport
1745 * @method inViewportRegion
1746 * @param {HTMLElement} element The DOM element.
1747 * @param {Boolean} all Should all of the node be inside the region
1748 * @param {Object} altRegion An object literal containing the region for this node if we already have the data (for performance i.e. DragDrop)
1749 * @return {Boolean} True if in region, false if not.
1751 inViewportRegion: function(node, all, altRegion) {
1752 return DOM.inRegion(node, DOM.viewportRegion(node), all, altRegion);
1756 _getRegion: function(t, r, b, l) {
1759 region[TOP] = region[1] = t;
1760 region[LEFT] = region[0] = l;
1763 region.width = region[RIGHT] - region[LEFT];
1764 region.height = region[BOTTOM] - region[TOP];
1770 * Returns an Object literal containing the following about the visible region of viewport: (top, right, bottom, left)
1771 * @method viewportRegion
1772 @return {Object} Object literal containing the following about the visible region of the viewport: (top, right, bottom, left)
1774 viewportRegion: function(node) {
1775 node = node || Y.config.doc.documentElement;
1781 scrollX = DOM.docScrollX(node);
1782 scrollY = DOM.docScrollY(node);
1784 ret = DOM._getRegion(scrollY, // top
1785 DOM.winWidth(node) + scrollX, // right
1786 scrollY + DOM.winHeight(node), // bottom
1796 }, '3.0.0' ,{requires:['dom-base', 'dom-style']});
1797 YUI.add('selector-native', function(Y) {
1801 * The selector-native module provides support for native querySelector
1803 * @submodule selector-native
1808 * Provides support for using CSS selectors to query the DOM
1814 Y.namespace('Selector'); // allow native module to standalone
1816 var COMPARE_DOCUMENT_POSITION = 'compareDocumentPosition',
1817 OWNER_DOCUMENT = 'ownerDocument',
1818 TMP_PREFIX = 'yui-tmp-',
1826 _compare: ('sourceIndex' in document.documentElement) ?
1827 function(nodeA, nodeB) {
1828 var a = nodeA.sourceIndex,
1829 b = nodeB.sourceIndex;
1839 } : (document.documentElement[COMPARE_DOCUMENT_POSITION] ?
1840 function(nodeA, nodeB) {
1841 if (nodeA[COMPARE_DOCUMENT_POSITION](nodeB) & 4) {
1847 function(nodeA, nodeB) {
1848 var rangeA, rangeB, compare;
1849 if (nodeA && nodeB) {
1850 rangeA = nodeA[OWNER_DOCUMENT].createRange();
1851 rangeA.setStart(nodeA, 0);
1852 rangeB = nodeB[OWNER_DOCUMENT].createRange();
1853 rangeB.setStart(nodeB, 0);
1854 compare = rangeA.compareBoundaryPoints(1, rangeB); // 1 === Range.START_TO_END
1861 _sort: function(nodes) {
1863 nodes = Y.Array(nodes, 0, true);
1865 nodes.sort(Selector._compare);
1872 _deDupe: function(nodes) {
1876 for (i = 0; (node = nodes[i++]);) {
1878 ret[ret.length] = node;
1883 for (i = 0; (node = ret[i++]);) {
1885 node.removeAttribute('_found');
1892 * Retrieves a set of nodes based on a given CSS selector.
1895 * @param {string} selector The CSS Selector to test the node against.
1896 * @param {HTMLElement} root optional An HTMLElement to start the query from. Defaults to Y.config.doc
1897 * @param {Boolean} firstOnly optional Whether or not to return only the first match.
1898 * @return {Array} An array of nodes that match the given selector.
1901 query: function(selector, root, firstOnly, skipNative) {
1902 root = root || Y.config.doc;
1904 useNative = (Y.Selector.useNative && document.querySelector && !skipNative),
1905 queries = [[selector, root]],
1909 fn = (useNative) ? Y.Selector._nativeQuery : Y.Selector._bruteQuery;
1911 if (selector && fn) {
1912 // split group into seperate queries
1913 if (!skipNative && // already done if skipping
1914 (!useNative || root.tagName)) { // split native when element scoping is needed
1915 queries = Selector._splitQueries(selector, root);
1918 for (i = 0; (query = queries[i++]);) {
1919 result = fn(query[0], query[1], firstOnly);
1920 if (!firstOnly) { // coerce DOM Collection to Array
1921 result = Y.Array(result, 0, true);
1924 ret = ret.concat(result);
1928 if (queries.length > 1) { // remove dupes and sort by doc order
1929 ret = Selector._sort(Selector._deDupe(ret));
1933 return (firstOnly) ? (ret[0] || null) : ret;
1937 // allows element scoped queries to begin with combinator
1938 // e.g. query('> p', document.body) === query('body > p')
1939 _splitQueries: function(selector, node) {
1940 var groups = selector.split(','),
1946 // enforce for element scoping
1948 node.id = node.id || Y.guid();
1949 prefix = '#' + node.id + ' ';
1952 for (i = 0, len = groups.length; i < len; ++i) {
1953 selector = prefix + groups[i];
1954 queries.push([selector, node]);
1961 _nativeQuery: function(selector, root, one) {
1963 return root['querySelector' + (one ? '' : 'All')](selector);
1964 } catch(e) { // fallback to brute if available
1965 return Y.Selector.query(selector, root, one, true); // redo with skipNative true
1969 filter: function(nodes, selector) {
1973 if (nodes && selector) {
1974 for (i = 0; (node = nodes[i++]);) {
1975 if (Y.Selector.test(node, selector)) {
1976 ret[ret.length] = node;
1985 test: function(node, selector, root) {
1987 groups = selector.split(','),
1991 if (node && node.tagName) { // only test HTMLElements
1992 root = root || node.ownerDocument;
1995 node.id = TMP_PREFIX + g_counter++;
1997 for (i = 0; (group = groups[i++]);) { // TODO: off-dom test
1998 group += '#' + node.id; // add ID for uniqueness
1999 item = Y.Selector.query(group, root, true);
2000 ret = (item === node);
2011 Y.mix(Y.Selector, Selector, true);
2016 }, '3.0.0' ,{requires:['dom-base']});
2017 YUI.add('selector-css2', function(Y) {
2020 * The selector module provides helper methods allowing CSS2 Selectors to be used with DOM elements.
2022 * @submodule selector-css2
2027 * Provides helper methods for collecting and filtering DOM elements.
2030 var PARENT_NODE = 'parentNode',
2031 TAG_NAME = 'tagName',
2032 ATTRIBUTES = 'attributes',
2033 COMBINATOR = 'combinator',
2034 PSEUDOS = 'pseudos',
2036 Selector = Y.Selector,
2040 _children: function(node, tag) {
2041 var ret = node.children,
2047 if (node.children && tag && node.children.tags) {
2048 children = node.children.tags(tag);
2049 } else if ((!ret && node[TAG_NAME]) || (ret && tag)) { // only HTMLElements have children
2050 childNodes = ret || node.childNodes;
2052 for (i = 0; (child = childNodes[i++]);) {
2053 if (child.tagName) {
2054 if (!tag || tag === child.tagName) {
2068 pseudos: /:([\-\w]+(?:\(?:['"]?(.+)['"]?\)))*/i
2072 * Mapping of shorthand tokens to corresponding attribute selector
2073 * @property shorthand
2077 '\\#(-?[_a-z]+[-\\w]*)': '[id=$1]',
2078 '\\.(-?[_a-z]+[-\\w]*)': '[className~=$1]'
2082 * List of operators and corresponding boolean functions.
2083 * These functions are passed the attribute and the current node's value of the attribute.
2084 * @property operators
2088 '': function(node, attr) { return Y.DOM.getAttribute(node, attr) !== ''; }, // Just test for existence of attribute
2090 //'=': '^{val}$', // equality
2091 '~=': '(?:^|\\s+){val}(?:\\s+|$)', // space-delimited
2092 '|=': '^{val}-?' // optional hyphen-delimited
2096 'first-child': function(node) {
2097 return Y.Selector._children(node[PARENT_NODE])[0] === node;
2101 _bruteQuery: function(selector, root, firstOnly) {
2104 tokens = Selector._tokenize(selector),
2105 token = tokens[tokens.length - 1],
2106 rootDoc = Y.DOM._getDoc(root),
2112 // if we have an initial ID, set to root when in document
2113 if (tokens[0] && rootDoc === root &&
2114 (id = tokens[0].id) &&
2115 rootDoc.getElementById(id)) {
2116 root = rootDoc.getElementById(id);
2122 className = token.className;
2123 tagName = token.tagName || '*';
2127 if (rootDoc.getElementById(id)) { // if in document
2128 nodes = [rootDoc.getElementById(id)]; // TODO: DOM.byId?
2130 // try className if supported
2131 } else if (className) {
2132 nodes = root.getElementsByClassName(className);
2133 } else if (tagName) { // default to tagName
2134 nodes = root.getElementsByTagName(tagName || '*');
2138 ret = Selector._filterNodes(nodes, tokens, firstOnly);
2145 _filterNodes: function(nodes, tokens, firstOnly) {
2148 len = tokens.length,
2153 getters = Y.Selector.getters,
2159 //FUNCTION = 'function',
2165 for (i = 0; (tmpNode = node = nodes[i++]);) {
2170 while (tmpNode && tmpNode.tagName) {
2172 tests = token.tests;
2175 while ((test = tests[--j])) {
2177 if (getters[test[0]]) {
2178 value = getters[test[0]](tmpNode, test[0]);
2180 value = tmpNode[test[0]];
2181 // use getAttribute for non-standard attributes
2182 if (value === undefined && tmpNode.getAttribute) {
2183 value = tmpNode.getAttribute(test[0]);
2187 if ((operator === '=' && value !== test[2]) || // fast path for equality
2188 (operator.test && !operator.test(value)) || // regex test
2189 (operator.call && !operator(tmpNode, test[0]))) { // function test
2191 // skip non element nodes or non-matching tags
2192 if ((tmpNode = tmpNode[path])) {
2194 (!tmpNode.tagName ||
2195 (token.tagName && token.tagName !== tmpNode.tagName))
2197 tmpNode = tmpNode[path];
2205 n--; // move to next token
2206 // now that we've passed the test, move up the tree by combinator
2207 if (!pass && (combinator = token.combinator)) {
2208 path = combinator.axis;
2209 tmpNode = tmpNode[path];
2211 // skip non element nodes
2212 while (tmpNode && !tmpNode.tagName) {
2213 tmpNode = tmpNode[path];
2216 if (combinator.direct) { // one pass only
2220 } else { // success if we made it this far
2228 }// while (tmpNode = node = nodes[++i]);
2229 node = tmpNode = null;
2233 _getRegExp: function(str, flags) {
2234 var regexCache = Selector._regexCache;
2235 flags = flags || '';
2236 if (!regexCache[str + flags]) {
2237 regexCache[str + flags] = new RegExp(str, flags);
2239 return regexCache[str + flags];
2254 axis: 'previousSibling',
2262 re: /^\[([a-z]+\w*)+([~\|\^\$\*!=]=?)?['"]?([^\]]*?)['"]?\]/i,
2263 fn: function(match, token) {
2264 var operator = match[2] || '',
2265 operators = Y.Selector.operators,
2268 // add prefiltering for ID and CLASS
2269 if ((match[1] === 'id' && operator === '=') ||
2270 (match[1] === 'className' &&
2271 document.getElementsByClassName &&
2272 (operator === '~=' || operator === '='))) {
2273 token.prefilter = match[1];
2274 token[match[1]] = match[3];
2278 if (operator in operators) {
2279 test = operators[operator];
2280 if (typeof test === 'string') {
2281 test = Y.Selector._getRegExp(test.replace('{val}', match[3]));
2285 if (!token.last || token.prefilter !== match[1]) {
2286 return match.slice(1);
2293 re: /^((?:-?[_a-z]+[\w-]*)|\*)/i,
2294 fn: function(match, token) {
2295 var tag = match[1].toUpperCase();
2296 token.tagName = tag;
2298 if (tag !== '*' && (!token.last || token.prefilter)) {
2299 return [TAG_NAME, '=', tag];
2301 if (!token.prefilter) {
2302 token.prefilter = 'tagName';
2308 re: /^\s*([>+~]|\s)\s*/,
2309 fn: function(match, token) {
2314 re: /^:([\-\w]+)(?:\(['"]?(.+)['"]?\))*/i,
2315 fn: function(match, token) {
2316 var test = Selector[PSEUDOS][match[1]];
2317 if (test) { // reorder match array
2318 return [match[2], test];
2319 } else { // selector token not supported (possibly missing CSS3 module)
2326 _getToken: function(token) {
2338 Break selector into token units per simple selector.
2339 Combinator is attached to the previous token.
2341 _tokenize: function(selector) {
2342 selector = selector || '';
2343 selector = Selector._replaceShorthand(Y.Lang.trim(selector));
2344 var token = Selector._getToken(), // one token per simple selector (left selector holds combinator)
2345 query = selector, // original query for debug report
2346 tokens = [], // array of tokens
2347 found = false, // whether or not any matches were found this pass
2348 match, // the regex match
2353 Search for selector patterns, store, and strip them from the selector string
2354 until no patterns match (invalid selector) or we run out of chars.
2356 Multiple attributes and pseudos are allowed, in any order.
2358 'form:first-child[type=button]:not(button)[lang|=en]'
2362 found = false; // reset after full pass
2363 for (i = 0; (parser = Selector._parsers[i++]);) {
2364 if ( (match = parser.re.exec(selector)) ) { // note assignment
2365 if (parser !== COMBINATOR ) {
2366 token.selector = selector;
2368 selector = selector.replace(match[0], ''); // strip current match from selector
2369 if (!selector.length) {
2373 if (Selector._attrFilters[match[1]]) { // convert class to className, etc.
2374 match[1] = Selector._attrFilters[match[1]];
2377 test = parser.fn(match, token);
2378 if (test === false) { // selector not supported
2382 token.tests.push(test);
2385 if (!selector.length || parser.name === COMBINATOR) {
2387 token = Selector._getToken(token);
2388 if (parser.name === COMBINATOR) {
2389 token.combinator = Y.Selector.combinators[match[1]];
2395 } while (found && selector.length);
2397 if (!found || selector.length) { // not fully parsed
2403 _replaceShorthand: function(selector) {
2404 var shorthand = Selector.shorthand,
2405 attrs = selector.match(Selector._re.attr), // pull attributes to avoid false pos on "." and "#"
2406 pseudos = selector.match(Selector._re.pseudos), // pull attributes to avoid false pos on "." and "#"
2410 selector = selector.replace(Selector._re.pseudos, '!!REPLACED_PSEUDO!!');
2414 selector = selector.replace(Selector._re.attr, '!!REPLACED_ATTRIBUTE!!');
2417 for (re in shorthand) {
2418 if (shorthand.hasOwnProperty(re)) {
2419 selector = selector.replace(Selector._getRegExp(re, 'gi'), shorthand[re]);
2424 for (i = 0, len = attrs.length; i < len; ++i) {
2425 selector = selector.replace('!!REPLACED_ATTRIBUTE!!', attrs[i]);
2429 for (i = 0, len = pseudos.length; i < len; ++i) {
2430 selector = selector.replace('!!REPLACED_PSEUDO!!', pseudos[i]);
2437 'class': 'className',
2442 href: function(node, attr) {
2443 return Y.DOM.getAttribute(node, attr);
2448 Y.mix(Y.Selector, SelectorCSS2, true);
2449 Y.Selector.getters.src = Y.Selector.getters.rel = Y.Selector.getters.href;
2451 // IE wants class with native queries
2452 if (Y.Selector.useNative && document.querySelector) {
2453 Y.Selector.shorthand['\\.(-?[_a-z]+[-\\w]*)'] = '[class~=$1]';
2458 }, '3.0.0' ,{requires:['selector-native']});
2461 YUI.add('selector', function(Y){}, '3.0.0' ,{use:['selector-native', 'selector-css2']});
2465 YUI.add('dom', function(Y){}, '3.0.0' ,{use:['dom-base', 'dom-style', 'dom-screen', 'selector']});