/files/yui/3.15.0/dom-base/dom-base.js
JavaScript | 702 lines | 469 code | 103 blank | 130 comment | 132 complexity | 741976b535d9413da70514d3ae5dff34 MD5 | raw file
- /*
- YUI 3.15.0 (build 834026e)
- Copyright 2014 Yahoo! Inc. All rights reserved.
- Licensed under the BSD License.
- http://yuilibrary.com/license/
- */
- YUI.add('dom-base', function (Y, NAME) {
- /**
- * @for DOM
- * @module dom
- */
- var documentElement = Y.config.doc.documentElement,
- Y_DOM = Y.DOM,
- TAG_NAME = 'tagName',
- OWNER_DOCUMENT = 'ownerDocument',
- EMPTY_STRING = '',
- addFeature = Y.Features.add,
- testFeature = Y.Features.test;
- Y.mix(Y_DOM, {
- /**
- * Returns the text content of the HTMLElement.
- * @method getText
- * @param {HTMLElement} element The html element.
- * @return {String} The text content of the element (includes text of any descending elements).
- */
- getText: (documentElement.textContent !== undefined) ?
- function(element) {
- var ret = '';
- if (element) {
- ret = element.textContent;
- }
- return ret || '';
- } : function(element) {
- var ret = '';
- if (element) {
- ret = element.innerText || element.nodeValue; // might be a textNode
- }
- return ret || '';
- },
- /**
- * Sets the text content of the HTMLElement.
- * @method setText
- * @param {HTMLElement} element The html element.
- * @param {String} content The content to add.
- */
- setText: (documentElement.textContent !== undefined) ?
- function(element, content) {
- if (element) {
- element.textContent = content;
- }
- } : function(element, content) {
- if ('innerText' in element) {
- element.innerText = content;
- } else if ('nodeValue' in element) {
- element.nodeValue = content;
- }
- },
- CUSTOM_ATTRIBUTES: (!documentElement.hasAttribute) ? { // IE < 8
- 'for': 'htmlFor',
- 'class': 'className'
- } : { // w3c
- 'htmlFor': 'for',
- 'className': 'class'
- },
- /**
- * Provides a normalized attribute interface.
- * @method setAttribute
- * @param {HTMLElement} el The target element for the attribute.
- * @param {String} attr The attribute to set.
- * @param {String} val The value of the attribute.
- */
- setAttribute: function(el, attr, val, ieAttr) {
- if (el && attr && el.setAttribute) {
- attr = Y_DOM.CUSTOM_ATTRIBUTES[attr] || attr;
- el.setAttribute(attr, val, ieAttr);
- }
- },
- /**
- * Provides a normalized attribute interface.
- * @method getAttribute
- * @param {HTMLElement} el The target element for the attribute.
- * @param {String} attr The attribute to get.
- * @return {String} The current value of the attribute.
- */
- getAttribute: function(el, attr, ieAttr) {
- ieAttr = (ieAttr !== undefined) ? ieAttr : 2;
- var ret = '';
- if (el && attr && el.getAttribute) {
- attr = Y_DOM.CUSTOM_ATTRIBUTES[attr] || attr;
- // BUTTON value issue for IE < 8
- ret = (el.tagName === "BUTTON" && attr === 'value') ? Y_DOM.getValue(el) : el.getAttribute(attr, ieAttr);
- if (ret === null) {
- ret = ''; // per DOM spec
- }
- }
- return ret;
- },
- VALUE_SETTERS: {},
- VALUE_GETTERS: {},
- getValue: function(node) {
- var ret = '', // TODO: return null?
- getter;
- if (node && node[TAG_NAME]) {
- getter = Y_DOM.VALUE_GETTERS[node[TAG_NAME].toLowerCase()];
- if (getter) {
- ret = getter(node);
- } else {
- ret = node.value;
- }
- }
- // workaround for IE8 JSON stringify bug
- // which converts empty string values to null
- if (ret === EMPTY_STRING) {
- ret = EMPTY_STRING; // for real
- }
- return (typeof ret === 'string') ? ret : '';
- },
- setValue: function(node, val) {
- var setter;
- if (node && node[TAG_NAME]) {
- setter = Y_DOM.VALUE_SETTERS[node[TAG_NAME].toLowerCase()];
- val = (val === null) ? '' : val;
- if (setter) {
- setter(node, val);
- } else {
- node.value = val;
- }
- }
- },
- creators: {}
- });
- addFeature('value-set', 'select', {
- test: function() {
- var node = Y.config.doc.createElement('select');
- node.innerHTML = '<option>1</option><option>2</option>';
- node.value = '2';
- return (node.value && node.value === '2');
- }
- });
- if (!testFeature('value-set', 'select')) {
- Y_DOM.VALUE_SETTERS.select = function(node, val) {
- for (var i = 0, options = node.getElementsByTagName('option'), option;
- option = options[i++];) {
- if (Y_DOM.getValue(option) === val) {
- option.selected = true;
- //Y_DOM.setAttribute(option, 'selected', 'selected');
- break;
- }
- }
- };
- }
- Y.mix(Y_DOM.VALUE_GETTERS, {
- button: function(node) {
- return (node.attributes && node.attributes.value) ? node.attributes.value.value : '';
- }
- });
- Y.mix(Y_DOM.VALUE_SETTERS, {
- // IE: node.value changes the button text, which should be handled via innerHTML
- button: function(node, val) {
- var attr = node.attributes.value;
- if (!attr) {
- attr = node[OWNER_DOCUMENT].createAttribute('value');
- node.setAttributeNode(attr);
- }
- attr.value = val;
- }
- });
- Y.mix(Y_DOM.VALUE_GETTERS, {
- option: function(node) {
- var attrs = node.attributes;
- return (attrs.value && attrs.value.specified) ? node.value : node.text;
- },
- select: function(node) {
- var val = node.value,
- options = node.options;
- if (options && options.length) {
- // TODO: implement multipe select
- if (node.multiple) {
- } else if (node.selectedIndex > -1) {
- val = Y_DOM.getValue(options[node.selectedIndex]);
- }
- }
- return val;
- }
- });
- var addClass, hasClass, removeClass;
- Y.mix(Y.DOM, {
- /**
- * Determines whether a DOM element has the given className.
- * @method hasClass
- * @for DOM
- * @param {HTMLElement} element The DOM element.
- * @param {String} className the class name to search for
- * @return {Boolean} Whether or not the element has the given class.
- */
- hasClass: function(node, className) {
- var re = Y.DOM._getRegExp('(?:^|\\s+)' + className + '(?:\\s+|$)');
- return re.test(node.className);
- },
- /**
- * Adds a class name to a given DOM element.
- * @method addClass
- * @for DOM
- * @param {HTMLElement} element The DOM element.
- * @param {String} className the class name to add to the class attribute
- */
- addClass: function(node, className) {
- if (!Y.DOM.hasClass(node, className)) { // skip if already present
- node.className = Y.Lang.trim([node.className, className].join(' '));
- }
- },
- /**
- * Removes a class name from a given element.
- * @method removeClass
- * @for DOM
- * @param {HTMLElement} element The DOM element.
- * @param {String} className the class name to remove from the class attribute
- */
- removeClass: function(node, className) {
- if (className && hasClass(node, className)) {
- node.className = Y.Lang.trim(node.className.replace(Y.DOM._getRegExp('(?:^|\\s+)' +
- className + '(?:\\s+|$)'), ' '));
- if ( hasClass(node, className) ) { // in case of multiple adjacent
- removeClass(node, className);
- }
- }
- },
- /**
- * Replace a class with another class for a given element.
- * If no oldClassName is present, the newClassName is simply added.
- * @method replaceClass
- * @for DOM
- * @param {HTMLElement} element The DOM element
- * @param {String} oldClassName the class name to be replaced
- * @param {String} newClassName the class name that will be replacing the old class name
- */
- replaceClass: function(node, oldC, newC) {
- removeClass(node, oldC); // remove first in case oldC === newC
- addClass(node, newC);
- },
- /**
- * If the className exists on the node it is removed, if it doesn't exist it is added.
- * @method toggleClass
- * @for DOM
- * @param {HTMLElement} element The DOM element
- * @param {String} className the class name to be toggled
- * @param {Boolean} addClass optional boolean to indicate whether class
- * should be added or removed regardless of current state
- */
- toggleClass: function(node, className, force) {
- var add = (force !== undefined) ? force :
- !(hasClass(node, className));
- if (add) {
- addClass(node, className);
- } else {
- removeClass(node, className);
- }
- }
- });
- hasClass = Y.DOM.hasClass;
- removeClass = Y.DOM.removeClass;
- addClass = Y.DOM.addClass;
- var re_tag = /<([a-z]+)/i,
- Y_DOM = Y.DOM,
- addFeature = Y.Features.add,
- testFeature = Y.Features.test,
- creators = {},
- createFromDIV = function(html, tag) {
- var div = Y.config.doc.createElement('div'),
- ret = true;
- div.innerHTML = html;
- if (!div.firstChild || div.firstChild.tagName !== tag.toUpperCase()) {
- ret = false;
- }
- return ret;
- },
- re_tbody = /(?:\/(?:thead|tfoot|tbody|caption|col|colgroup)>)+\s*<tbody/,
- TABLE_OPEN = '<table>',
- TABLE_CLOSE = '</table>',
-
- selectedIndex;
- Y.mix(Y.DOM, {
- _fragClones: {},
- _create: function(html, doc, tag) {
- tag = tag || 'div';
- var frag = Y_DOM._fragClones[tag];
- if (frag) {
- frag = frag.cloneNode(false);
- } else {
- frag = Y_DOM._fragClones[tag] = doc.createElement(tag);
- }
- frag.innerHTML = html;
- return frag;
- },
- _children: function(node, tag) {
- var i = 0,
- children = node.children,
- childNodes,
- hasComments,
- child;
- if (children && children.tags) { // use tags filter when possible
- if (tag) {
- children = node.children.tags(tag);
- } else { // IE leaks comments into children
- hasComments = children.tags('!').length;
- }
- }
- if (!children || (!children.tags && tag) || hasComments) {
- childNodes = children || node.childNodes;
- children = [];
- while ((child = childNodes[i++])) {
- if (child.nodeType === 1) {
- if (!tag || tag === child.tagName) {
- children.push(child);
- }
- }
- }
- }
- return children || [];
- },
- /**
- * Creates a new dom node using the provided markup string.
- * @method create
- * @param {String} html The markup used to create the element
- * @param {HTMLDocument} doc An optional document context
- * @return {HTMLElement|DocumentFragment} returns a single HTMLElement
- * when creating one node, and a documentFragment when creating
- * multiple nodes.
- */
- create: function(html, doc) {
- if (typeof html === 'string') {
- html = Y.Lang.trim(html); // match IE which trims whitespace from innerHTML
- }
- doc = doc || Y.config.doc;
- var m = re_tag.exec(html),
- create = Y_DOM._create,
- custom = creators,
- ret = null,
- creator, tag, node, nodes;
- if (html != undefined) { // not undefined or null
- if (m && m[1]) {
- creator = custom[m[1].toLowerCase()];
- if (typeof creator === 'function') {
- create = creator;
- } else {
- tag = creator;
- }
- }
-
- node = create(html, doc, tag);
- nodes = node.childNodes;
- if (nodes.length === 1) { // return single node, breaking parentNode ref from "fragment"
- ret = node.removeChild(nodes[0]);
- } else if (nodes[0] && nodes[0].className === 'yui3-big-dummy') { // using dummy node to preserve some attributes (e.g. OPTION not selected)
- selectedIndex = node.selectedIndex;
-
- if (nodes.length === 2) {
- ret = nodes[0].nextSibling;
- } else {
- node.removeChild(nodes[0]);
- ret = Y_DOM._nl2frag(nodes, doc);
- }
- } else { // return multiple nodes as a fragment
- ret = Y_DOM._nl2frag(nodes, doc);
- }
- }
- return ret;
- },
- _nl2frag: function(nodes, doc) {
- var ret = null,
- i, len;
- if (nodes && (nodes.push || nodes.item) && nodes[0]) {
- doc = doc || nodes[0].ownerDocument;
- ret = doc.createDocumentFragment();
- if (nodes.item) { // convert live list to static array
- nodes = Y.Array(nodes, 0, true);
- }
- for (i = 0, len = nodes.length; i < len; i++) {
- ret.appendChild(nodes[i]);
- }
- } // else inline with log for minification
- return ret;
- },
- /**
- * Inserts content in a node at the given location
- * @method addHTML
- * @param {HTMLElement} node The node to insert into
- * @param {HTMLElement | Array | HTMLCollection} content The content to be inserted
- * @param {HTMLElement} where Where to insert the content
- * If no "where" is given, content is appended to the node
- * Possible values for "where"
- * <dl>
- * <dt>HTMLElement</dt>
- * <dd>The element to insert before</dd>
- * <dt>"replace"</dt>
- * <dd>Replaces the existing HTML</dd>
- * <dt>"before"</dt>
- * <dd>Inserts before the existing HTML</dd>
- * <dt>"before"</dt>
- * <dd>Inserts content before the node</dd>
- * <dt>"after"</dt>
- * <dd>Inserts content after the node</dd>
- * </dl>
- */
- addHTML: function(node, content, where) {
- var nodeParent = node.parentNode,
- i = 0,
- item,
- ret = content,
- newNode;
- if (content != undefined) { // not null or undefined (maybe 0)
- if (content.nodeType) { // DOM node, just add it
- newNode = content;
- } else if (typeof content == 'string' || typeof content == 'number') {
- ret = newNode = Y_DOM.create(content);
- } else if (content[0] && content[0].nodeType) { // array or collection
- newNode = Y.config.doc.createDocumentFragment();
- while ((item = content[i++])) {
- newNode.appendChild(item); // append to fragment for insertion
- }
- }
- }
- if (where) {
- if (newNode && where.parentNode) { // insert regardless of relationship to node
- where.parentNode.insertBefore(newNode, where);
- } else {
- switch (where) {
- case 'replace':
- while (node.firstChild) {
- node.removeChild(node.firstChild);
- }
- if (newNode) { // allow empty content to clear node
- node.appendChild(newNode);
- }
- break;
- case 'before':
- if (newNode) {
- nodeParent.insertBefore(newNode, node);
- }
- break;
- case 'after':
- if (newNode) {
- if (node.nextSibling) { // IE errors if refNode is null
- nodeParent.insertBefore(newNode, node.nextSibling);
- } else {
- nodeParent.appendChild(newNode);
- }
- }
- break;
- default:
- if (newNode) {
- node.appendChild(newNode);
- }
- }
- }
- } else if (newNode) {
- node.appendChild(newNode);
- }
- // `select` elements are the only elements with `selectedIndex`.
- // Don't grab the dummy `option` element's `selectedIndex`.
- if (node.nodeName == "SELECT" && selectedIndex > 0) {
- node.selectedIndex = selectedIndex - 1;
- }
-
- return ret;
- },
- wrap: function(node, html) {
- var parent = (html && html.nodeType) ? html : Y.DOM.create(html),
- nodes = parent.getElementsByTagName('*');
- if (nodes.length) {
- parent = nodes[nodes.length - 1];
- }
- if (node.parentNode) {
- node.parentNode.replaceChild(parent, node);
- }
- parent.appendChild(node);
- },
- unwrap: function(node) {
- var parent = node.parentNode,
- lastChild = parent.lastChild,
- next = node,
- grandparent;
- if (parent) {
- grandparent = parent.parentNode;
- if (grandparent) {
- node = parent.firstChild;
- while (node !== lastChild) {
- next = node.nextSibling;
- grandparent.insertBefore(node, parent);
- node = next;
- }
- grandparent.replaceChild(lastChild, parent);
- } else {
- parent.removeChild(node);
- }
- }
- }
- });
- addFeature('innerhtml', 'table', {
- test: function() {
- var node = Y.config.doc.createElement('table');
- try {
- node.innerHTML = '<tbody></tbody>';
- } catch(e) {
- return false;
- }
- return (node.firstChild && node.firstChild.nodeName === 'TBODY');
- }
- });
- addFeature('innerhtml-div', 'tr', {
- test: function() {
- return createFromDIV('<tr></tr>', 'tr');
- }
- });
- addFeature('innerhtml-div', 'script', {
- test: function() {
- return createFromDIV('<script></script>', 'script');
- }
- });
- if (!testFeature('innerhtml', 'table')) {
- // TODO: thead/tfoot with nested tbody
- // IE adds TBODY when creating TABLE elements (which may share this impl)
- creators.tbody = function(html, doc) {
- var frag = Y_DOM.create(TABLE_OPEN + html + TABLE_CLOSE, doc),
- tb = Y.DOM._children(frag, 'tbody')[0];
- if (frag.children.length > 1 && tb && !re_tbody.test(html)) {
- tb.parentNode.removeChild(tb); // strip extraneous tbody
- }
- return frag;
- };
- }
- if (!testFeature('innerhtml-div', 'script')) {
- creators.script = function(html, doc) {
- var frag = doc.createElement('div');
- frag.innerHTML = '-' + html;
- frag.removeChild(frag.firstChild);
- return frag;
- };
- creators.link = creators.style = creators.script;
- }
- if (!testFeature('innerhtml-div', 'tr')) {
- Y.mix(creators, {
- option: function(html, doc) {
- return Y_DOM.create('<select><option class="yui3-big-dummy" selected></option>' + html + '</select>', doc);
- },
- tr: function(html, doc) {
- return Y_DOM.create('<tbody>' + html + '</tbody>', doc);
- },
- td: function(html, doc) {
- return Y_DOM.create('<tr>' + html + '</tr>', doc);
- },
- col: function(html, doc) {
- return Y_DOM.create('<colgroup>' + html + '</colgroup>', doc);
- },
- tbody: 'table'
- });
- Y.mix(creators, {
- legend: 'fieldset',
- th: creators.td,
- thead: creators.tbody,
- tfoot: creators.tbody,
- caption: creators.tbody,
- colgroup: creators.tbody,
- optgroup: creators.option
- });
- }
- Y_DOM.creators = creators;
- Y.mix(Y.DOM, {
- /**
- * Sets the width of the element to the given size, regardless
- * of box model, border, padding, etc.
- * @method setWidth
- * @param {HTMLElement} element The DOM element.
- * @param {String|Number} size The pixel height to size to
- */
- setWidth: function(node, size) {
- Y.DOM._setSize(node, 'width', size);
- },
- /**
- * Sets the height of the element to the given size, regardless
- * of box model, border, padding, etc.
- * @method setHeight
- * @param {HTMLElement} element The DOM element.
- * @param {String|Number} size The pixel height to size to
- */
- setHeight: function(node, size) {
- Y.DOM._setSize(node, 'height', size);
- },
- _setSize: function(node, prop, val) {
- val = (val > 0) ? val : 0;
- var size = 0;
- node.style[prop] = val + 'px';
- size = (prop === 'height') ? node.offsetHeight : node.offsetWidth;
- if (size > val) {
- val = val - (size - val);
- if (val < 0) {
- val = 0;
- }
- node.style[prop] = val + 'px';
- }
- }
- });
- }, '3.15.0', {"requires": ["dom-core"]});