我正在开发一个使用一些客户端模板来呈现数据的应用程序,并且大多数javascript模板引擎返回带有标记的简单字符串,并由开发人员将该字符串插入DOM中。
我google了一下,看到一群人建议使用空div,将innerHTML设置为新字符串,然后迭代遍历该div的子节点,如此
var parsedTemplate = 'something returned by template engine';
var tempDiv = document.createElement('div'), childNode;
var documentFragment = document.createDocumentFragment;
tempDiv.innerHTML = parsedTemplate;
while ( childNode = tempDiv.firstChild ) {
documentFragment.appendChild(childNode);
}
TADA,documentFragment
现在包含已解析的模板。但是,如果我的模板恰好是tr
,则在其周围添加div不会达到预期的行为,因为它会在行内添加td
的内容。
有人知道解决这个问题的好方法吗?现在我正在检查将插入解析模板的节点,并从其标签名称创建一个元素。我甚至不确定是否有另一种方法可以做到这一点。
在搜索时,我在w3邮件列表上遇到了this discussion,但遗憾的是没有有用的解决方案。
答案 0 :(得分:5)
您可以将DOMParser用作XHTML,以避免HTML“自动更正”DOM执行:
var parser = new DOMParser(),
doc = parser.parseFromString('<tr><td>something returned </td><td>by template engine</td></tr>', "text/xml"),
documentFragment = document.createDocumentFragment() ;
documentFragment.appendChild( doc.documentElement );
//fragment populated, now view as HTML to verify fragment contains what's expected:
var temp=document.createElement('div');
temp.appendChild(documentFragment);
console.log(temp.outerHTML);
// shows: "<div><tr><td>something returned </td><td>by template engine</td></tr></div>"
这与使用带有临时div的朴素innerHTML形成对比:
var temp=document.createElement('div');
temp.innerHTML='<tr><td>something returned </td><td>by template engine</td></tr>';
console.log(temp.outerHTML);
// shows: '<div>something returned by template engine</div>' (bad)
通过将模板视为XHTML / XML(确保其格式正确),我们可以改变HTML的常规规则。 DOMParser的覆盖范围应该与对documentFragment的支持相关联,但是在firefox的一些旧拷贝(单位数版本)上,您可能需要使用importNode()。
作为可重复使用的功能:
function strToFrag(strHTML){
var temp=document.createElement('template');
if( temp.content ){
temp.innerHTML=strHTML;
return temp.content;
}
var parser = new DOMParser(),
doc = parser.parseFromString(strHTML, "text/xml"),
documentFragment = document.createDocumentFragment() ;
documentFragment.appendChild( doc.documentElement );
return documentFragment;
}
答案 1 :(得分:2)
这是未来的一个,而不是现在,但HTML5定义了一个<template>
元素,它将为您创建片段。你将能够:
var parsedTemplate = '<tr><td>xxx</td></tr>';
var tempEL = document.createElement('template');
tempEl.innerHTML = parsedTemplate;
var documentFragment = tempEl.content;
目前适用于Firefox。 See here
答案 2 :(得分:1)
理想的方法是使用HTML5中的<template>
标记。您可以以编程方式创建模板元素,为其分配.innerHTML
,并且template.content
属性中将存在所有已解析的元素(甚至是表的片段)。这可以为您完成所有工作。但是,这只存在于最新版本的Firefox和Chrome中。
如果存在模板支持,它就像这样简单:
function makeDocFragment(htmlString) {
var container = document.createElement("template");
container.innerHTML = htmlString;
return container.content;
}
此返回结果就像documentFragment
一样。您可以直接附加它,它就像documentFragment
一样解决问题,除了它具有支持.innerHTML
赋值的优势,它允许您使用部分形成的HTML片段(解决我们需要的两个问题)
但是,模板支持在任何地方都不存在,因此您需要一种后备方法。处理回退的强力方法是查看HTML字符串的开头并查看它开始的选项卡类型,并为该类型的标记创建适当的容器,并使用该容器将HTML分配给。这是一种蛮力方法,但它有效。任何类型的HTML元素都需要这种特殊处理,这些HTML元素只能合法地存在于特定类型的容器中。我在下面的代码中包含了一堆这些类型的元素(虽然我没有尝试使列表详尽无遗)。这是代码和下面的工作jsFiddle链接。如果您使用最新版本的Chrome或Firefox,则代码将采用使用模板对象的路径。如果是其他浏览器,它会创建相应类型的容器对象。
var makeDocFragment = (function() {
// static data in closure so it only has to be parsed once
var specials = {
td: {
parentElement: "table",
starterHTML: "<tbody><tr class='xx_Root_'></tr></tbody>"
},
tr: {
parentElement: "table",
starterHTML: "<tbody class='xx_Root_'></tbody>"
},
thead: {
parentElement: "table",
starterHTML: "<tbody class='xx_Root_'></tbody>"
},
caption: {
parentElement: "table",
starterHTML: "<tbody class='xx_Root_'></tbody>"
},
li: {
parentElement: "ul",
},
dd: {
parentElement: "dl",
},
dt: {
parentElement: "dl",
},
optgroup: {
parentElement: "select",
},
option: {
parentElement: "select",
}
};
// feature detect template tag support and use simpler path if so
// testing for the content property is suggested by MDN
var testTemplate = document.createElement("template");
if ("content" in testTemplate) {
return function(htmlString) {
var container = document.createElement("template");
container.innerHTML = htmlString;
return container.content;
}
} else {
return function(htmlString) {
var specialInfo, container, root, tagMatch,
documentFragment;
// can't use template tag, so lets mini-parse the first HTML tag
// to discern if it needs a special container
tagMatch = htmlString.match(/^\s*<([^>\s]+)/);
if (tagMatch) {
specialInfo = specials[tagMatch[1].toLowerCase()];
if (specialInfo) {
container = document.createElement(specialInfo.parentElement);
if (specialInfo.starterHTML) {
container.innerHTML = specialInfo.starterHTML;
}
root = container.querySelector(".xx_Root_");
if (!root) {
root = container;
}
root.innerHTML = htmlString;
}
}
if (!container) {
container = document.createElement("div");
container.innerHTML = htmlString;
root = container;
}
documentFragment = document.createDocumentFragment();
// start at the actual root we want
while (root.firstChild) {
documentFragment.appendChild(root.firstChild);
}
return documentFragment;
}
}
// don't let the feature test template object hang around in closure
testTemplate = null;
})();
// test cases
var frag = makeDocFragment("<tr><td>Three</td><td>Four</td></tr>");
document.getElementById("myTableBody").appendChild(frag);
frag = makeDocFragment("<td>Zero</td><td>Zero</td>");
document.getElementById("emptyRow").appendChild(frag);
frag = makeDocFragment("<li>Two</li><li>Three</li>");
document.getElementById("myUL").appendChild(frag);
frag = makeDocFragment("<option>Second Option</option><option>Third Option</option>");
document.getElementById("mySelect").appendChild(frag);
包含多个测试用例的工作演示:http://jsfiddle.net/jfriend00/SycL6/
答案 3 :(得分:0)
使用此功能
function createFragment(html){
var tmpl = document.createElement('template');
tmpl.innerHTML = html;
if (tmpl.content == void 0){ // ie11
var fragment = document.createDocumentFragment();
var isTableEl = /^[^\S]*?<(t(?:head|body|foot|r|d|h))/i.test(html);
tmpl.innerHTML = isTableEl ? '<table>'+html : html;
var els = isTableEl ? tmpl.querySelector(RegExp.$1).parentNode.childNodes : tmpl.childNodes;
while(els[0]) fragment.appendChild(els[0]);
return fragment;
}
return tmpl.content;
}
@dandavis的解决方案只接受ie11中符合xml的内容。
我不知道是否还有其他标签必须加以考虑?