NOBUG: Fixed file access permissions
[moodle.git] / lib / yuilib / 3.13.0 / dom-base / dom-base.js
blob3c16228684f918c7f61fea0290fd62729d4952c2
1 /*
2 YUI 3.13.0 (build 508226d)
3 Copyright 2013 Yahoo! Inc. All rights reserved.
4 Licensed under the BSD License.
5 http://yuilibrary.com/license/
6 */
8 YUI.add('dom-base', function (Y, NAME) {
10 /**
11 * @for DOM
12 * @module dom
14 var documentElement = Y.config.doc.documentElement,
15     Y_DOM = Y.DOM,
16     TAG_NAME = 'tagName',
17     OWNER_DOCUMENT = 'ownerDocument',
18     EMPTY_STRING = '',
19     addFeature = Y.Features.add,
20     testFeature = Y.Features.test;
22 Y.mix(Y_DOM, {
23     /**
24      * Returns the text content of the HTMLElement.
25      * @method getText
26      * @param {HTMLElement} element The html element.
27      * @return {String} The text content of the element (includes text of any descending elements).
28      */
29     getText: (documentElement.textContent !== undefined) ?
30         function(element) {
31             var ret = '';
32             if (element) {
33                 ret = element.textContent;
34             }
35             return ret || '';
36         } : function(element) {
37             var ret = '';
38             if (element) {
39                 ret = element.innerText || element.nodeValue; // might be a textNode
40             }
41             return ret || '';
42         },
44     /**
45      * Sets the text content of the HTMLElement.
46      * @method setText
47      * @param {HTMLElement} element The html element.
48      * @param {String} content The content to add.
49      */
50     setText: (documentElement.textContent !== undefined) ?
51         function(element, content) {
52             if (element) {
53                 element.textContent = content;
54             }
55         } : function(element, content) {
56             if ('innerText' in element) {
57                 element.innerText = content;
58             } else if ('nodeValue' in element) {
59                 element.nodeValue = content;
60             }
61     },
63     CUSTOM_ATTRIBUTES: (!documentElement.hasAttribute) ? { // IE < 8
64         'for': 'htmlFor',
65         'class': 'className'
66     } : { // w3c
67         'htmlFor': 'for',
68         'className': 'class'
69     },
71     /**
72      * Provides a normalized attribute interface.
73      * @method setAttribute
74      * @param {HTMLElement} el The target element for the attribute.
75      * @param {String} attr The attribute to set.
76      * @param {String} val The value of the attribute.
77      */
78     setAttribute: function(el, attr, val, ieAttr) {
79         if (el && attr && el.setAttribute) {
80             attr = Y_DOM.CUSTOM_ATTRIBUTES[attr] || attr;
81             el.setAttribute(attr, val, ieAttr);
82         }
83     },
86     /**
87      * Provides a normalized attribute interface.
88      * @method getAttribute
89      * @param {HTMLElement} el The target element for the attribute.
90      * @param {String} attr The attribute to get.
91      * @return {String} The current value of the attribute.
92      */
93     getAttribute: function(el, attr, ieAttr) {
94         ieAttr = (ieAttr !== undefined) ? ieAttr : 2;
95         var ret = '';
96         if (el && attr && el.getAttribute) {
97             attr = Y_DOM.CUSTOM_ATTRIBUTES[attr] || attr;
98             ret = el.getAttribute(attr, ieAttr);
100             if (ret === null) {
101                 ret = ''; // per DOM spec
102             }
103         }
104         return ret;
105     },
107     VALUE_SETTERS: {},
109     VALUE_GETTERS: {},
111     getValue: function(node) {
112         var ret = '', // TODO: return null?
113             getter;
115         if (node && node[TAG_NAME]) {
116             getter = Y_DOM.VALUE_GETTERS[node[TAG_NAME].toLowerCase()];
118             if (getter) {
119                 ret = getter(node);
120             } else {
121                 ret = node.value;
122             }
123         }
125         // workaround for IE8 JSON stringify bug
126         // which converts empty string values to null
127         if (ret === EMPTY_STRING) {
128             ret = EMPTY_STRING; // for real
129         }
131         return (typeof ret === 'string') ? ret : '';
132     },
134     setValue: function(node, val) {
135         var setter;
137         if (node && node[TAG_NAME]) {
138             setter = Y_DOM.VALUE_SETTERS[node[TAG_NAME].toLowerCase()];
140             if (setter) {
141                 setter(node, val);
142             } else {
143                 node.value = val;
144             }
145         }
146     },
148     creators: {}
151 addFeature('value-set', 'select', {
152     test: function() {
153         var node = Y.config.doc.createElement('select');
154         node.innerHTML = '<option>1</option><option>2</option>';
155         node.value = '2';
156         return (node.value && node.value === '2');
157     }
160 if (!testFeature('value-set', 'select')) {
161     Y_DOM.VALUE_SETTERS.select = function(node, val) {
162         for (var i = 0, options = node.getElementsByTagName('option'), option;
163                 option = options[i++];) {
164             if (Y_DOM.getValue(option) === val) {
165                 option.selected = true;
166                 //Y_DOM.setAttribute(option, 'selected', 'selected');
167                 break;
168             }
169         }
170     };
173 Y.mix(Y_DOM.VALUE_GETTERS, {
174     button: function(node) {
175         return (node.attributes && node.attributes.value) ? node.attributes.value.value : '';
176     }
179 Y.mix(Y_DOM.VALUE_SETTERS, {
180     // IE: node.value changes the button text, which should be handled via innerHTML
181     button: function(node, val) {
182         var attr = node.attributes.value;
183         if (!attr) {
184             attr = node[OWNER_DOCUMENT].createAttribute('value');
185             node.setAttributeNode(attr);
186         }
188         attr.value = val;
189     }
193 Y.mix(Y_DOM.VALUE_GETTERS, {
194     option: function(node) {
195         var attrs = node.attributes;
196         return (attrs.value && attrs.value.specified) ? node.value : node.text;
197     },
199     select: function(node) {
200         var val = node.value,
201             options = node.options;
203         if (options && options.length) {
204             // TODO: implement multipe select
205             if (node.multiple) {
206             } else if (node.selectedIndex > -1) {
207                 val = Y_DOM.getValue(options[node.selectedIndex]);
208             }
209         }
211         return val;
212     }
214 var addClass, hasClass, removeClass;
216 Y.mix(Y.DOM, {
217     /**
218      * Determines whether a DOM element has the given className.
219      * @method hasClass
220      * @for DOM
221      * @param {HTMLElement} element The DOM element.
222      * @param {String} className the class name to search for
223      * @return {Boolean} Whether or not the element has the given class.
224      */
225     hasClass: function(node, className) {
226         var re = Y.DOM._getRegExp('(?:^|\\s+)' + className + '(?:\\s+|$)');
227         return re.test(node.className);
228     },
230     /**
231      * Adds a class name to a given DOM element.
232      * @method addClass
233      * @for DOM
234      * @param {HTMLElement} element The DOM element.
235      * @param {String} className the class name to add to the class attribute
236      */
237     addClass: function(node, className) {
238         if (!Y.DOM.hasClass(node, className)) { // skip if already present
239             node.className = Y.Lang.trim([node.className, className].join(' '));
240         }
241     },
243     /**
244      * Removes a class name from a given element.
245      * @method removeClass
246      * @for DOM
247      * @param {HTMLElement} element The DOM element.
248      * @param {String} className the class name to remove from the class attribute
249      */
250     removeClass: function(node, className) {
251         if (className && hasClass(node, className)) {
252             node.className = Y.Lang.trim(node.className.replace(Y.DOM._getRegExp('(?:^|\\s+)' +
253                             className + '(?:\\s+|$)'), ' '));
255             if ( hasClass(node, className) ) { // in case of multiple adjacent
256                 removeClass(node, className);
257             }
258         }
259     },
261     /**
262      * Replace a class with another class for a given element.
263      * If no oldClassName is present, the newClassName is simply added.
264      * @method replaceClass
265      * @for DOM
266      * @param {HTMLElement} element The DOM element
267      * @param {String} oldClassName the class name to be replaced
268      * @param {String} newClassName the class name that will be replacing the old class name
269      */
270     replaceClass: function(node, oldC, newC) {
271         removeClass(node, oldC); // remove first in case oldC === newC
272         addClass(node, newC);
273     },
275     /**
276      * If the className exists on the node it is removed, if it doesn't exist it is added.
277      * @method toggleClass
278      * @for DOM
279      * @param {HTMLElement} element The DOM element
280      * @param {String} className the class name to be toggled
281      * @param {Boolean} addClass optional boolean to indicate whether class
282      * should be added or removed regardless of current state
283      */
284     toggleClass: function(node, className, force) {
285         var add = (force !== undefined) ? force :
286                 !(hasClass(node, className));
288         if (add) {
289             addClass(node, className);
290         } else {
291             removeClass(node, className);
292         }
293     }
296 hasClass = Y.DOM.hasClass;
297 removeClass = Y.DOM.removeClass;
298 addClass = Y.DOM.addClass;
300 var re_tag = /<([a-z]+)/i,
302     Y_DOM = Y.DOM,
304     addFeature = Y.Features.add,
305     testFeature = Y.Features.test,
307     creators = {},
309     createFromDIV = function(html, tag) {
310         var div = Y.config.doc.createElement('div'),
311             ret = true;
313         div.innerHTML = html;
314         if (!div.firstChild || div.firstChild.tagName !== tag.toUpperCase()) {
315             ret = false;
316         }
318         return ret;
319     },
321     re_tbody = /(?:\/(?:thead|tfoot|tbody|caption|col|colgroup)>)+\s*<tbody/,
323     TABLE_OPEN = '<table>',
324     TABLE_CLOSE = '</table>';
326 Y.mix(Y.DOM, {
327     _fragClones: {},
329     _create: function(html, doc, tag) {
330         tag = tag || 'div';
332         var frag = Y_DOM._fragClones[tag];
333         if (frag) {
334             frag = frag.cloneNode(false);
335         } else {
336             frag = Y_DOM._fragClones[tag] = doc.createElement(tag);
337         }
338         frag.innerHTML = html;
339         return frag;
340     },
342     _children: function(node, tag) {
343             var i = 0,
344             children = node.children,
345             childNodes,
346             hasComments,
347             child;
349         if (children && children.tags) { // use tags filter when possible
350             if (tag) {
351                 children = node.children.tags(tag);
352             } else { // IE leaks comments into children
353                 hasComments = children.tags('!').length;
354             }
355         }
357         if (!children || (!children.tags && tag) || hasComments) {
358             childNodes = children || node.childNodes;
359             children = [];
360             while ((child = childNodes[i++])) {
361                 if (child.nodeType === 1) {
362                     if (!tag || tag === child.tagName) {
363                         children.push(child);
364                     }
365                 }
366             }
367         }
369         return children || [];
370     },
372     /**
373      * Creates a new dom node using the provided markup string.
374      * @method create
375      * @param {String} html The markup used to create the element
376      * @param {HTMLDocument} doc An optional document context
377      * @return {HTMLElement|DocumentFragment} returns a single HTMLElement
378      * when creating one node, and a documentFragment when creating
379      * multiple nodes.
380      */
381     create: function(html, doc) {
382         if (typeof html === 'string') {
383             html = Y.Lang.trim(html); // match IE which trims whitespace from innerHTML
385         }
387         doc = doc || Y.config.doc;
388         var m = re_tag.exec(html),
389             create = Y_DOM._create,
390             custom = creators,
391             ret = null,
392             creator,
393             tag, nodes;
395         if (html != undefined) { // not undefined or null
396             if (m && m[1]) {
397                 creator = custom[m[1].toLowerCase()];
398                 if (typeof creator === 'function') {
399                     create = creator;
400                 } else {
401                     tag = creator;
402                 }
403             }
405             nodes = create(html, doc, tag).childNodes;
407             if (nodes.length === 1) { // return single node, breaking parentNode ref from "fragment"
408                 ret = nodes[0].parentNode.removeChild(nodes[0]);
409             } else if (nodes[0] && nodes[0].className === 'yui3-big-dummy') { // using dummy node to preserve some attributes (e.g. OPTION not selected)
410                 if (nodes.length === 2) {
411                     ret = nodes[0].nextSibling;
412                 } else {
413                     nodes[0].parentNode.removeChild(nodes[0]);
414                     ret = Y_DOM._nl2frag(nodes, doc);
415                 }
416             } else { // return multiple nodes as a fragment
417                  ret = Y_DOM._nl2frag(nodes, doc);
418             }
420         }
422         return ret;
423     },
425     _nl2frag: function(nodes, doc) {
426         var ret = null,
427             i, len;
429         if (nodes && (nodes.push || nodes.item) && nodes[0]) {
430             doc = doc || nodes[0].ownerDocument;
431             ret = doc.createDocumentFragment();
433             if (nodes.item) { // convert live list to static array
434                 nodes = Y.Array(nodes, 0, true);
435             }
437             for (i = 0, len = nodes.length; i < len; i++) {
438                 ret.appendChild(nodes[i]);
439             }
440         } // else inline with log for minification
441         return ret;
442     },
444     /**
445      * Inserts content in a node at the given location
446      * @method addHTML
447      * @param {HTMLElement} node The node to insert into
448      * @param {HTMLElement | Array | HTMLCollection} content The content to be inserted
449      * @param {HTMLElement} where Where to insert the content
450      * If no "where" is given, content is appended to the node
451      * Possible values for "where"
452      * <dl>
453      * <dt>HTMLElement</dt>
454      * <dd>The element to insert before</dd>
455      * <dt>"replace"</dt>
456      * <dd>Replaces the existing HTML</dd>
457      * <dt>"before"</dt>
458      * <dd>Inserts before the existing HTML</dd>
459      * <dt>"before"</dt>
460      * <dd>Inserts content before the node</dd>
461      * <dt>"after"</dt>
462      * <dd>Inserts content after the node</dd>
463      * </dl>
464      */
465     addHTML: function(node, content, where) {
466         var nodeParent = node.parentNode,
467             i = 0,
468             item,
469             ret = content,
470             newNode;
473         if (content != undefined) { // not null or undefined (maybe 0)
474             if (content.nodeType) { // DOM node, just add it
475                 newNode = content;
476             } else if (typeof content == 'string' || typeof content == 'number') {
477                 ret = newNode = Y_DOM.create(content);
478             } else if (content[0] && content[0].nodeType) { // array or collection
479                 newNode = Y.config.doc.createDocumentFragment();
480                 while ((item = content[i++])) {
481                     newNode.appendChild(item); // append to fragment for insertion
482                 }
483             }
484         }
486         if (where) {
487             if (newNode && where.parentNode) { // insert regardless of relationship to node
488                 where.parentNode.insertBefore(newNode, where);
489             } else {
490                 switch (where) {
491                     case 'replace':
492                         while (node.firstChild) {
493                             node.removeChild(node.firstChild);
494                         }
495                         if (newNode) { // allow empty content to clear node
496                             node.appendChild(newNode);
497                         }
498                         break;
499                     case 'before':
500                         if (newNode) {
501                             nodeParent.insertBefore(newNode, node);
502                         }
503                         break;
504                     case 'after':
505                         if (newNode) {
506                             if (node.nextSibling) { // IE errors if refNode is null
507                                 nodeParent.insertBefore(newNode, node.nextSibling);
508                             } else {
509                                 nodeParent.appendChild(newNode);
510                             }
511                         }
512                         break;
513                     default:
514                         if (newNode) {
515                             node.appendChild(newNode);
516                         }
517                 }
518             }
519         } else if (newNode) {
520             node.appendChild(newNode);
521         }
523         return ret;
524     },
526     wrap: function(node, html) {
527         var parent = (html && html.nodeType) ? html : Y.DOM.create(html),
528             nodes = parent.getElementsByTagName('*');
530         if (nodes.length) {
531             parent = nodes[nodes.length - 1];
532         }
534         if (node.parentNode) {
535             node.parentNode.replaceChild(parent, node);
536         }
537         parent.appendChild(node);
538     },
540     unwrap: function(node) {
541         var parent = node.parentNode,
542             lastChild = parent.lastChild,
543             next = node,
544             grandparent;
546         if (parent) {
547             grandparent = parent.parentNode;
548             if (grandparent) {
549                 node = parent.firstChild;
550                 while (node !== lastChild) {
551                     next = node.nextSibling;
552                     grandparent.insertBefore(node, parent);
553                     node = next;
554                 }
555                 grandparent.replaceChild(lastChild, parent);
556             } else {
557                 parent.removeChild(node);
558             }
559         }
560     }
563 addFeature('innerhtml', 'table', {
564     test: function() {
565         var node = Y.config.doc.createElement('table');
566         try {
567             node.innerHTML = '<tbody></tbody>';
568         } catch(e) {
569             return false;
570         }
571         return (node.firstChild && node.firstChild.nodeName === 'TBODY');
572     }
575 addFeature('innerhtml-div', 'tr', {
576     test: function() {
577         return createFromDIV('<tr></tr>', 'tr');
578     }
581 addFeature('innerhtml-div', 'script', {
582     test: function() {
583         return createFromDIV('<script></script>', 'script');
584     }
587 if (!testFeature('innerhtml', 'table')) {
588     // TODO: thead/tfoot with nested tbody
589         // IE adds TBODY when creating TABLE elements (which may share this impl)
590     creators.tbody = function(html, doc) {
591         var frag = Y_DOM.create(TABLE_OPEN + html + TABLE_CLOSE, doc),
592             tb = Y.DOM._children(frag, 'tbody')[0];
594         if (frag.children.length > 1 && tb && !re_tbody.test(html)) {
595             tb.parentNode.removeChild(tb); // strip extraneous tbody
596         }
597         return frag;
598     };
601 if (!testFeature('innerhtml-div', 'script')) {
602     creators.script = function(html, doc) {
603         var frag = doc.createElement('div');
605         frag.innerHTML = '-' + html;
606         frag.removeChild(frag.firstChild);
607         return frag;
608     };
610     creators.link = creators.style = creators.script;
613 if (!testFeature('innerhtml-div', 'tr')) {
614     Y.mix(creators, {
615         option: function(html, doc) {
616             return Y_DOM.create('<select><option class="yui3-big-dummy" selected></option>' + html + '</select>', doc);
617         },
619         tr: function(html, doc) {
620             return Y_DOM.create('<tbody>' + html + '</tbody>', doc);
621         },
623         td: function(html, doc) {
624             return Y_DOM.create('<tr>' + html + '</tr>', doc);
625         },
627         col: function(html, doc) {
628             return Y_DOM.create('<colgroup>' + html + '</colgroup>', doc);
629         },
631         tbody: 'table'
632     });
634     Y.mix(creators, {
635         legend: 'fieldset',
636         th: creators.td,
637         thead: creators.tbody,
638         tfoot: creators.tbody,
639         caption: creators.tbody,
640         colgroup: creators.tbody,
641         optgroup: creators.option
642     });
645 Y_DOM.creators = creators;
646 Y.mix(Y.DOM, {
647     /**
648      * Sets the width of the element to the given size, regardless
649      * of box model, border, padding, etc.
650      * @method setWidth
651      * @param {HTMLElement} element The DOM element.
652      * @param {String|Number} size The pixel height to size to
653      */
655     setWidth: function(node, size) {
656         Y.DOM._setSize(node, 'width', size);
657     },
659     /**
660      * Sets the height of the element to the given size, regardless
661      * of box model, border, padding, etc.
662      * @method setHeight
663      * @param {HTMLElement} element The DOM element.
664      * @param {String|Number} size The pixel height to size to
665      */
667     setHeight: function(node, size) {
668         Y.DOM._setSize(node, 'height', size);
669     },
671     _setSize: function(node, prop, val) {
672         val = (val > 0) ? val : 0;
673         var size = 0;
675         node.style[prop] = val + 'px';
676         size = (prop === 'height') ? node.offsetHeight : node.offsetWidth;
678         if (size > val) {
679             val = val - (size - val);
681             if (val < 0) {
682                 val = 0;
683             }
685             node.style[prop] = val + 'px';
686         }
687     }
691 }, '3.13.0', {"requires": ["dom-core"]});