0

以下 javascript(在 chrome 控制台中运行)没有达到我的预期:

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

<tr>标签消失了!

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

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

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

因此,似乎构造本身(<tr>不在 a 内<table>)是允许的,但是使用 innerHTML 将其放置在那里的方法不起作用 - 也许这会通过一些 html 清理,在创建 DOM 节点时删除不严格的东西直接不接受同样的验证。

我的问题:有没有办法从字符串中填充任意 DOM 节点而不会遇到此类清理/验证问题?我的用例最终会得到完全有效的结构(我计划稍后将其作为子级),但是当我尝试构建各个部分时,浏览器阻止了我。

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

关于我为什么要这样做的一些背景:

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

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

第二点是我是怎么遇到这个bug的。我的模板包含一个子模板。

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 和 chrome 中的工作方式都相同,所以我不认为我只是遇到了浏览器错误。

4

2 回答 2

2

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

最容易被误解的事情之一innerHTML源于 API 的设计方式。它重载了+and=运算符来执行 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. 从页面中提取表格,然后使用字符串处理(正则表达式等)将其<td>插入表格字符串,然后innerHTML将整个表格重新插入页面。

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

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

于 2013-04-02T07:43:11.433 回答
0

Mihai Stancu 对 jquery 的评论让我想到:如果你调用$("<tr></tr>"). 我知道 jquery 有一个看起来像单个标签的字符串的快捷方式,但它也必须适用于复杂的 HTML。

因此,我深入研究了 jquery 源代码,并找到了票:

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

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

我已将此代码应用到一个独立函数中,它将任意字符串转换为 DOM 节点:

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

于 2013-04-03T07:31:10.390 回答