如何克服 HTML 表单嵌套限制?

How do you overcome the HTML form nesting limitation?

提问人:gCoder 提问时间:2/28/2009 最后编辑:Peter MortensengCoder 更新时间:10/23/2020 访问量:113076

问:

我知道 XHTML 不支持嵌套表单标签,并且我已经在 Stack Overflow 上阅读了有关此主题的其他答案,但我仍然没有找到解决问题的优雅解决方案。

有人说你不需要它,他们想不出需要这种情况的场景。好吧,就我个人而言,我想不出我不需要它的场景。

让我们看一个非常简单的例子:

您正在制作一个博客应用程序,并且您有一个表单,其中包含一些用于创建新帖子的字段和一个带有“操作”的工具栏,例如“保存”、“删除”、“取消”。

<form
 action="/post/dispatch/too_bad_the_action_url_is_in_the_form_tag_even_though_conceptually_every_submit_button_inside_it_may_need_to_post_to_a_diffent_distinct_url"
method="post">

    <input type="text" name="foo" /> <!-- several of those here -->
    <div id="toolbar">
        <input type="submit" name="save" value="Save" />
        <input type="submit" name="delete" value="Delete" />
        <a href="/home/index">Cancel</a>
    </div>
</form>

我们的目标是以一种不需要 JavaScript 的方式编写表单,只需要普通的旧 HTML 表单和提交按钮。

由于操作 URL 是在 Form 标记中定义的,而不是在每个单独的提交按钮中定义的,因此我们唯一的选择是发布到通用 URL,然后开始“如果...然后。。。else“来确定提交的按钮的名称。不是很优雅,但我们唯一的选择,因为我们不想依赖JavaScript。

唯一的问题是,按“删除”将提交服务器上的所有表单字段,即使此操作唯一需要的是带有后 ID 的隐藏输入。在这个小例子中没什么大不了的,但我的 LOB 应用程序中有数百个(可以这么说)字段和选项卡的表单,这些表单(由于要求)必须一次性提交所有内容,无论如何这似乎非常低效和浪费。如果支持表单嵌套,我至少可以将“删除”提交按钮包装在它自己的表单中,只有 post-id 字段。

您可能会说“只需将”删除“实现为链接而不是提交”。这在很多级别上都是错误的,但最重要的是,因为像这里的“删除”这样的副作用操作永远不应该是 GET 请求。

所以我的问题(特别是对于那些说他们不需要表单嵌套的人)是你做什么?有没有我缺少的优雅的解决方案,或者底线真的是“要么需要 JavaScript,要么提交所有内容”?

HTML 表单 嵌套

评论

18赞 Nishi 5/14/2010
这很有趣,但 XHTML 确实允许根据有趣的注释 anderwald.info/internet/nesting-form-tags-in-xhtml 间接表单嵌套 - (X)HTML 不允许嵌套形式,例如“表单>表单”,但允许“表单>字段集>表单”,W3 验证器说它是有效的,但浏览器存在这种嵌套的错误。
0赞 Castro Roy 6/5/2015
可能的重复 在另一个 html 表单中有一个 html 表单是否有效?
1赞 albert 9/14/2019
@Nishi的评论链接的 linkrot 修复:web.archive.org/web/20170420110433/http://anderwald.info/...

答:

2赞 Ron Savage 2/28/2009 #1

在没有 javascript 的情况下,我这样做的一种方法是添加一组单选按钮来定义要执行的操作:

  • 更新
  • 删除
  • 无论什么

然后,操作脚本将根据单选按钮集的值执行不同的操作。

另一种方法是按照您的建议在页面上放置两个表单,只是不要嵌套。不过,布局可能难以控制:

<form name="editform" action="the_action_url"  method="post">
   <input type="hidden" name="task" value="update" />
   <input type="text" name="foo" />
   <input type="submit" name="save" value="Save" />
</form>

<form name="delform" action="the_action_url"  method="post">
   <input type="hidden" name="task" value="delete" />
   <input type="hidden" name="post_id" value="5" />
   <input type="submit" name="delete" value="Delete" />
</form>

使用处理脚本中隐藏的“任务”字段进行适当的分支。

55赞 One Crayon 2/28/2009 #2

我会完全按照您的描述实现这一点:将所有内容提交到服务器并执行简单的 if/else 来检查单击了哪个按钮。

然后,我将实现一个 Javascript 调用,该调用绑定到表单的 onsubmit 事件中,该事件将在提交表单之前进行检查,并且仅将相关数据提交到服务器(可能通过页面上的第二个表单,其中包含将事物作为隐藏输入处理所需的 ID,或者使用您需要作为 GET 请求传递的数据刷新页面位置, 或者对服务器进行 Ajax 发布,或者......

这样,没有 Javascript 的人就可以很好地使用表单,但服务器负载被抵消了,因为有 Javascript 的人只提交了所需的单个数据。让自己专注于只支持其中之一确实不必要地限制了您的选择。

或者,如果你在公司防火墙或其他东西后面工作,并且每个人都禁用了 Javascript,你可能想做两个表单,并施展一些 CSS 魔法,让它看起来像是第一个表单的一部分。

评论

15赞 Jason 2/28/2009
OP 原文声明没有 javascript
26赞 user132447 2/25/2012
这种方法的问题在于它是非 RESTful 的。保存和删除对应于资源的不同操作: “Save” -> POST /my_resource (创建新资源) “Save” -> PUT /my_resource (修改现有资源) “Delete” -> DELETE /my_resource (销毁资源) REST完全来说,问题在于 POST 应该创建一个新资源。当然,它永远不应该删除现有资源。
16赞 Jason 2/28/2009 #3

您可以将表单并排放在页面上,但不能嵌套。然后使用CSS使所有按钮排列得漂亮?

<form method="post" action="delete_processing_page">
   <input type="hidden" name="id" value="foo" />
   <input type="submit" value="delete" class="css_makes_me_pretty" />
</form>

<form method="post" action="add_edit_processing_page">
   <input type="text" name="foo1"  />
   <input type="text" name="foo2"  />
   <input type="text" name="foo3"  />
   ...
   <input type="submit" value="post/edit" class="css_makes_me_pretty" />
</form>

评论

5赞 gCoder 2/28/2009
是的,感觉太骇人了。此外,在一个非常大的页面中(想象一下选项卡和子选项卡,以及将提交按钮嵌套在多个范围级别中),几乎不可能将元素的屏幕位置与它们在文档中的位置完全分开。
3赞 Jason 2/28/2009
好吧,它总是在你想做的事情和你能做的事情之间取得平衡。 看起来这些是选项 - 要么在服务器上处理一个表单,要么一个难以维护的多个表单 - 明智地选择:)
3赞 gCoder 2/28/2009
是的,不幸的是,由于 html 中的所有这些限制,我将坚持支持的内容并将整个表单发送到服务器,然后不显眼地使用 javascript 让支持它的客户端拦截表单提交并只发送真正需要的内容。这是最好的折衷方案。
-1赞 Pim Jager 2/28/2009 #4

如果你真的不想使用多个表单(正如 Jason 所建议的那样),那么使用按钮和 onclick 处理程序。

<form id='form' name='form' action='path/to/add/edit/blog' method='post'>
    <textarea name='message' id='message'>Blog message here</textarea>
    <input type='submit' id='save' value='Save'>
</form>
<button id='delete'>Delete</button>
<button id='cancel'>Cancel</button>

然后在 javascript 中(为了方便起见,我在这里使用 jQuery)(尽管添加一些 onclick 处理程序是相当矫枉过正的)

$('#delete').click( function() {
   document.location = 'path/to/delete/post/id'; 
});
$('#cancel').click( function() {
   document.location = '/home/index';
});

我也知道,如果没有 javascript,这将使一半的页面无法工作。

评论

1赞 Kyle Fox 2/25/2011
除非我忽略了某些东西,否则这并不比链接到删除操作更好:最终结果将是对删除操作的 GET 请求(错误)。事实上,它有点粗暴,因为它需要 JavaScript 来完成普通旧链接标签可以做的事情。
4赞 AmbroseChapel 3/1/2009 #5

我认为杰森是对的。如果您的“删除”操作是最小的,请将其单独设置为一个表单,并将其与其他按钮对齐,以使界面看起来像一个统一的表单,即使它不是。

或者,当然,重新设计你的界面,让人们完全删除其他地方,这根本不需要他们看到巨大的形式。

15赞 Nishi 5/14/2010 #6

HTML5 有一个“表单所有者”的概念——输入元素的“表单”属性。它允许模拟嵌套形式并将解决问题。

评论

1赞 dakab 3/26/2016
有什么值得链接的参考资料吗?
0赞 Nishi 3/27/2016
请参阅 w3.org/TR/html5/forms.html#form-owner“默认情况下,表单关联元素与其最近的祖先表单元素相关联,但是,如果它是可重新关联的,则可以指定表单属性来覆盖此元素。
-2赞 Qiniso 1/27/2011 #7

或者,您可以即时分配表单 actiob...可能不是最好的解决方案,但肯定可以缓解服务器端逻辑......

<form name="frm" method="post">  
    <input type="submit" value="One" onclick="javascript:this.form.action='1.htm'" />  
    <input type="submit" value="Two" onclick="javascript:this.form.action='2.htm'" />  
</form>

评论

0赞 Zack Morris 11/13/2012
为什么?我是认真问的,因为没有简单的方法可以获得点击了哪个按钮。你是说应该使用jQuery的click()方法来设置按钮的onclick处理程序吗?还是在提交表单之前处理点击事件是问题所在?
-1赞 Dan Rosenstark 3/5/2011 #8

对嵌套表单使用 an。如果他们需要共享字段,那么......它不是真正的嵌套。iframe

评论

1赞 Nicholas Shanks 9/7/2011
使用 iframe 是个好主意,但遗憾的是,在大多数情况下,您需要 javascript 来调整它的大小(因为您无法知道用户的文本大小是多少,因此按钮会有多大)。
0赞 Dan Rosenstark 9/7/2011
@Nicholas,如何使用 Javascript 来调整它的大小?iframed HTML 无法与 iframe 通信,反之亦然,除非它们位于同一域中。
0赞 Dan Rosenstark 9/10/2011
@Nicholas,谢谢,只是想确保它们必须在同一域中
-1赞 Nicholas Shanks 9/9/2011 #9

为了回答 Yar 在评论中发表的一个问题,我展示了一些可以调整 iframe 大小的 JavaScript。对于表单按钮,可以安全地假设 iframe 将位于同一域中。这是我使用的代码。您必须更改自己站点的数学/常量:

function resizeIFrame(frame)
{
    try {
        innerDoc = ('contentDocument' in frame) ? frame.contentDocument : frame.contentWindow.document;
        if('style' in frame) {
            frame.style.width = Math.min(755, Math.ceil(innerDoc.body.scrollWidth)) + 'px';
            frame.style.height = Math.ceil(innerDoc.body.scrollHeight) + 'px';
        } else {
            frame.width = Math.ceil(innerDoc.body.scrollWidth);
            frame.height = Math.ceil(innerDoc.body.scrollHeight);
        }
    } catch(err) {
        window.status = err.message;
    }
}

然后这样称呼它:

<iframe ... frameborder="0" onload="if(window.parent && window.parent.resizeIFrame){window.parent.resizeIFrame(this);}"></iframe>
186赞 shovavnik 12/5/2011 #10

我知道这是一个老问题,但HTML5提供了一些新的选择。

第一种方法是将窗体与标记中的工具栏分开,为删除操作添加另一个窗体,然后使用属性将工具栏中的按钮与其各自的窗体相关联。form

<form id="saveForm" action="/post/dispatch/save" method="post">
    <input type="text" name="foo" /> <!-- several of those here -->  
</form>
<form id="deleteForm" action="/post/dispatch/delete" method="post">
    <input type="hidden" value="some_id" />
</form>
<div id="toolbar">
    <input type="submit" name="save" value="Save" form="saveForm" />
    <input type="submit" name="delete" value="Delete" form="deleteForm" />
    <a href="/home/index">Cancel</a>
</div>

这个选项非常灵活,但原来的帖子还提到,可能需要使用单个表单执行不同的操作。HTML5 再次派上用场。您可以在提交按钮上使用该属性,以便同一表单中的不同按钮可以提交到不同的网址。此示例只是将一个克隆方法添加到窗体外部的工具栏中,但它的工作方式与嵌套在窗体中的方法相同。formaction

<div id="toolbar">
    <input type="submit" name="clone" value="Clone" form="saveForm"
           formaction="/post/dispatch/clone" />
</div>

http://www.whatwg.org/specs/web-apps/current-work/#attributes-for-form-submission

这些新功能的优点是它们无需 JavaScript 即可以声明方式完成所有这些工作。缺点是它们在较旧的浏览器上不受支持,因此您必须为较旧的浏览器进行一些填充。

评论

5赞 AKX 5/15/2012
+1 -- 但很高兴知道浏览器支持什么 -- CanIUse 并没有真正说。caniuse.com/#feat=formsform=
1赞 Jako 9/26/2012
是的,这很好,但截至今天,IE不支持的功能仍然使它失去了“生产”使用的资格
4赞 Nicholas Shanks 12/6/2012
使用按钮声明是一种不好的做法。该元素是 15 年前为此目的引入的。真的是人。<input><button>
27赞 shovavnik 12/15/2012
你的观点是不必要的争议,与OP的问题无关。OP 询问了不使用 JavaScript 的表单嵌套限制,答案提供了一个解决方案。是否使用 or 元素无关紧要,尽管正如您所说,按钮通常更适合工具栏。顺便说一句,15 年来,所有浏览器都不支持按钮元素。<input><button>
6赞 fatty 12/22/2013
@AKX 虽然是旧声明,但您可以在此处查看对此功能的支持:html5test.com/compare/feature/form-association-form.html。IE是一笔勾销,大多数其他桌面浏览器似乎是可以容忍的。
-1赞 mordy 3/16/2012 #11

我刚刚想出了一个用jquery做的好方法。

<form name="mainform">    
    <div id="placeholder">
    <div>
</form>

<form id="nested_form" style="position:absolute">
</form>

<script>
   $(document).ready(function(){
      pos = $('#placeholder').position();
      $('#nested_form')
         .css('left', pos.left.toFixed(0)+'px')
         .css('top', pos.top.toFixed(0)+'px');
   });
</script>
0赞 Lauri Lüüs 11/22/2012 #12

好吧,如果您提交表单,浏览器还会发送输入提交名称和值。 所以你能做的是

<form   
 action="/post/dispatch/too_bad_the_action_url_is_in_the_form_tag_even_though_conceptually_every_submit_button_inside_it_may_need_to_post_to_a_diffent_distinct_url"  
method="post">  

    <input type="text" name="foo" /> <!-- several of those here -->  
    <div id="toolbar">
        <input type="submit" name="action:save" value="Save" />
        <input type="submit" name="action:delete" value="Delete" />
        <input type="submit" name="action:cancel" value="Cancel" />
    </div>
</form>

因此,在服务器端,您只需查找以 width 字符串“action:”开头的参数,其余部分就会告诉您要执行的操作

因此,当您单击“保存”按钮时,浏览器会向您发送类似 foo=asd&action:save=Save 的内容

11赞 B.D.Joe 12/6/2012 #13

有点老话题,但这个可能对某人有用:

正如上面提到的 - 您可以使用虚拟表单。 前段时间我不得不克服这个问题。起初,我完全忘记了这个 HTML 限制,只是添加了嵌套表单。结果很有趣 - 我从嵌套中丢失了我的第一个表单。然后,事实证明,在实际的嵌套表单之前简单地添加一个虚拟表单(将从浏览器中删除)是一种“技巧”。

就我而言,它看起来像这样:

<form id="Main">
  <form></form> <!--this is the dummy one-->
  <input...><form id="Nested 1> ... </form>
  <input...><form id="Nested 1> ... </form>
  <input...><form id="Nested 1> ... </form>
  <input...><form id="Nested 1> ... </form>
  ......
</form>

适用于 Chrome、Firefox 和 Safari。IE 最多 9(不确定 10)和 Opera 不会检测主窗体中的参数。无论输入如何,$_REQUEST 全局都是空的。内在形式似乎在任何地方都很好用。

尚未测试此处描述的另一个建议 - 围绕嵌套表单的字段集。

编辑:框架集不起作用! 我只是在其他表单之后添加了主表单(不再嵌套表单),并使用jQuery的“克隆”在按钮单击时复制表单中的输入。将 .hide() 添加到每个克隆的输入中以保持布局不变,现在它就像一个魅力。

评论

0赞 John - Not A Number 11/21/2013
这对我来说非常有效。我正在制作一个 asp.net 页面,它有一个全封闭的形式。我有一个内部嵌套形式用于 jQuery 验证插件 ( github.com/jzaefferer/jquery-validation ),虽然它在 FireFox 和 IE 中完美运行,但在 Chrome 中失败并出现“TypeError:无法调用未定义的方法'element'”。事实证明,validate() 初始化调用失败,因为它找不到内部表单,因为 Chrome 将其从使用 jQuery.html( 添加的 html 块中删除。首先添加一个小的虚拟表单会导致 Chrome 离开真实的表单。
0赞 Tarquin 5/20/2015
这似乎不再起作用了。Firefox 剥离了第二个 <form> 标签,然后用第一个 </form> 标签终止了我的表单。这会在页面底部留下一个无效的</表单>标记,并且表单无法正确提交。:(
0赞 JM Yang 11/19/2015
对我来说非常有用。我正在将包含表单的 Web 小部件添加到 asp.net 页面中。对 WebForm 感到惊讶的是,它总是生成一个包含 HTML 正文中所有内容的表单。封闭的表单成为 Web 小部件的头疼问题。对于这种情况,虚拟形式似乎是一种快速有效的解决方案。到目前为止,它在 Chrome 和 Safari 中运行良好,尚未测试 Firefox。谢谢你的分享,为我节省了很多时间!
0赞 scotweb 3/24/2013 #14

我的解决方案是让按钮调用JS函数,这些函数编写然后提交表单与主表单

<head>
<script>
function removeMe(A, B){
        document.write('<form name="removeForm" method="POST" action="Delete.php">');
        document.write('<input type="hidden" name="customerID" value="' + A + '">');
        document.write('<input type="hidden" name="productID" value="' + B + '">');
        document.write('</form>');
        document.removeForm.submit();
}
function insertMe(A, B){
        document.write('<form name="insertForm" method="POST" action="Insert.php">');
        document.write('<input type="hidden" name="customerID" value="' + A + '">');
        document.write('<input type="hidden" name="productID" value="' + B + '">');
        document.write('</form>');
        document.insertForm.submit();
}
</script>
</head>

<body>
<form method="POST" action="main_form_purpose_page.php">

<input type="button" name="remove" Value="Remove" onclick="removeMe('$customerID','$productID')">
<input type="button" name="insert" Value="Insert" onclick="insertMe('$customerID','$productID')">

<input type="submit" name="submit" value="Submit">
</form>
</body>
-1赞 Gregws 11/23/2013 #15

我通过包含一个复选框来解决这个问题,具体取决于该人想要做什么形式。 然后用1个按钮提交整个表格。

评论

2赞 Qantas 94 Heavy 11/23/2013
欢迎来到 Stack Overflow。最好描述一下这是如何解决问题的,而不是仅仅说你做了什么。请参阅如何回答,了解有关在 Stack Overflow 上创建出色答案的提示。谢谢!
1赞 Rob Crowther 3/20/2014 #16

我仍然对这个讨论感兴趣。在原始帖子的背后是OP似乎共享的“要求”,即具有向后兼容性的形式。作为一个在撰写本文时工作有时必须支持IE6(以及未来几年)的人,我挖掘了这一点。

在不推动框架的情况下(所有组织都希望在兼容性/健壮性方面放心,我不是用这个讨论作为框架的理由),这个问题的Drupal解决方案很有趣。Drupal也是直接相关的,因为该框架长期以来一直有“它应该在没有Javascript的情况下工作(仅在你想要的时候)”的政策,即OP的问题。

Drupal使用它相当广泛的form.inc函数来查找triggering_element(是的,这是代码中的名称)。有关form_builder,请参阅 API 页面上列出的代码底部(如果您想深入了解详细信息,建议使用源代码 - drupal-x.xx/includes/form.inc)。构建器使用自动 HTML 属性生成,通过它,可以在返回时检测按下了哪个按钮,并采取相应的行动(这些也可以设置为运行单独的进程)。

除了表单构建器之外,Drupal还将数据“删除”操作拆分为单独的URL/表单,这可能是由于原始帖子中提到的原因。这需要某种搜索/列表步骤(呻吟另一种形式!但用户友好)作为初步。但这的优点是消除了“提交所有内容”的问题。包含数据的大表单用于其预期目的,数据创建/更新(甚至是“合并”操作)。

换句话说,解决问题的一种方法是将表单一分为二,然后问题就消失了(HTML 方法也可以通过 POST 进行更正)。