无法使用innerHTML从HTML字符串创建某种类型的节点

时间:2013-04-02 06:35:30

标签: javascript html validation dom

以下javascript(在Chrome控制台中运行)不能达到我的期望:

> var elem = document.createElement("foo");
undefined
> elem.innerHTML = "<tr></tr>"
"<tr></tr>"
> elem.outerHTML
"<foo></foo>"

<tr>标记已消失!

这似乎与表相关的元素有关。使用<div><span>按预期工作。

我希望我正在做的事情是无效的,因为“foo”不是一个已知元素,并且可能与表相关的元素只能出现在a中。有趣的是,以下代码工作正常:

> var elem = document.createElement("foo"), tr = document.createElement("tr");
> elem.appendChild(tr);
> elem.outerHTML
"<foo><tr></tr></foo>"

所以看起来构造本身(<tr>不在<table>内)是允许的,但是使用innerHTML将它放在那里的方法不起作用 - 也许这可以通过一些html清理,它可以删除不严格的内容,而直接创建DOM节点则不需要进行相同的验证。

我的问题:有没有办法从字符串填充任意DOM节点而不会遇到这样的清理/验证问题?我的用例将结束具有完全有效的结构(我计划将其作为稍后的孩子),但是当我尝试构建各个部分时,浏览器正在阻止我。 / p>

听起来有点像DocumentFragment应该是我正在寻找的东西,但据我所知,这些只能以编程方式构建 - 它们不支持innerHTML。

我想要这样做的一些背景知识:

我的用例是基于javascript的实时模板(即不输出html字符串,但实际的DOM节点)。所以要求是:

  • 必须允许模板输入为任意HTML(这就是为什么我使用innerHTML而不是以编程方式构造节点)
  • 必须能够创建子模板,然后将其附加到更大的文档中(这就是为什么我不能一次创建整个文档)。

第二点是我遇到这个错误的方法。我的模板包含一个子模板。

var row = Html("<tr></tr>");
var table = Html(["<table><thead>", row, "</thead></table>"]);

我稍后会添加如下代码:

row.append(Html(["<td>", column.header, "</td>"]));

实际填充列。因此,当它完全构建时,html 有效。但在中间阶段,每个模板/片段都在单个元素下构建。这意味着模板如:

Html(["Hello <span>", name, "</span>"]);

仍然作为单个节点出现(以便可以将它们作为单个实体进行操作):

<foo>Hello <span>bob</span></foo>

当模板在<foo>内只生成一个子节点时,外部节点将被删除。但在构建期间,上面的row模板应该看起来像<foo><tr></tr></foo>。由于我在使用innerHTML时看到的验证行为,它最终会以<foo></foo>结束。

我已经检查过所有代码在firefox和amp;中都是一样的chrome,所以我不指望我只是遇到浏览器错误。

2 个答案:

答案 0 :(得分:2)

不幸的是,您的一般问题的答案是否定的,没有办法使用innerHTML添加任意不完整的HTML片段。我知道这不是你想听的答案,但就是这样。

关于innerHTML最容易被误解的事情之一源于API的设计方式。它会使+=运算符重载以执行DOM插入。这使得程序员认为它只是在进行字符串操作,而实际上innerHTML的行为更像是函数而不是变量。如果innerHTML的设计如下:

,那么人们就不那么容易混淆了
element.innerHTML('some <b>html</b> here');

遗憾的是,更改API为时已晚,因此我们必须明白它实际上是一个API,而不仅仅是一个属性/变量。

现在,了解innerHTML所谓的“验证”行为。当您修改innerHTML时,它会触发对浏览器的HTML编译器的调用。它是编译你的html文件/文件的相同编译器。 innerHTML调用的HTML编译器没什么特别之处。因此,无论你对html文件做什么,都可以传递给innerHTML(一个例外是嵌入式javascript不会被执行 - 可能出于安全原因)。

从浏览器开发人员的角度来看,这是有道理的。为什么在浏览器中包含两个单独的HTML编译器?特别是考虑到HTML编译器是巨大的,复杂的野兽。

缺点是不完整的HTML的处理方式与处理html文档的方式相同。如果<td>元素不在表格内,大多数浏览器都会将其剥离(正如您自己观察到的那样)。这基本上就是你要做的 - 创建无效/不完整的HTML。

有两种解决方法:

  1. 从页面中提取表格,然后使用字符串处理(regex et.el.)将<td>插入表格字符串,然后将innerHTML整个表格重新插入页面。

  2. 解析插入的HTML字符串,如果发现任何<td><tr>(或<option>)提取出html元素并使用DOM方法插入它。

  3. 不幸的是,两者都很痛苦。

答案 1 :(得分:0)

Mihai Stancu关于jquery的评论让我想到:如果你打电话给$("<tr></tr>"),jquery肯定会管理这个。我知道jquery有一个看起来像单个标签的字符串的快捷方式,但它也适用于复杂的HTML。

所以我深入研究了jquery源代码,找到了票证:

https://github.com/jquery/jquery/blob/6a0ee2d9ed34b81d4ad0662423bf815a3110990f/src/manipulation.js#L450

它正在使用正则表达式来检测字符串中第一个标记的名称,然后使用此信息来确定它需要包含什么“上下文”,以便innerHTML进程将其视为有效。我认为这种技术应该适用于所有格式良好的输入。

我已将此代码用于独立函数,该函数将任意字符串转换为DOM节点:

https://gist.github.com/gfxmonk/5299096