PDA

View Full Version : [OPEN-1078] DomHelper insertHtml throws error on thead element



in2chicago
26 Jun 2010, 2:30 PM
While writing a plugin for GridPanel that was attempting to insert html inside the <thead> element of the header, I was getting an "invalid target element for this operation" error. I tracked it down to Ext.DomHelper.

Here is my fix:



Ext.DomHelper = function(){
var tempTableEl = null,
emptyTags = /^(?:br|frame|hr|img|input|link|meta|range|spacer|wbr|area|param|col)$/i,
tableRe = /^table|thead|tbody|tr|td$/i,
confRe = /tag|children|cn|html$/i,
tableElRe = /td|tr|tbody/i,
cssRe = /([a-z0-9-]+)\s*:\s*([^;\s]+(?:\s*[^;\s]+)*);?/gi,
endRe = /end/i,
pub,

afterbegin = 'afterbegin',
afterend = 'afterend',
beforebegin = 'beforebegin',
beforeend = 'beforeend',
ts = '<table>',
te = '</table>',
ths = ts+'<thead>',
the = ts+'</thead>',
tbs = ts+'<tbody>',
tbe = '</tbody>'+te,
trs = tbs + '<tr>',
tre = '</tr>'+tbe;


function doInsert(el, o, returnElement, pos, sibling, append){
var newNode = pub.insertHtml(pos, Ext.getDom(el), createHtml(o));
return returnElement ? Ext.get(newNode, true) : newNode;
}


function createHtml(o){
var b = '',
attr,
val,
key,
keyVal,
cn;

if(typeof o == "string"){
b = o;
} else if (Ext.isArray(o)) {
for (var i=0; i < o.length; i++) {
if(o[i]) {
b += createHtml(o[i]);
}
};
} else {
b += '<' + (o.tag = o.tag || 'div');
for (attr in o) {
val = o[attr];
if(!confRe.test(attr)){
if (typeof val == "object") {
b += ' ' + attr + '="';
for (key in val) {
b += key + ':' + val[key] + ';';
};
b += '"';
}else{
b += ' ' + ({cls : 'class', htmlFor : 'for'}[attr] || attr) + '="' + val + '"';
}
}
};

if (emptyTags.test(o.tag)) {
b += '/>';
} else {
b += '>';
if ((cn = o.children || o.cn)) {
b += createHtml(cn);
} else if(o.html){
b += o.html;
}
b += '</' + o.tag + '>';
}
}
return b;
}

function ieTable(depth, s, h, e){
tempTableEl.innerHTML = [s, h, e].join('');
var i = -1,
el = tempTableEl,
ns;
while(++i < depth){
el = el.firstChild;
}

if(ns = el.nextSibling){
var df = document.createDocumentFragment();
while(el){
ns = el.nextSibling;
df.appendChild(el);
el = ns;
}
el = df;
}
return el;
}


function insertIntoTable(tag, where, el, html) {
var node,
before;

tempTableEl = tempTableEl || document.createElement('div');

if(tag == 'td' && (where == afterbegin || where == beforeend) ||
!tableElRe.test(tag) && (where == beforebegin || where == afterend)) {
return;
}
before = where == beforebegin ? el :
where == afterend ? el.nextSibling :
where == afterbegin ? el.firstChild : null;

if (where == beforebegin || where == afterend) {
el = el.parentNode;
}

if (tag == 'td' || (tag == 'tr' && (where == beforeend || where == afterbegin))) {
node = ieTable(4, trs, html, tre);
} else if ((tag == 'thead' && (where == beforeend || where == afterbegin)) ||
(el.parentNode.tagName.toLowerCase() == 'thead' && (tag == 'tr' && (where == beforebegin || where == afterend)))) {
node = ieTable(3, ths, html, the);
} else if ((tag == 'tbody' && (where == beforeend || where == afterbegin)) ||
(tag == 'tr' && (where == beforebegin || where == afterend))) {
node = ieTable(3, tbs, html, tbe);
} else {
node = ieTable(2, ts, html, te);
}
el.insertBefore(node, before);
return node;
}


pub = {

markup : function(o){
return createHtml(o);
},


applyStyles : function(el, styles){
if(styles){
var i = 0,
len,
style,
matches;

el = Ext.fly(el);
if(typeof styles == "function"){
styles = styles.call();
}
if(typeof styles == "string"){
while((matches = cssRe.exec(styles))){
el.setStyle(matches[1], matches[2]);
}
}else if (typeof styles == "object"){
el.setStyle(styles);
}
}
},


insertHtml : function(where, el, html){
var hash = {},
hashVal,
setStart,
range,
frag,
rangeEl,
rs;

where = where.toLowerCase();

hash[beforebegin] = ['BeforeBegin', 'previousSibling'];
hash[afterend] = ['AfterEnd', 'nextSibling'];

if (el.insertAdjacentHTML) {
if(tableRe.test(el.tagName) && (rs = insertIntoTable(el.tagName.toLowerCase(), where, el, html))){
return rs;
}

hash[afterbegin] = ['AfterBegin', 'firstChild'];
hash[beforeend] = ['BeforeEnd', 'lastChild'];
if ((hashVal = hash[where])) {
el.insertAdjacentHTML(hashVal[0], html);
return el[hashVal[1]];
}
} else {
range = el.ownerDocument.createRange();
setStart = 'setStart' + (endRe.test(where) ? 'After' : 'Before');
if (hash[where]) {
range[setStart](el);
frag = range.createContextualFragment(html);
el.parentNode.insertBefore(frag, where == beforebegin ? el : el.nextSibling);
return el[(where == beforebegin ? 'previous' : 'next') + 'Sibling'];
} else {
rangeEl = (where == afterbegin ? 'first' : 'last') + 'Child';
if (el.firstChild) {
range[setStart](el[rangeEl]);
frag = range.createContextualFragment(html);
if(where == afterbegin){
el.insertBefore(frag, el.firstChild);
}else{
el.appendChild(frag);
}
} else {
el.innerHTML = html;
}
return el[rangeEl];
}
}
throw 'Illegal insertion point -> "' + where + '"';
},


insertBefore : function(el, o, returnElement){
return doInsert(el, o, returnElement, beforebegin);
},


insertAfter : function(el, o, returnElement){
return doInsert(el, o, returnElement, afterend, 'nextSibling');
},


insertFirst : function(el, o, returnElement){
return doInsert(el, o, returnElement, afterbegin, 'firstChild');
},


append : function(el, o, returnElement){
return doInsert(el, o, returnElement, beforeend, '', true);
},


overwrite : function(el, o, returnElement){
el = Ext.getDom(el);
el.innerHTML = createHtml(o);
return returnElement ? Ext.get(el.firstChild) : el.firstChild;
},

createHtml : createHtml
};
return pub;
}();

mcouillard
9 May 2011, 12:52 PM
Thanks in2chicago!

Your solution nearly worked for me. I had to add support for TH elements (treated just like TDs).

Devs: this remains a problem in Ext Core 3.1.0 and ExtJS 3.3.1
(Core, remember that one? Would love an update! :D)

Test page:


<html>
<head>
<script type="text/javascript" src="ext-core-3.1.0/ext-core.js"></script>
</head>
<body>

<table border=1>
<thead>
<tr id="thead1"><th>THEAD One</th></tr>
</thead>
<tbody>
<tr id="tbody1"><th>TBODY One</th></tr>
<tr id="tbody2"><td>TBODY Two</td></tr>
</tbody>
</table>

<h5>Note: works in IE when target is TR; also works when TD; does NOT when TH</h5>

<script>
alert('test 1...');
Ext.DomHelper.insertAfter(Ext.get('tbody1').down('th').dom,{
tag: 'th',
children: [{
tag: 'th'
,html: 'THEAD 2 - from ExtJS'
}]
});


alert('test 2...');
Ext.DomHelper.insertAfter(Ext.get('thead1').down('th').dom,{
tag: 'td',
children: [{
tag: 'td'
,html: 'THEAD 2 - from ExtJS'
}]
});
</script>

</body></html>
Here's the fully-fixed code with support for both THEAD and TH:


Ext.DomHelper = function(){
var tempTableEl = null,
emptyTags = /^(?:br|frame|hr|img|input|link|meta|range|spacer|wbr|area|param|col)$/i,
tableRe = /^table|thead|tbody|tr|td|th$/i,
confRe = /tag|children|cn|html$/i,
tableElRe = /th|td|tr|tbody/i,
cssRe = /([a-z0-9-]+)\s*:\s*([^;\s]+(?:\s*[^;\s]+)*);?/gi,
endRe = /end/i,
pub,

afterbegin = 'afterbegin',
afterend = 'afterend',
beforebegin = 'beforebegin',
beforeend = 'beforeend',
ts = '<table>',
te = '</table>',
ths = ts+'<thead>',
the = ts+'</thead>',
tbs = ts+'<tbody>',
tbe = '</tbody>'+te,
trs = tbs + '<tr>',
tre = '</tr>'+tbe;


function doInsert(el, o, returnElement, pos, sibling, append){
var newNode = pub.insertHtml(pos, Ext.getDom(el), createHtml(o));
return returnElement ? Ext.get(newNode, true) : newNode;
}


function createHtml(o){
var b = '',
attr,
val,
key,
keyVal,
cn;

if(typeof o == "string"){
b = o;
} else if (Ext.isArray(o)) {
for (var i=0; i < o.length; i++) {
if(o[i]) {
b += createHtml(o[i]);
}
};
} else {
b += '<' + (o.tag = o.tag || 'div');
for (attr in o) {
val = o[attr];
if(!confRe.test(attr)){
if (typeof val == "object") {
b += ' ' + attr + '="';
for (key in val) {
b += key + ':' + val[key] + ';';
};
b += '"';
}else{
b += ' ' + ({cls : 'class', htmlFor : 'for'}[attr] || attr) + '="' + val + '"';
}
}
};

if (emptyTags.test(o.tag)) {
b += '/>';
} else {
b += '>';
if ((cn = o.children || o.cn)) {
b += createHtml(cn);
} else if(o.html){
b += o.html;
}
b += '</' + o.tag + '>';
}
}
return b;
}

function ieTable(depth, s, h, e){
tempTableEl.innerHTML = [s, h, e].join('');
var i = -1,
el = tempTableEl,
ns;
while(++i < depth){
el = el.firstChild;
}

if(ns = el.nextSibling){
var df = document.createDocumentFragment();
while(el){
ns = el.nextSibling;
df.appendChild(el);
el = ns;
}
el = df;
}
return el;
}


function insertIntoTable(tag, where, el, html) {
var node,
before;

tempTableEl = tempTableEl || document.createElement('div');

if((tag == 'td' || tag == 'th') && (where == afterbegin || where == beforeend) ||
!tableElRe.test(tag) && (where == beforebegin || where == afterend)) {
return;
}
before = where == beforebegin ? el :
where == afterend ? el.nextSibling :
where == afterbegin ? el.firstChild : null;

if (where == beforebegin || where == afterend) {
el = el.parentNode;
}

if ((tag == 'td' || tag == 'th') || (tag == 'tr' && (where == beforeend || where == afterbegin))) {
node = ieTable(4, trs, html, tre);
} else if ((tag == 'thead' && (where == beforeend || where == afterbegin)) ||
(el.parentNode.tagName.toLowerCase() == 'thead' && (tag == 'tr' && (where == beforebegin || where == afterend)))) {
node = ieTable(3, ths, html, the);
} else if ((tag == 'tbody' && (where == beforeend || where == afterbegin)) ||
(tag == 'tr' && (where == beforebegin || where == afterend))) {
node = ieTable(3, tbs, html, tbe);
} else {
node = ieTable(2, ts, html, te);
}
el.insertBefore(node, before);
return node;
}


pub = {

markup : function(o){
return createHtml(o);
},


applyStyles : function(el, styles){
if(styles){
var i = 0,
len,
style,
matches;

el = Ext.fly(el);
if(typeof styles == "function"){
styles = styles.call();
}
if(typeof styles == "string"){
while((matches = cssRe.exec(styles))){
el.setStyle(matches[1], matches[2]);
}
}else if (typeof styles == "object"){
el.setStyle(styles);
}
}
},


insertHtml : function(where, el, html){
var hash = {},
hashVal,
setStart,
range,
frag,
rangeEl,
rs;

where = where.toLowerCase();

hash[beforebegin] = ['BeforeBegin', 'previousSibling'];
hash[afterend] = ['AfterEnd', 'nextSibling'];

if (el.insertAdjacentHTML) {
if(tableRe.test(el.tagName) && (rs = insertIntoTable(el.tagName.toLowerCase(), where, el, html))){
return rs;
}

hash[afterbegin] = ['AfterBegin', 'firstChild'];
hash[beforeend] = ['BeforeEnd', 'lastChild'];
if ((hashVal = hash[where])) {
el.insertAdjacentHTML(hashVal[0], html);
return el[hashVal[1]];
}
} else {
range = el.ownerDocument.createRange();
setStart = 'setStart' + (endRe.test(where) ? 'After' : 'Before');
if (hash[where]) {
range[setStart](el);
frag = range.createContextualFragment(html);
el.parentNode.insertBefore(frag, where == beforebegin ? el : el.nextSibling);
return el[(where == beforebegin ? 'previous' : 'next') + 'Sibling'];
} else {
rangeEl = (where == afterbegin ? 'first' : 'last') + 'Child';
if (el.firstChild) {
range[setStart](el[rangeEl]);
frag = range.createContextualFragment(html);
if(where == afterbegin){
el.insertBefore(frag, el.firstChild);
}else{
el.appendChild(frag);
}
} else {
el.innerHTML = html;
}
return el[rangeEl];
}
}
throw 'Illegal insertion point -> "' + where + '"';
},


insertBefore : function(el, o, returnElement){
return doInsert(el, o, returnElement, beforebegin);
},


insertAfter : function(el, o, returnElement){
return doInsert(el, o, returnElement, afterend, 'nextSibling');
},


insertFirst : function(el, o, returnElement){
return doInsert(el, o, returnElement, afterbegin, 'firstChild');
},


append : function(el, o, returnElement){
return doInsert(el, o, returnElement, beforeend, '', true);
},


overwrite : function(el, o, returnElement){
el = Ext.getDom(el);
el.innerHTML = createHtml(o);
return returnElement ? Ext.get(el.firstChild) : el.firstChild;
},

createHtml : createHtml
};
return pub;
}();