2 Copyright (c) 2010, Yahoo! Inc. All rights reserved.
3 Code licensed under the BSD License:
4 http://developer.yahoo.com/yui/license.html
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.
22 * Provides DOM helper methods.
26 var NODE_TYPE = 'nodeType',
27 OWNER_DOCUMENT = 'ownerDocument',
28 DOCUMENT_ELEMENT = 'documentElement',
29 DEFAULT_VIEW = 'defaultView',
30 PARENT_WINDOW = 'parentWindow',
32 PARENT_NODE = 'parentNode',
33 FIRST_CHILD = 'firstChild',
34 PREVIOUS_SIBLING = 'previousSibling',
35 NEXT_SIBLING = 'nextSibling',
36 CONTAINS = 'contains',
37 COMPARE_DOCUMENT_POSITION = 'compareDocumentPosition',
41 documentElement = Y.config.doc.documentElement,
43 re_tag = /<([a-z]+)/i,
45 createFromDIV = function(html, tag) {
46 var div = Y.config.doc.createElement('div'),
50 if (!div.firstChild || div.firstChild.tagName !== tag.toUpperCase()) {
57 addFeature = Y.Features.add,
58 testFeature = Y.Features.test,
62 * Returns the HTMLElement with the given ID (Wrapper for document.getElementById).
64 * @param {String} id the id attribute
65 * @param {Object} doc optional The document to search. Defaults to current document
66 * @return {HTMLElement | null} The HTMLElement with the id, or null if none found.
68 byId: function(id, doc) {
69 // handle dupe IDs and IE name collision
70 return Y_DOM.allById(id, doc)[0] || null;
74 * Returns the text content of the HTMLElement.
76 * @param {HTMLElement} element The html element.
77 * @return {String} The text content of the element (includes text of any descending elements).
79 getText: (documentElement.textContent !== undefined) ?
83 ret = element.textContent;
86 } : function(element) {
89 ret = element.innerText || element.nodeValue; // might be a textNode
95 * Sets the text content of the HTMLElement.
97 * @param {HTMLElement} element The html element.
98 * @param {String} content The content to add.
100 setText: (documentElement.textContent !== undefined) ?
101 function(element, content) {
103 element.textContent = content;
105 } : function(element, content) {
106 if ('innerText' in element) {
107 element.innerText = content;
108 } else if ('nodeValue' in element) {
109 element.nodeValue = content;
115 * Finds the ancestor of the element.
117 * @param {HTMLElement} element The html element.
118 * @param {Function} fn optional An optional boolean test to apply.
119 * The optional function is passed the current DOM node being tested as its only argument.
120 * If no function is given, the parentNode is returned.
121 * @param {Boolean} testSelf optional Whether or not to include the element in the scan
122 * @return {HTMLElement | null} The matching DOM node or null if none found.
124 ancestor: function(element, fn, testSelf) {
127 ret = (!fn || fn(element)) ? element : null;
130 return ret || Y_DOM.elementByAxis(element, PARENT_NODE, fn, null);
134 * Finds the ancestors of the element.
136 * @param {HTMLElement} element The html element.
137 * @param {Function} fn optional An optional boolean test to apply.
138 * The optional function is passed the current DOM node being tested as its only argument.
139 * If no function is given, all ancestors are returned.
140 * @param {Boolean} testSelf optional Whether or not to include the element in the scan
141 * @return {Array} An array containing all matching DOM nodes.
143 ancestors: function(element, fn, testSelf) {
144 var ancestor = Y_DOM.ancestor.apply(Y_DOM, arguments),
145 ret = (ancestor) ? [ancestor] : [];
147 while ((ancestor = Y_DOM.ancestor(ancestor, fn))) {
149 ret.unshift(ancestor);
157 * Searches the element by the given axis for the first matching element.
158 * @method elementByAxis
159 * @param {HTMLElement} element The html element.
160 * @param {String} axis The axis to search (parentNode, nextSibling, previousSibling).
161 * @param {Function} fn optional An optional boolean test to apply.
162 * @param {Boolean} all optional Whether all node types should be returned, or just element nodes.
163 * The optional function is passed the current HTMLElement being tested as its only argument.
164 * If no function is given, the first element is returned.
165 * @return {HTMLElement | null} The matching element or null if none found.
167 elementByAxis: function(element, axis, fn, all) {
168 while (element && (element = element[axis])) { // NOTE: assignment
169 if ( (all || element[TAG_NAME]) && (!fn || fn(element)) ) {
177 * Determines whether or not one HTMLElement is or contains another HTMLElement.
179 * @param {HTMLElement} element The containing html element.
180 * @param {HTMLElement} needle The html element that may be contained.
181 * @return {Boolean} Whether or not the element is or contains the needle.
183 contains: function(element, needle) {
186 if ( !needle || !element || !needle[NODE_TYPE] || !element[NODE_TYPE]) {
188 } else if (element[CONTAINS]) {
189 if (Y.UA.opera || needle[NODE_TYPE] === 1) { // IE & SAF contains fail if needle not an ELEMENT_NODE
190 ret = element[CONTAINS](needle);
192 ret = Y_DOM._bruteContains(element, needle);
194 } else if (element[COMPARE_DOCUMENT_POSITION]) { // gecko
195 if (element === needle || !!(element[COMPARE_DOCUMENT_POSITION](needle) & 16)) {
204 * Determines whether or not the HTMLElement is part of the document.
206 * @param {HTMLElement} element The containing html element.
207 * @param {HTMLElement} doc optional The document to check.
208 * @return {Boolean} Whether or not the element is attached to the document.
210 inDoc: function(element, doc) {
214 if (element && element.nodeType) {
215 (doc) || (doc = element[OWNER_DOCUMENT]);
217 rootNode = doc[DOCUMENT_ELEMENT];
219 // contains only works with HTML_ELEMENT
220 if (rootNode && rootNode.contains && element.tagName) {
221 ret = rootNode.contains(element);
223 ret = Y_DOM.contains(rootNode, element);
231 allById: function(id, root) {
232 root = root || Y.config.doc;
238 if (root.querySelectorAll) {
239 ret = root.querySelectorAll('[id="' + id + '"]');
240 } else if (root.all) {
241 nodes = root.all(id);
244 // root.all may return HTMLElement or HTMLCollection.
245 // some elements are also HTMLCollection (FORM, SELECT).
246 if (nodes.nodeName) {
247 if (nodes.id === id) { // avoid false positive on name
249 nodes = EMPTY_ARRAY; // done, no need to filter
250 } else { // prep for filtering
256 // filter out matches on node.name
257 // and element.id as reference to element with id === 'id'
258 for (i = 0; node = nodes[i++];) {
259 if (node.id === id ||
260 (node.attributes && node.attributes.id &&
261 node.attributes.id.value === id)) {
268 ret = [Y_DOM._getDoc(root).getElementById(id)];
275 * Creates a new dom node using the provided markup string.
277 * @param {String} html The markup used to create the element
278 * @param {HTMLDocument} doc An optional document context
279 * @return {HTMLElement|DocumentFragment} returns a single HTMLElement
280 * when creating one node, and a documentFragment when creating
283 create: function(html, doc) {
284 if (typeof html === 'string') {
285 html = Y.Lang.trim(html); // match IE which trims whitespace from innerHTML
289 doc = doc || Y.config.doc;
290 var m = re_tag.exec(html),
291 create = Y_DOM._create,
292 custom = Y_DOM.creators,
297 if (html != undefined) { // not undefined or null
299 creator = custom[m[1].toLowerCase()];
300 if (typeof creator === 'function') {
307 nodes = create(html, doc, tag).childNodes;
309 if (nodes.length === 1) { // return single node, breaking parentNode ref from "fragment"
310 ret = nodes[0].parentNode.removeChild(nodes[0]);
311 } else if (nodes[0] && nodes[0].className === 'yui3-big-dummy') { // using dummy node to preserve some attributes (e.g. OPTION not selected)
312 if (nodes.length === 2) {
313 ret = nodes[0].nextSibling;
315 nodes[0].parentNode.removeChild(nodes[0]);
316 ret = Y_DOM._nl2frag(nodes, doc);
318 } else { // return multiple nodes as a fragment
319 ret = Y_DOM._nl2frag(nodes, doc);
326 _nl2frag: function(nodes, doc) {
330 if (nodes && (nodes.push || nodes.item) && nodes[0]) {
331 doc = doc || nodes[0].ownerDocument;
332 ret = doc.createDocumentFragment();
334 if (nodes.item) { // convert live list to static array
335 nodes = Y.Array(nodes, 0, true);
338 for (i = 0, len = nodes.length; i < len; i++) {
339 ret.appendChild(nodes[i]);
341 } // else inline with log for minification
346 CUSTOM_ATTRIBUTES: (!documentElement.hasAttribute) ? { // IE < 8
355 * Provides a normalized attribute interface.
356 * @method setAttibute
357 * @param {HTMLElement} el The target element for the attribute.
358 * @param {String} attr The attribute to set.
359 * @param {String} val The value of the attribute.
361 setAttribute: function(el, attr, val, ieAttr) {
362 if (el && attr && el.setAttribute) {
363 attr = Y_DOM.CUSTOM_ATTRIBUTES[attr] || attr;
364 el.setAttribute(attr, val, ieAttr);
370 * Provides a normalized attribute interface.
371 * @method getAttibute
372 * @param {HTMLElement} el The target element for the attribute.
373 * @param {String} attr The attribute to get.
374 * @return {String} The current value of the attribute.
376 getAttribute: function(el, attr, ieAttr) {
377 ieAttr = (ieAttr !== undefined) ? ieAttr : 2;
379 if (el && attr && el.getAttribute) {
380 attr = Y_DOM.CUSTOM_ATTRIBUTES[attr] || attr;
381 ret = el.getAttribute(attr, ieAttr);
384 ret = ''; // per DOM spec
390 isWindow: function(obj) {
391 return !!(obj && obj.alert && obj.document);
396 _create: function(html, doc, tag) {
399 var frag = Y_DOM._fragClones[tag];
401 frag = frag.cloneNode(false);
403 frag = Y_DOM._fragClones[tag] = doc.createElement(tag);
405 frag.innerHTML = html;
409 _removeChildNodes: function(node) {
410 while (node.firstChild) {
411 node.removeChild(node.firstChild);
416 * Inserts content in a node at the given location
418 * @param {HTMLElement} node The node to insert into
419 * @param {HTMLElement | Array | HTMLCollection} content The content to be inserted
420 * @param {HTMLElement} where Where to insert the content
421 * If no "where" is given, content is appended to the node
422 * Possible values for "where"
424 * <dt>HTMLElement</dt>
425 * <dd>The element to insert before</dd>
427 * <dd>Replaces the existing HTML</dd>
429 * <dd>Inserts before the existing HTML</dd>
431 * <dd>Inserts content before the node</dd>
433 * <dd>Inserts content after the node</dd>
436 addHTML: function(node, content, where) {
437 var nodeParent = node.parentNode,
444 if (content != undefined) { // not null or undefined (maybe 0)
445 if (content.nodeType) { // DOM node, just add it
447 } else if (typeof content == 'string' || typeof content == 'number') {
448 ret = newNode = Y_DOM.create(content);
449 } else if (content[0] && content[0].nodeType) { // array or collection
450 newNode = Y.config.doc.createDocumentFragment();
451 while ((item = content[i++])) {
452 newNode.appendChild(item); // append to fragment for insertion
458 if (where.nodeType) { // insert regardless of relationship to node
459 where.parentNode.insertBefore(newNode, where);
463 while (node.firstChild) {
464 node.removeChild(node.firstChild);
466 if (newNode) { // allow empty content to clear node
467 node.appendChild(newNode);
471 nodeParent.insertBefore(newNode, node);
474 if (node.nextSibling) { // IE errors if refNode is null
475 nodeParent.insertBefore(newNode, node.nextSibling);
477 nodeParent.appendChild(newNode);
481 node.appendChild(newNode);
484 } else if (newNode) {
485 node.appendChild(newNode);
495 getValue: function(node) {
496 var ret = '', // TODO: return null?
499 if (node && node[TAG_NAME]) {
500 getter = Y_DOM.VALUE_GETTERS[node[TAG_NAME].toLowerCase()];
509 // workaround for IE8 JSON stringify bug
510 // which converts empty string values to null
511 if (ret === EMPTY_STRING) {
512 ret = EMPTY_STRING; // for real
515 return (typeof ret === 'string') ? ret : '';
518 setValue: function(node, val) {
521 if (node && node[TAG_NAME]) {
522 setter = Y_DOM.VALUE_SETTERS[node[TAG_NAME].toLowerCase()];
532 siblings: function(node, fn) {
536 while ((sibling = sibling[PREVIOUS_SIBLING])) {
537 if (sibling[TAG_NAME] && (!fn || fn(sibling))) {
538 nodes.unshift(sibling);
543 while ((sibling = sibling[NEXT_SIBLING])) {
544 if (sibling[TAG_NAME] && (!fn || fn(sibling))) {
553 * Brute force version of contains.
554 * Used for browsers without contains support for non-HTMLElement Nodes (textNodes, etc).
555 * @method _bruteContains
557 * @param {HTMLElement} element The containing html element.
558 * @param {HTMLElement} needle The html element that may be contained.
559 * @return {Boolean} Whether or not the element is or contains the needle.
561 _bruteContains: function(element, needle) {
563 if (element === needle) {
566 needle = needle.parentNode;
571 // TODO: move to Lang?
573 * Memoizes dynamic regular expressions to boost runtime performance.
576 * @param {String} str The string to convert to a regular expression.
577 * @param {String} flags optional An optinal string of flags.
578 * @return {RegExp} An instance of RegExp
580 _getRegExp: function(str, flags) {
582 Y_DOM._regexCache = Y_DOM._regexCache || {};
583 if (!Y_DOM._regexCache[str + flags]) {
584 Y_DOM._regexCache[str + flags] = new RegExp(str, flags);
586 return Y_DOM._regexCache[str + flags];
589 // TODO: make getDoc/Win true privates?
591 * returns the appropriate document.
594 * @param {HTMLElement} element optional Target element.
595 * @return {Object} The document for the given element or the default document.
597 _getDoc: function(element) {
598 var doc = Y.config.doc;
600 doc = (element[NODE_TYPE] === 9) ? element : // element === document
601 element[OWNER_DOCUMENT] || // element === DOM node
602 element.document || // element === window
603 Y.config.doc; // default
610 * returns the appropriate window.
613 * @param {HTMLElement} element optional Target element.
614 * @return {Object} The window for the given element or the default window.
616 _getWin: function(element) {
617 var doc = Y_DOM._getDoc(element);
618 return doc[DEFAULT_VIEW] || doc[PARENT_WINDOW] || Y.config.win;
621 _batch: function(nodes, fn, arg1, arg2, arg3, etc) {
622 fn = (typeof fn === 'string') ? Y_DOM[fn] : fn;
624 args = Array.prototype.slice.call(arguments, 2),
630 while ((node = nodes[i++])) {
631 result = result = fn.call(Y_DOM, node, arg1, arg2, arg3, etc);
632 if (typeof result !== 'undefined') {
639 return (typeof ret !== 'undefined') ? ret : nodes;
642 wrap: function(node, html) {
643 var parent = Y.DOM.create(html),
644 nodes = parent.getElementsByTagName('*');
647 parent = nodes[nodes.length - 1];
650 if (node.parentNode) {
651 node.parentNode.replaceChild(parent, node);
653 parent.appendChild(node);
656 unwrap: function(node) {
657 var parent = node.parentNode,
658 lastChild = parent.lastChild,
659 node = parent.firstChild,
664 grandparent = parent.parentNode;
666 while (node !== lastChild) {
667 next = node.nextSibling;
668 grandparent.insertBefore(node, parent);
671 grandparent.replaceChild(lastChild, parent);
673 parent.removeChild(node);
678 generateID: function(el) {
692 addFeature('innerhtml', 'table', {
694 var node = Y.config.doc.createElement('table');
696 node.innerHTML = '<tbody></tbody>';
700 return (node.firstChild && node.firstChild.nodeName === 'TBODY');
704 addFeature('innerhtml-div', 'tr', {
706 return createFromDIV('<tr></tr>', 'tr');
710 addFeature('innerhtml-div', 'script', {
712 return createFromDIV('<script></script>', 'script');
716 addFeature('value-set', 'select', {
718 var node = Y.config.doc.createElement('select');
719 node.innerHTML = '<option>1</option><option>2</option>';
721 return (node.value && node.value === '2');
726 var creators = Y_DOM.creators,
727 create = Y_DOM.create,
728 re_tbody = /(?:\/(?:thead|tfoot|tbody|caption|col|colgroup)>)+\s*<tbody/,
730 TABLE_OPEN = '<table>',
731 TABLE_CLOSE = '</table>';
733 if (!testFeature('innerhtml', 'table')) {
734 // TODO: thead/tfoot with nested tbody
735 // IE adds TBODY when creating TABLE elements (which may share this impl)
736 creators.tbody = function(html, doc) {
737 var frag = create(TABLE_OPEN + html + TABLE_CLOSE, doc),
738 tb = frag.children.tags('tbody')[0];
740 if (frag.children.length > 1 && tb && !re_tbody.test(html)) {
741 tb[PARENT_NODE].removeChild(tb); // strip extraneous tbody
747 if (!testFeature('innerhtml-div', 'script')) {
748 creators.script = function(html, doc) {
749 var frag = doc.createElement('div');
751 frag.innerHTML = '-' + html;
752 frag.removeChild(frag[FIRST_CHILD]);
756 Y_DOM.creators.link = Y_DOM.creators.style = Y_DOM.creators.script;
760 if (!testFeature('value-set', 'select')) {
761 Y_DOM.VALUE_SETTERS.select = function(node, val) {
762 for (var i = 0, options = node.getElementsByTagName('option'), option;
763 option = options[i++];) {
764 if (Y_DOM.getValue(option) === val) {
765 option.selected = true;
766 //Y_DOM.setAttribute(option, 'selected', 'selected');
773 Y.mix(Y_DOM.VALUE_GETTERS, {
774 button: function(node) {
775 return (node.attributes && node.attributes.value) ? node.attributes.value.value : '';
779 Y.mix(Y_DOM.VALUE_SETTERS, {
780 // IE: node.value changes the button text, which should be handled via innerHTML
781 button: function(node, val) {
782 var attr = node.attributes.value;
784 attr = node[OWNER_DOCUMENT].createAttribute('value');
785 node.setAttributeNode(attr);
793 if (!testFeature('innerhtml-div', 'tr')) {
795 option: function(html, doc) {
796 return create('<select><option class="yui3-big-dummy" selected></option>' + html + '</select>', doc);
799 tr: function(html, doc) {
800 return create('<tbody>' + html + '</tbody>', doc);
803 td: function(html, doc) {
804 return create('<tr>' + html + '</tr>', doc);
807 col: function(html, doc) {
808 return create('<colgroup>' + html + '</colgroup>', doc);
817 thead: creators.tbody,
818 tfoot: creators.tbody,
819 caption: creators.tbody,
820 colgroup: creators.tbody,
821 optgroup: creators.option
825 Y.mix(Y_DOM.VALUE_GETTERS, {
826 option: function(node) {
827 var attrs = node.attributes;
828 return (attrs.value && attrs.value.specified) ? node.value : node.text;
831 select: function(node) {
832 var val = node.value,
833 options = node.options;
835 if (options && options.length) {
836 // TODO: implement multipe select
839 val = Y_DOM.getValue(options[node.selectedIndex]);
850 var addClass, hasClass, removeClass;
854 * Determines whether a DOM element has the given className.
857 * @param {HTMLElement} element The DOM element.
858 * @param {String} className the class name to search for
859 * @return {Boolean} Whether or not the element has the given class.
861 hasClass: function(node, className) {
862 var re = Y.DOM._getRegExp('(?:^|\\s+)' + className + '(?:\\s+|$)');
863 return re.test(node.className);
867 * Adds a class name to a given DOM element.
870 * @param {HTMLElement} element The DOM element.
871 * @param {String} className the class name to add to the class attribute
873 addClass: function(node, className) {
874 if (!Y.DOM.hasClass(node, className)) { // skip if already present
875 node.className = Y.Lang.trim([node.className, className].join(' '));
880 * Removes a class name from a given element.
881 * @method removeClass
883 * @param {HTMLElement} element The DOM element.
884 * @param {String} className the class name to remove from the class attribute
886 removeClass: function(node, className) {
887 if (className && hasClass(node, className)) {
888 node.className = Y.Lang.trim(node.className.replace(Y.DOM._getRegExp('(?:^|\\s+)' +
889 className + '(?:\\s+|$)'), ' '));
891 if ( hasClass(node, className) ) { // in case of multiple adjacent
892 removeClass(node, className);
898 * Replace a class with another class for a given element.
899 * If no oldClassName is present, the newClassName is simply added.
900 * @method replaceClass
902 * @param {HTMLElement} element The DOM element
903 * @param {String} oldClassName the class name to be replaced
904 * @param {String} newClassName the class name that will be replacing the old class name
906 replaceClass: function(node, oldC, newC) {
907 removeClass(node, oldC); // remove first in case oldC === newC
908 addClass(node, newC);
912 * If the className exists on the node it is removed, if it doesn't exist it is added.
913 * @method toggleClass
915 * @param {HTMLElement} element The DOM element
916 * @param {String} className the class name to be toggled
917 * @param {Boolean} addClass optional boolean to indicate whether class
918 * should be added or removed regardless of current state
920 toggleClass: function(node, className, force) {
921 var add = (force !== undefined) ? force :
922 !(hasClass(node, className));
925 addClass(node, className);
927 removeClass(node, className);
932 hasClass = Y.DOM.hasClass;
933 removeClass = Y.DOM.removeClass;
934 addClass = Y.DOM.addClass;
938 * Sets the width of the element to the given size, regardless
939 * of box model, border, padding, etc.
941 * @param {HTMLElement} element The DOM element.
942 * @param {String|Int} size The pixel height to size to
945 setWidth: function(node, size) {
946 Y.DOM._setSize(node, 'width', size);
950 * Sets the height of the element to the given size, regardless
951 * of box model, border, padding, etc.
953 * @param {HTMLElement} element The DOM element.
954 * @param {String|Int} size The pixel height to size to
957 setHeight: function(node, size) {
958 Y.DOM._setSize(node, 'height', size);
961 _setSize: function(node, prop, val) {
962 val = (val > 0) ? val : 0;
965 node.style[prop] = val + 'px';
966 size = (prop === 'height') ? node.offsetHeight : node.offsetWidth;
969 val = val - (size - val);
975 node.style[prop] = val + 'px';
981 }, '3.3.0' ,{requires:['oop']});
982 YUI.add('dom-style', function(Y) {
986 * Add style management functionality to DOM.
988 * @submodule dom-style
992 var DOCUMENT_ELEMENT = 'documentElement',
993 DEFAULT_VIEW = 'defaultView',
994 OWNER_DOCUMENT = 'ownerDocument',
997 CSS_FLOAT = 'cssFloat',
998 STYLE_FLOAT = 'styleFloat',
999 TRANSPARENT = 'transparent',
1000 GET_COMPUTED_STYLE = 'getComputedStyle',
1001 GET_BOUNDING_CLIENT_RECT = 'getBoundingClientRect',
1003 WINDOW = Y.config.win,
1004 DOCUMENT = Y.config.doc,
1005 UNDEFINED = undefined,
1009 TRANSFORM = 'transform',
1010 VENDOR_TRANSFORM = [
1016 re_color = /color$/i,
1017 re_unit = /width|height|top|left|right|bottom|margin|padding/i;
1019 Y.Array.each(VENDOR_TRANSFORM, function(val) {
1020 if (val in DOCUMENT[DOCUMENT_ELEMENT].style) {
1033 * Sets a style property for a given element.
1035 * @param {HTMLElement} An HTMLElement to apply the style to.
1036 * @param {String} att The style property to set.
1037 * @param {String|Number} val The value.
1039 setStyle: function(node, att, val, style) {
1040 style = style || node.style;
1041 var CUSTOM_STYLES = Y_DOM.CUSTOM_STYLES;
1044 if (val === null || val === '') { // normalize unsetting
1046 } else if (!isNaN(new Number(val)) && re_unit.test(att)) { // number values may need a unit
1047 val += Y_DOM.DEFAULT_UNIT;
1050 if (att in CUSTOM_STYLES) {
1051 if (CUSTOM_STYLES[att].set) {
1052 CUSTOM_STYLES[att].set(node, val, style);
1053 return; // NOTE: return
1054 } else if (typeof CUSTOM_STYLES[att] === 'string') {
1055 att = CUSTOM_STYLES[att];
1057 } else if (att === '') { // unset inline styles
1066 * Returns the current style value for the given property.
1068 * @param {HTMLElement} An HTMLElement to get the style from.
1069 * @param {String} att The style property to get.
1071 getStyle: function(node, att, style) {
1072 style = style || node.style;
1073 var CUSTOM_STYLES = Y_DOM.CUSTOM_STYLES,
1077 if (att in CUSTOM_STYLES) {
1078 if (CUSTOM_STYLES[att].get) {
1079 return CUSTOM_STYLES[att].get(node, att, style); // NOTE: return
1080 } else if (typeof CUSTOM_STYLES[att] === 'string') {
1081 att = CUSTOM_STYLES[att];
1085 if (val === '') { // TODO: is empty string sufficient?
1086 val = Y_DOM[GET_COMPUTED_STYLE](node, att);
1094 * Sets multiple style properties.
1096 * @param {HTMLElement} node An HTMLElement to apply the styles to.
1097 * @param {Object} hash An object literal of property:value pairs.
1099 setStyles: function(node, hash) {
1100 var style = node.style;
1101 Y.each(hash, function(v, n) {
1102 Y_DOM.setStyle(node, n, v, style);
1107 * Returns the computed style for the given node.
1108 * @method getComputedStyle
1109 * @param {HTMLElement} An HTMLElement to get the style from.
1110 * @param {String} att The style property to get.
1111 * @return {String} The computed value of the style property.
1113 getComputedStyle: function(node, att) {
1115 doc = node[OWNER_DOCUMENT];
1117 if (node[STYLE] && doc[DEFAULT_VIEW] && doc[DEFAULT_VIEW][GET_COMPUTED_STYLE]) {
1118 val = doc[DEFAULT_VIEW][GET_COMPUTED_STYLE](node, null)[att];
1124 // normalize reserved word float alternatives ("cssFloat" or "styleFloat")
1125 if (DOCUMENT[DOCUMENT_ELEMENT][STYLE][CSS_FLOAT] !== UNDEFINED) {
1126 Y_DOM.CUSTOM_STYLES[FLOAT] = CSS_FLOAT;
1127 } else if (DOCUMENT[DOCUMENT_ELEMENT][STYLE][STYLE_FLOAT] !== UNDEFINED) {
1128 Y_DOM.CUSTOM_STYLES[FLOAT] = STYLE_FLOAT;
1131 // fix opera computedStyle default color unit (convert to rgb)
1133 Y_DOM[GET_COMPUTED_STYLE] = function(node, att) {
1134 var view = node[OWNER_DOCUMENT][DEFAULT_VIEW],
1135 val = view[GET_COMPUTED_STYLE](node, '')[att];
1137 if (re_color.test(att)) {
1138 val = Y.Color.toRGB(val);
1146 // safari converts transparent to rgba(), others use "transparent"
1148 Y_DOM[GET_COMPUTED_STYLE] = function(node, att) {
1149 var view = node[OWNER_DOCUMENT][DEFAULT_VIEW],
1150 val = view[GET_COMPUTED_STYLE](node, '')[att];
1152 if (val === 'rgba(0, 0, 0, 0)') {
1161 Y.DOM._getAttrOffset = function(node, attr) {
1162 var val = Y.DOM[GET_COMPUTED_STYLE](node, attr),
1163 offsetParent = node.offsetParent,
1168 if (val === 'auto') {
1169 position = Y.DOM.getStyle(node, 'position');
1170 if (position === 'static' || position === 'relative') {
1172 } else if (offsetParent && offsetParent[GET_BOUNDING_CLIENT_RECT]) {
1173 parentOffset = offsetParent[GET_BOUNDING_CLIENT_RECT]()[attr];
1174 offset = node[GET_BOUNDING_CLIENT_RECT]()[attr];
1175 if (attr === 'left' || attr === 'top') {
1176 val = offset - parentOffset;
1178 val = parentOffset - node[GET_BOUNDING_CLIENT_RECT]()[attr];
1186 Y.DOM._getOffset = function(node) {
1191 pos = Y_DOM.getStyle(node, 'position');
1193 parseInt(Y_DOM[GET_COMPUTED_STYLE](node, 'left'), 10),
1194 parseInt(Y_DOM[GET_COMPUTED_STYLE](node, 'top'), 10)
1197 if ( isNaN(xy[0]) ) { // in case of 'auto'
1198 xy[0] = parseInt(Y_DOM.getStyle(node, 'left'), 10); // try inline
1199 if ( isNaN(xy[0]) ) { // default to offset value
1200 xy[0] = (pos === 'relative') ? 0 : node.offsetLeft || 0;
1204 if ( isNaN(xy[1]) ) { // in case of 'auto'
1205 xy[1] = parseInt(Y_DOM.getStyle(node, 'top'), 10); // try inline
1206 if ( isNaN(xy[1]) ) { // default to offset value
1207 xy[1] = (pos === 'relative') ? 0 : node.offsetTop || 0;
1216 Y_DOM.CUSTOM_STYLES.transform = {
1217 set: function(node, val, style) {
1218 style[TRANSFORM] = val;
1221 get: function(node, style) {
1222 return Y_DOM[GET_COMPUTED_STYLE](node, TRANSFORM);
1229 var PARSE_INT = parseInt,
1252 re_RGB: /^rgb\(([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\)$/i,
1253 re_hex: /^#?([0-9A-F]{2})([0-9A-F]{2})([0-9A-F]{2})$/i,
1254 re_hex3: /([0-9A-F])/gi,
1256 toRGB: function(val) {
1257 if (!Y.Color.re_RGB.test(val)) {
1258 val = Y.Color.toHex(val);
1261 if(Y.Color.re_hex.exec(val)) {
1263 PARSE_INT(RE.$1, 16),
1264 PARSE_INT(RE.$2, 16),
1265 PARSE_INT(RE.$3, 16)
1271 toHex: function(val) {
1272 val = Y.Color.KEYWORDS[val] || val;
1273 if (Y.Color.re_RGB.exec(val)) {
1275 Number(RE.$1).toString(16),
1276 Number(RE.$2).toString(16),
1277 Number(RE.$3).toString(16)
1280 for (var i = 0; i < val.length; i++) {
1281 if (val[i].length < 2) {
1282 val[i] = '0' + val[i];
1289 if (val.length < 6) {
1290 val = val.replace(Y.Color.re_hex3, '$1$1');
1293 if (val !== 'transparent' && val.indexOf('#') < 0) {
1297 return val.toUpperCase();
1304 }, '3.3.0' ,{requires:['dom-base']});
1305 YUI.add('dom-screen', function(Y) {
1310 * Adds position and region management functionality to DOM.
1312 * @submodule dom-screen
1316 var DOCUMENT_ELEMENT = 'documentElement',
1317 COMPAT_MODE = 'compatMode',
1318 POSITION = 'position',
1320 RELATIVE = 'relative',
1323 _BACK_COMPAT = 'BackCompat',
1325 BORDER_LEFT_WIDTH = 'borderLeftWidth',
1326 BORDER_TOP_WIDTH = 'borderTopWidth',
1327 GET_BOUNDING_CLIENT_RECT = 'getBoundingClientRect',
1328 GET_COMPUTED_STYLE = 'getComputedStyle',
1332 // TODO: how about thead/tbody/tfoot/tr?
1333 // TODO: does caption matter?
1334 RE_TABLE = /^t(?:able|d|h)$/i,
1339 if (Y.config.doc[COMPAT_MODE] !== 'quirks') {
1340 SCROLL_NODE = DOCUMENT_ELEMENT;
1342 SCROLL_NODE = 'body';
1348 * Returns the inner height of the viewport (exludes scrollbar).
1350 * @return {Number} The current height of the viewport.
1352 winHeight: function(node) {
1353 var h = Y_DOM._getWinSize(node).height;
1358 * Returns the inner width of the viewport (exludes scrollbar).
1360 * @return {Number} The current width of the viewport.
1362 winWidth: function(node) {
1363 var w = Y_DOM._getWinSize(node).width;
1370 * @return {Number} The current height of the document.
1372 docHeight: function(node) {
1373 var h = Y_DOM._getDocSize(node).height;
1374 return Math.max(h, Y_DOM._getWinSize(node).height);
1380 * @return {Number} The current width of the document.
1382 docWidth: function(node) {
1383 var w = Y_DOM._getDocSize(node).width;
1384 return Math.max(w, Y_DOM._getWinSize(node).width);
1388 * Amount page has been scroll horizontally
1389 * @method docScrollX
1390 * @return {Number} The current amount the screen is scrolled horizontally.
1392 docScrollX: function(node, doc) {
1393 doc = doc || (node) ? Y_DOM._getDoc(node) : Y.config.doc; // perf optimization
1394 var dv = doc.defaultView,
1395 pageOffset = (dv) ? dv.pageXOffset : 0;
1396 return Math.max(doc[DOCUMENT_ELEMENT].scrollLeft, doc.body.scrollLeft, pageOffset);
1400 * Amount page has been scroll vertically
1401 * @method docScrollY
1402 * @return {Number} The current amount the screen is scrolled vertically.
1404 docScrollY: function(node, doc) {
1405 doc = doc || (node) ? Y_DOM._getDoc(node) : Y.config.doc; // perf optimization
1406 var dv = doc.defaultView,
1407 pageOffset = (dv) ? dv.pageYOffset : 0;
1408 return Math.max(doc[DOCUMENT_ELEMENT].scrollTop, doc.body.scrollTop, pageOffset);
1412 * Gets the current position of an element based on page coordinates.
1413 * Element must be part of the DOM tree to have page coordinates
1414 * (display:none or elements not appended return false).
1416 * @param element The target element
1417 * @return {Array} The XY position of the element
1419 TODO: test inDocument/display?
1422 if (Y.config.doc[DOCUMENT_ELEMENT][GET_BOUNDING_CLIENT_RECT]) {
1423 return function(node) {
1435 if (node && node.tagName) {
1436 doc = node.ownerDocument;
1437 rootNode = doc[DOCUMENT_ELEMENT];
1439 // inline inDoc check for perf
1440 if (rootNode.contains) {
1441 inDoc = rootNode.contains(node);
1443 inDoc = Y.DOM.contains(rootNode, node);
1447 scrollLeft = (SCROLL_NODE) ? doc[SCROLL_NODE].scrollLeft : Y_DOM.docScrollX(node, doc);
1448 scrollTop = (SCROLL_NODE) ? doc[SCROLL_NODE].scrollTop : Y_DOM.docScrollY(node, doc);
1449 box = node[GET_BOUNDING_CLIENT_RECT]();
1450 xy = [box.left, box.top];
1455 mode = doc[COMPAT_MODE];
1456 bLeft = Y_DOM[GET_COMPUTED_STYLE](doc[DOCUMENT_ELEMENT], BORDER_LEFT_WIDTH);
1457 bTop = Y_DOM[GET_COMPUTED_STYLE](doc[DOCUMENT_ELEMENT], BORDER_TOP_WIDTH);
1459 if (Y.UA.ie === 6) {
1460 if (mode !== _BACK_COMPAT) {
1466 if ((mode == _BACK_COMPAT)) {
1467 if (bLeft !== MEDIUM) {
1468 off1 = parseInt(bLeft, 10);
1470 if (bTop !== MEDIUM) {
1471 off2 = parseInt(bTop, 10);
1480 if ((scrollTop || scrollLeft)) {
1481 if (!Y.UA.ios || (Y.UA.ios >= 4.2)) {
1482 xy[0] += scrollLeft;
1488 xy = Y_DOM._getOffset(node);
1494 return function(node) { // manually calculate by crawling up offsetParents
1495 //Calculate the Top and Left border sizes (assumes pixels)
1504 if (Y_DOM.inDoc(node)) {
1505 xy = [node.offsetLeft, node.offsetTop];
1506 doc = node.ownerDocument;
1508 // TODO: refactor with !! or just falsey
1509 bCheck = ((Y.UA.gecko || Y.UA.webkit > 519) ? true : false);
1511 // TODO: worth refactoring for TOP/LEFT only?
1512 while ((parentNode = parentNode.offsetParent)) {
1513 xy[0] += parentNode.offsetLeft;
1514 xy[1] += parentNode.offsetTop;
1516 xy = Y_DOM._calcBorders(parentNode, xy);
1520 // account for any scrolled ancestors
1521 if (Y_DOM.getStyle(node, POSITION) != FIXED) {
1524 while ((parentNode = parentNode.parentNode)) {
1525 scrollTop = parentNode.scrollTop;
1526 scrollLeft = parentNode.scrollLeft;
1528 //Firefox does something funky with borders when overflow is not visible.
1529 if (Y.UA.gecko && (Y_DOM.getStyle(parentNode, 'overflow') !== 'visible')) {
1530 xy = Y_DOM._calcBorders(parentNode, xy);
1534 if (scrollTop || scrollLeft) {
1535 xy[0] -= scrollLeft;
1539 xy[0] += Y_DOM.docScrollX(node, doc);
1540 xy[1] += Y_DOM.docScrollY(node, doc);
1543 //Fix FIXED position -- add scrollbars
1544 xy[0] += Y_DOM.docScrollX(node, doc);
1545 xy[1] += Y_DOM.docScrollY(node, doc);
1548 xy = Y_DOM._getOffset(node);
1555 }(),// NOTE: Executing for loadtime branching
1558 * Gets the current X position of an element based on page coordinates.
1559 * Element must be part of the DOM tree to have page coordinates
1560 * (display:none or elements not appended return false).
1562 * @param element The target element
1563 * @return {Int} The X position of the element
1566 getX: function(node) {
1567 return Y_DOM.getXY(node)[0];
1571 * Gets the current Y position of an element based on page coordinates.
1572 * Element must be part of the DOM tree to have page coordinates
1573 * (display:none or elements not appended return false).
1575 * @param element The target element
1576 * @return {Int} The Y position of the element
1579 getY: function(node) {
1580 return Y_DOM.getXY(node)[1];
1584 * Set the position of an html element in page coordinates.
1585 * The element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
1587 * @param element The target element
1588 * @param {Array} xy Contains X & Y values for new position (coordinates are page-based)
1589 * @param {Boolean} noRetry By default we try and set the position a second time if the first fails
1591 setXY: function(node, xy, noRetry) {
1592 var setStyle = Y_DOM.setStyle,
1599 pos = Y_DOM.getStyle(node, POSITION);
1601 delta = Y_DOM._getOffset(node);
1602 if (pos == 'static') { // default to relative
1604 setStyle(node, POSITION, pos);
1606 currentXY = Y_DOM.getXY(node);
1608 if (xy[0] !== null) {
1609 setStyle(node, LEFT, xy[0] - currentXY[0] + delta[0] + 'px');
1612 if (xy[1] !== null) {
1613 setStyle(node, TOP, xy[1] - currentXY[1] + delta[1] + 'px');
1617 newXY = Y_DOM.getXY(node);
1618 if (newXY[0] !== xy[0] || newXY[1] !== xy[1]) {
1619 Y_DOM.setXY(node, xy, true);
1628 * Set the X position of an html element in page coordinates, regardless of how the element is positioned.
1629 * The element(s) must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
1631 * @param element The target element
1632 * @param {Int} x The X values for new position (coordinates are page-based)
1634 setX: function(node, x) {
1635 return Y_DOM.setXY(node, [x, null]);
1639 * Set the Y position of an html element in page coordinates, regardless of how the element is positioned.
1640 * The element(s) must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
1642 * @param element The target element
1643 * @param {Int} y The Y values for new position (coordinates are page-based)
1645 setY: function(node, y) {
1646 return Y_DOM.setXY(node, [null, y]);
1651 * @description Swap the xy position with another node
1652 * @param {Node} node The node to swap with
1653 * @param {Node} otherNode The other node to swap with
1656 swapXY: function(node, otherNode) {
1657 var xy = Y_DOM.getXY(node);
1658 Y_DOM.setXY(node, Y_DOM.getXY(otherNode));
1659 Y_DOM.setXY(otherNode, xy);
1662 _calcBorders: function(node, xy2) {
1663 var t = parseInt(Y_DOM[GET_COMPUTED_STYLE](node, BORDER_TOP_WIDTH), 10) || 0,
1664 l = parseInt(Y_DOM[GET_COMPUTED_STYLE](node, BORDER_LEFT_WIDTH), 10) || 0;
1666 if (RE_TABLE.test(node.tagName)) {
1676 _getWinSize: function(node, doc) {
1677 doc = doc || (node) ? Y_DOM._getDoc(node) : Y.config.doc;
1678 var win = doc.defaultView || doc.parentWindow,
1679 mode = doc[COMPAT_MODE],
1680 h = win.innerHeight,
1682 root = doc[DOCUMENT_ELEMENT];
1684 if ( mode && !Y.UA.opera ) { // IE, Gecko
1685 if (mode != 'CSS1Compat') { // Quirks
1688 h = root.clientHeight;
1689 w = root.clientWidth;
1691 return { height: h, width: w };
1694 _getDocSize: function(node) {
1695 var doc = (node) ? Y_DOM._getDoc(node) : Y.config.doc,
1696 root = doc[DOCUMENT_ELEMENT];
1698 if (doc[COMPAT_MODE] != 'CSS1Compat') {
1702 return { height: root.scrollHeight, width: root.scrollWidth };
1713 getOffsets = function(r1, r2) {
1714 var t = Math.max(r1[TOP], r2[TOP]),
1715 r = Math.min(r1[RIGHT], r2[RIGHT]),
1716 b = Math.min(r1[BOTTOM], r2[BOTTOM]),
1717 l = Math.max(r1[LEFT], r2[LEFT]),
1731 * Returns an Object literal containing the following about this element: (top, right, bottom, left)
1734 * @param {HTMLElement} element The DOM element.
1735 * @return {Object} Object literal containing the following about this element: (top, right, bottom, left)
1737 region: function(node) {
1738 var xy = DOM.getXY(node),
1742 ret = DOM._getRegion(
1744 xy[0] + node.offsetWidth, // right
1745 xy[1] + node.offsetHeight, // bottom
1754 * Find the intersect information for the passes nodes.
1757 * @param {HTMLElement} element The first element
1758 * @param {HTMLElement | Object} element2 The element or region to check the interect with
1759 * @param {Object} altRegion An object literal containing the region for the first element if we already have the data (for performance i.e. DragDrop)
1760 * @return {Object} Object literal containing the following intersection data: (top, right, bottom, left, area, yoff, xoff, inRegion)
1762 intersect: function(node, node2, altRegion) {
1763 var r = altRegion || DOM.region(node), region = {},
1768 region = DOM.region(n);
1769 } else if (Y.Lang.isObject(node2)) {
1775 off = getOffsets(region, r);
1779 bottom: off[BOTTOM],
1781 area: ((off[BOTTOM] - off[TOP]) * (off[RIGHT] - off[LEFT])),
1782 yoff: ((off[BOTTOM] - off[TOP])),
1783 xoff: (off[RIGHT] - off[LEFT]),
1784 inRegion: DOM.inRegion(node, node2, false, altRegion)
1789 * Check if any part of this node is in the passed region
1792 * @param {Object} node2 The node to get the region from or an Object literal of the region
1793 * $param {Boolean} all Should all of the node be inside the region
1794 * @param {Object} altRegion An object literal containing the region for this node if we already have the data (for performance i.e. DragDrop)
1795 * @return {Boolean} True if in region, false if not.
1797 inRegion: function(node, node2, all, altRegion) {
1799 r = altRegion || DOM.region(node),
1804 region = DOM.region(n);
1805 } else if (Y.Lang.isObject(node2)) {
1813 r[LEFT] >= region[LEFT] &&
1814 r[RIGHT] <= region[RIGHT] &&
1815 r[TOP] >= region[TOP] &&
1816 r[BOTTOM] <= region[BOTTOM] );
1818 off = getOffsets(region, r);
1819 if (off[BOTTOM] >= off[TOP] && off[RIGHT] >= off[LEFT]) {
1829 * Check if any part of this element is in the viewport
1830 * @method inViewportRegion
1832 * @param {HTMLElement} element The DOM element.
1833 * @param {Boolean} all Should all of the node be inside the region
1834 * @param {Object} altRegion An object literal containing the region for this node if we already have the data (for performance i.e. DragDrop)
1835 * @return {Boolean} True if in region, false if not.
1837 inViewportRegion: function(node, all, altRegion) {
1838 return DOM.inRegion(node, DOM.viewportRegion(node), all, altRegion);
1842 _getRegion: function(t, r, b, l) {
1845 region[TOP] = region[1] = t;
1846 region[LEFT] = region[0] = l;
1849 region.width = region[RIGHT] - region[LEFT];
1850 region.height = region[BOTTOM] - region[TOP];
1856 * Returns an Object literal containing the following about the visible region of viewport: (top, right, bottom, left)
1857 * @method viewportRegion
1859 * @return {Object} Object literal containing the following about the visible region of the viewport: (top, right, bottom, left)
1861 viewportRegion: function(node) {
1862 node = node || Y.config.doc.documentElement;
1868 scrollX = DOM.docScrollX(node);
1869 scrollY = DOM.docScrollY(node);
1871 ret = DOM._getRegion(scrollY, // top
1872 DOM.winWidth(node) + scrollX, // right
1873 scrollY + DOM.winHeight(node), // bottom
1883 }, '3.3.0' ,{requires:['dom-base', 'dom-style', 'event-base']});
1884 YUI.add('selector-native', function(Y) {
1888 * The selector-native module provides support for native querySelector
1890 * @submodule selector-native
1895 * Provides support for using CSS selectors to query the DOM
1901 Y.namespace('Selector'); // allow native module to standalone
1903 var COMPARE_DOCUMENT_POSITION = 'compareDocumentPosition',
1904 OWNER_DOCUMENT = 'ownerDocument';
1911 _compare: ('sourceIndex' in Y.config.doc.documentElement) ?
1912 function(nodeA, nodeB) {
1913 var a = nodeA.sourceIndex,
1914 b = nodeB.sourceIndex;
1924 } : (Y.config.doc.documentElement[COMPARE_DOCUMENT_POSITION] ?
1925 function(nodeA, nodeB) {
1926 if (nodeA[COMPARE_DOCUMENT_POSITION](nodeB) & 4) {
1932 function(nodeA, nodeB) {
1933 var rangeA, rangeB, compare;
1934 if (nodeA && nodeB) {
1935 rangeA = nodeA[OWNER_DOCUMENT].createRange();
1936 rangeA.setStart(nodeA, 0);
1937 rangeB = nodeB[OWNER_DOCUMENT].createRange();
1938 rangeB.setStart(nodeB, 0);
1939 compare = rangeA.compareBoundaryPoints(1, rangeB); // 1 === Range.START_TO_END
1946 _sort: function(nodes) {
1948 nodes = Y.Array(nodes, 0, true);
1950 nodes.sort(Selector._compare);
1957 _deDupe: function(nodes) {
1961 for (i = 0; (node = nodes[i++]);) {
1963 ret[ret.length] = node;
1968 for (i = 0; (node = ret[i++]);) {
1970 node.removeAttribute('_found');
1977 * Retrieves a set of nodes based on a given CSS selector.
1980 * @param {string} selector The CSS Selector to test the node against.
1981 * @param {HTMLElement} root optional An HTMLElement to start the query from. Defaults to Y.config.doc
1982 * @param {Boolean} firstOnly optional Whether or not to return only the first match.
1983 * @return {Array} An array of nodes that match the given selector.
1986 query: function(selector, root, firstOnly, skipNative) {
1987 root = root || Y.config.doc;
1989 useNative = (Y.Selector.useNative && Y.config.doc.querySelector && !skipNative),
1990 queries = [[selector, root]],
1994 fn = (useNative) ? Y.Selector._nativeQuery : Y.Selector._bruteQuery;
1996 if (selector && fn) {
1997 // split group into seperate queries
1998 if (!skipNative && // already done if skipping
1999 (!useNative || root.tagName)) { // split native when element scoping is needed
2000 queries = Selector._splitQueries(selector, root);
2003 for (i = 0; (query = queries[i++]);) {
2004 result = fn(query[0], query[1], firstOnly);
2005 if (!firstOnly) { // coerce DOM Collection to Array
2006 result = Y.Array(result, 0, true);
2009 ret = ret.concat(result);
2013 if (queries.length > 1) { // remove dupes and sort by doc order
2014 ret = Selector._sort(Selector._deDupe(ret));
2018 return (firstOnly) ? (ret[0] || null) : ret;
2022 // allows element scoped queries to begin with combinator
2023 // e.g. query('> p', document.body) === query('body > p')
2024 _splitQueries: function(selector, node) {
2025 var groups = selector.split(','),
2031 // enforce for element scoping
2033 node.id = node.id || Y.guid();
2034 prefix = '[id="' + node.id + '"] ';
2037 for (i = 0, len = groups.length; i < len; ++i) {
2038 selector = prefix + groups[i];
2039 queries.push([selector, node]);
2046 _nativeQuery: function(selector, root, one) {
2047 if (Y.UA.webkit && selector.indexOf(':checked') > -1 &&
2048 (Y.Selector.pseudos && Y.Selector.pseudos.checked)) { // webkit (chrome, safari) fails to find "selected"
2049 return Y.Selector.query(selector, root, one, true); // redo with skipNative true to try brute query
2052 return root['querySelector' + (one ? '' : 'All')](selector);
2053 } catch(e) { // fallback to brute if available
2054 return Y.Selector.query(selector, root, one, true); // redo with skipNative true
2058 filter: function(nodes, selector) {
2062 if (nodes && selector) {
2063 for (i = 0; (node = nodes[i++]);) {
2064 if (Y.Selector.test(node, selector)) {
2065 ret[ret.length] = node;
2074 test: function(node, selector, root) {
2076 groups = selector.split(','),
2084 if (node && node.tagName) { // only test HTMLElements
2086 // we need a root if off-doc
2087 if (!root && !Y.DOM.inDoc(node)) {
2088 parent = node.parentNode;
2091 } else { // only use frag when no parent to query
2092 frag = node[OWNER_DOCUMENT].createDocumentFragment();
2093 frag.appendChild(node);
2098 root = root || node[OWNER_DOCUMENT];
2103 for (i = 0; (group = groups[i++]);) { // TODO: off-dom test
2104 group += '[id="' + node.id + '"]';
2105 items = Y.Selector.query(group, root);
2107 for (j = 0; item = items[j++];) {
2108 if (item === node) {
2118 if (useFrag) { // cleanup
2119 frag.removeChild(node);
2127 * A convenience function to emulate Y.Node's aNode.ancestor(selector).
2128 * @param {HTMLElement} element An HTMLElement to start the query from.
2129 * @param {String} selector The CSS selector to test the node against.
2130 * @return {HTMLElement} The ancestor node matching the selector, or null.
2131 * @param {Boolean} testSelf optional Whether or not to include the element in the scan
2135 ancestor: function (element, selector, testSelf) {
2136 return Y.DOM.ancestor(element, function(n) {
2137 return Y.Selector.test(n, selector);
2142 Y.mix(Y.Selector, Selector, true);
2147 }, '3.3.0' ,{requires:['dom-base']});
2148 YUI.add('selector-css2', function(Y) {
2151 * The selector module provides helper methods allowing CSS2 Selectors to be used with DOM elements.
2153 * @submodule selector-css2
2158 * Provides helper methods for collecting and filtering DOM elements.
2161 var PARENT_NODE = 'parentNode',
2162 TAG_NAME = 'tagName',
2163 ATTRIBUTES = 'attributes',
2164 COMBINATOR = 'combinator',
2165 PSEUDOS = 'pseudos',
2167 Selector = Y.Selector,
2170 _reRegExpTokens: /([\^\$\?\[\]\*\+\-\.\(\)\|\\])/, // TODO: move?
2172 _children: function(node, tag) {
2173 var ret = node.children,
2179 if (node.children && tag && node.children.tags) {
2180 children = node.children.tags(tag);
2181 } else if ((!ret && node[TAG_NAME]) || (ret && tag)) { // only HTMLElements have children
2182 childNodes = ret || node.childNodes;
2184 for (i = 0; (child = childNodes[i++]);) {
2185 if (child.tagName) {
2186 if (!tag || tag === child.tagName) {
2197 //attr: /(\[.*\])/g,
2198 attr: /(\[[^\]]*\])/g,
2199 pseudos: /:([\-\w]+(?:\(?:['"]?(.+)['"]?\)))*/i
2203 * Mapping of shorthand tokens to corresponding attribute selector
2204 * @property shorthand
2208 '\\#(-?[_a-z]+[-\\w]*)': '[id=$1]',
2209 '\\.(-?[_a-z]+[-\\w]*)': '[className~=$1]'
2213 * List of operators and corresponding boolean functions.
2214 * These functions are passed the attribute and the current node's value of the attribute.
2215 * @property operators
2219 '': function(node, attr) { return Y.DOM.getAttribute(node, attr) !== ''; }, // Just test for existence of attribute
2221 //'=': '^{val}$', // equality
2222 '~=': '(?:^|\\s+){val}(?:\\s+|$)', // space-delimited
2223 '|=': '^{val}-?' // optional hyphen-delimited
2227 'first-child': function(node) {
2228 return Y.Selector._children(node[PARENT_NODE])[0] === node;
2232 _bruteQuery: function(selector, root, firstOnly) {
2235 tokens = Selector._tokenize(selector),
2236 token = tokens[tokens.length - 1],
2237 rootDoc = Y.DOM._getDoc(root),
2244 // if we have an initial ID, set to root when in document
2246 if (tokens[0] && rootDoc === root &&
2247 (id = tokens[0].id) &&
2248 rootDoc.getElementById(id)) {
2249 root = rootDoc.getElementById(id);
2256 className = token.className;
2257 tagName = token.tagName || '*';
2259 if (root.getElementsByTagName) { // non-IE lacks DOM api on doc frags
2260 // try ID first, unless no root.all && root not in document
2261 // (root.all works off document, but not getElementById)
2262 // TODO: move to allById?
2263 if (id && (root.all || (root.nodeType === 9 || Y.DOM.inDoc(root)))) {
2264 nodes = Y.DOM.allById(id, root);
2266 } else if (className) {
2267 nodes = root.getElementsByClassName(className);
2268 } else { // default to tagName
2269 nodes = root.getElementsByTagName(tagName);
2272 } else { // brute getElementsByTagName('*')
2273 child = root.firstChild;
2275 if (child.tagName) { // only collect HTMLElements
2278 child = child.nextSilbing || child.firstChild;
2282 ret = Selector._filterNodes(nodes, tokens, firstOnly);
2289 _filterNodes: function(nodes, tokens, firstOnly) {
2292 len = tokens.length,
2297 getters = Y.Selector.getters,
2303 //FUNCTION = 'function',
2309 for (i = 0; (tmpNode = node = nodes[i++]);) {
2314 while (tmpNode && tmpNode.tagName) {
2316 tests = token.tests;
2319 while ((test = tests[--j])) {
2321 if (getters[test[0]]) {
2322 value = getters[test[0]](tmpNode, test[0]);
2324 value = tmpNode[test[0]];
2325 // use getAttribute for non-standard attributes
2326 if (value === undefined && tmpNode.getAttribute) {
2327 value = tmpNode.getAttribute(test[0]);
2331 if ((operator === '=' && value !== test[2]) || // fast path for equality
2332 (typeof operator !== 'string' && // protect against String.test monkey-patch (Moo)
2333 operator.test && !operator.test(value)) || // regex test
2334 (!operator.test && // protect against RegExp as function (webkit)
2335 typeof operator === 'function' && !operator(tmpNode, test[0]))) { // function test
2337 // skip non element nodes or non-matching tags
2338 if ((tmpNode = tmpNode[path])) {
2340 (!tmpNode.tagName ||
2341 (token.tagName && token.tagName !== tmpNode.tagName))
2343 tmpNode = tmpNode[path];
2351 n--; // move to next token
2352 // now that we've passed the test, move up the tree by combinator
2353 if (!pass && (combinator = token.combinator)) {
2354 path = combinator.axis;
2355 tmpNode = tmpNode[path];
2357 // skip non element nodes
2358 while (tmpNode && !tmpNode.tagName) {
2359 tmpNode = tmpNode[path];
2362 if (combinator.direct) { // one pass only
2366 } else { // success if we made it this far
2374 }// while (tmpNode = node = nodes[++i]);
2375 node = tmpNode = null;
2391 axis: 'previousSibling',
2399 re: /^\[(-?[a-z]+[\w\-]*)+([~\|\^\$\*!=]=?)?['"]?([^\]]*?)['"]?\]/i,
2400 fn: function(match, token) {
2401 var operator = match[2] || '',
2402 operators = Y.Selector.operators,
2405 // add prefiltering for ID and CLASS
2406 if ((match[1] === 'id' && operator === '=') ||
2407 (match[1] === 'className' &&
2408 Y.config.doc.documentElement.getElementsByClassName &&
2409 (operator === '~=' || operator === '='))) {
2410 token.prefilter = match[1];
2411 token[match[1]] = match[3];
2415 if (operator in operators) {
2416 test = operators[operator];
2417 if (typeof test === 'string') {
2418 match[3] = match[3].replace(Y.Selector._reRegExpTokens, '\\$1');
2419 test = Y.DOM._getRegExp(test.replace('{val}', match[3]));
2423 if (!token.last || token.prefilter !== match[1]) {
2424 return match.slice(1);
2431 re: /^((?:-?[_a-z]+[\w-]*)|\*)/i,
2432 fn: function(match, token) {
2433 var tag = match[1].toUpperCase();
2434 token.tagName = tag;
2436 if (tag !== '*' && (!token.last || token.prefilter)) {
2437 return [TAG_NAME, '=', tag];
2439 if (!token.prefilter) {
2440 token.prefilter = 'tagName';
2446 re: /^\s*([>+~]|\s)\s*/,
2447 fn: function(match, token) {
2452 re: /^:([\-\w]+)(?:\(['"]?(.+)['"]?\))*/i,
2453 fn: function(match, token) {
2454 var test = Selector[PSEUDOS][match[1]];
2455 if (test) { // reorder match array
2456 return [match[2], test];
2457 } else { // selector token not supported (possibly missing CSS3 module)
2464 _getToken: function(token) {
2476 Break selector into token units per simple selector.
2477 Combinator is attached to the previous token.
2479 _tokenize: function(selector) {
2480 selector = selector || '';
2481 selector = Selector._replaceShorthand(Y.Lang.trim(selector));
2482 var token = Selector._getToken(), // one token per simple selector (left selector holds combinator)
2483 query = selector, // original query for debug report
2484 tokens = [], // array of tokens
2485 found = false, // whether or not any matches were found this pass
2486 match, // the regex match
2491 Search for selector patterns, store, and strip them from the selector string
2492 until no patterns match (invalid selector) or we run out of chars.
2494 Multiple attributes and pseudos are allowed, in any order.
2496 'form:first-child[type=button]:not(button)[lang|=en]'
2500 found = false; // reset after full pass
2501 for (i = 0; (parser = Selector._parsers[i++]);) {
2502 if ( (match = parser.re.exec(selector)) ) { // note assignment
2503 if (parser.name !== COMBINATOR ) {
2504 token.selector = selector;
2506 selector = selector.replace(match[0], ''); // strip current match from selector
2507 if (!selector.length) {
2511 if (Selector._attrFilters[match[1]]) { // convert class to className, etc.
2512 match[1] = Selector._attrFilters[match[1]];
2515 test = parser.fn(match, token);
2516 if (test === false) { // selector not supported
2520 token.tests.push(test);
2523 if (!selector.length || parser.name === COMBINATOR) {
2525 token = Selector._getToken(token);
2526 if (parser.name === COMBINATOR) {
2527 token.combinator = Y.Selector.combinators[match[1]];
2533 } while (found && selector.length);
2535 if (!found || selector.length) { // not fully parsed
2541 _replaceShorthand: function(selector) {
2542 var shorthand = Selector.shorthand,
2543 attrs = selector.match(Selector._re.attr), // pull attributes to avoid false pos on "." and "#"
2544 pseudos = selector.match(Selector._re.pseudos), // pull attributes to avoid false pos on "." and "#"
2548 selector = selector.replace(Selector._re.pseudos, '!!REPLACED_PSEUDO!!');
2552 selector = selector.replace(Selector._re.attr, '!!REPLACED_ATTRIBUTE!!');
2555 for (re in shorthand) {
2556 if (shorthand.hasOwnProperty(re)) {
2557 selector = selector.replace(Y.DOM._getRegExp(re, 'gi'), shorthand[re]);
2562 for (i = 0, len = attrs.length; i < len; ++i) {
2563 selector = selector.replace('!!REPLACED_ATTRIBUTE!!', attrs[i]);
2567 for (i = 0, len = pseudos.length; i < len; ++i) {
2568 selector = selector.replace('!!REPLACED_PSEUDO!!', pseudos[i]);
2575 'class': 'className',
2580 href: function(node, attr) {
2581 return Y.DOM.getAttribute(node, attr);
2586 Y.mix(Y.Selector, SelectorCSS2, true);
2587 Y.Selector.getters.src = Y.Selector.getters.rel = Y.Selector.getters.href;
2589 // IE wants class with native queries
2590 if (Y.Selector.useNative && Y.config.doc.querySelector) {
2591 Y.Selector.shorthand['\\.(-?[_a-z]+[-\\w]*)'] = '[class~=$1]';
2596 }, '3.3.0' ,{requires:['selector-native']});
2599 YUI.add('selector', function(Y){}, '3.3.0' ,{use:['selector-native', 'selector-css2']});
2603 YUI.add('dom', function(Y){}, '3.3.0' ,{use:['dom-base', 'dom-style', 'dom-screen', 'selector']});