dom-create.js revision 177b40909a5a6eae6736027cdbce3faf45152f81
332N/Avar re_tag = /<([a-z]+)/i,
332N/A
332N/A Y_DOM = Y.DOM,
332N/A
332N/A addFeature = Y.Features.add,
332N/A testFeature = Y.Features.test,
332N/A
332N/A creators = {},
332N/A
332N/A createFromDIV = function(html, tag) {
332N/A var div = Y.config.doc.createElement('div'),
332N/A ret = true;
332N/A
332N/A div.innerHTML = html;
332N/A if (!div.firstChild || div.firstChild.tagName !== tag.toUpperCase()) {
332N/A ret = false;
332N/A }
332N/A
332N/A return ret;
332N/A },
332N/A
332N/A re_tbody = /(?:\/(?:thead|tfoot|tbody|caption|col|colgroup)>)+\s*<tbody/,
332N/A
332N/A TABLE_OPEN = '<table>',
332N/A TABLE_CLOSE = '</table>';
332N/A
332N/AY.mix(Y.DOM, {
921N/A _fragClones: {},
332N/A
332N/A _create: function(html, doc, tag) {
332N/A tag = tag || 'div';
332N/A
332N/A var frag = Y_DOM._fragClones[tag];
332N/A if (frag) {
332N/A frag = frag.cloneNode(false);
332N/A } else {
332N/A frag = Y_DOM._fragClones[tag] = doc.createElement(tag);
332N/A }
332N/A frag.innerHTML = html;
332N/A return frag;
332N/A },
921N/A
332N/A /**
1127N/A * Creates a new dom node using the provided markup string.
332N/A * @method create
332N/A * @param {String} html The markup used to create the element
332N/A * @param {HTMLDocument} doc An optional document context
332N/A * @return {HTMLElement|DocumentFragment} returns a single HTMLElement
332N/A * when creating one node, and a documentFragment when creating
332N/A * multiple nodes.
1000N/A */
1000N/A create: function(html, doc) {
1000N/A if (typeof html === 'string') {
1000N/A html = Y.Lang.trim(html); // match IE which trims whitespace from innerHTML
1000N/A
1000N/A }
1000N/A
1000N/A doc = doc || Y.config.doc;
1000N/A var m = re_tag.exec(html),
1000N/A create = Y_DOM._create,
1127N/A custom = creators,
1000N/A ret = null,
1000N/A creator,
1000N/A tag, nodes;
1000N/A
1127N/A if (html != undefined) { // not undefined or null
1000N/A if (m && m[1]) {
1000N/A creator = custom[m[1].toLowerCase()];
332N/A if (typeof creator === 'function') {
create = creator;
} else {
tag = creator;
}
}
nodes = create(html, doc, tag).childNodes;
if (nodes.length === 1) { // return single node, breaking parentNode ref from "fragment"
ret = nodes[0].parentNode.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)
if (nodes.length === 2) {
ret = nodes[0].nextSibling;
} else {
nodes[0].parentNode.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
else { Y.log('unable to convert ' + nodes + ' to fragment', 'warn', 'dom'); }
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);
}
return ret;
}
});
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 = frag.children.tags('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;