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

Posted

技术标签:

【中文标题】您如何克服 HTML 表单嵌套限制?【英文标题】:How do you overcome the HTML form nesting limitation? 【发布时间】:2010-10-10 12:02:27 【问题描述】:

我知道 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 是在表单标签中定义的,而不是在每个单独的提交按钮中,我们唯一的选择是发布到通用 URL,然后启动“if...then...else”来确定提交的按钮。不是很优雅,但我们唯一的选择,因为我们不想依赖 JavaScript。

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

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

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

【问题讨论】:

这很有趣,但是根据有趣的注释 XHTML 确实允许间接表单嵌套 anderwald.info/internet/nesting-form-tags-in-xhtml - (X)HTML 不允许嵌套表单,如“form > form”,但允许“form > fieldset > form”,W3验证器说它是有效的,但是浏览器存在这种嵌套的错误。 Is it valid to have a html form inside another html form?的可能重复 @Nishi 评论链接的 linkrot 修复:web.archive.org/web/20170420110433/http://anderwald.info/… 【参考方案1】:

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

更新 删除 随便

然后动作脚本会根据单选按钮集的值采取不同的动作。

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

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

【讨论】:

【参考方案2】:

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

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

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

或者,如果您在公司防火墙或其他地方工作,并且每个人都禁用了 Javascript,您可能想要创建两个表单并使用一些 CSS 魔法,使其看起来像删除按钮是第一个表单的一部分。

【讨论】:

OP原版声明没有javascript 这个方法的问题是它是非RESTful的。保存和删除对应于对资源的不同操作:“保存”-> POST /my_resource(创建新资源)“保存”-> PUT /my_resource(修改现有资源)“删除”-> DELETE /my_resource(销毁资源)从 REST 角度来说,问题在于 POST 预计会创建一个新资源。当然,它不应该删除现有资源。【参考方案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>

【讨论】:

是的,感觉太老套了。此外,在一个非常大的页面中(想象选项卡和子选项卡以及在多个范围级别中嵌套提交按钮)几乎不可能将元素的屏幕位置与其在文档中的位置完全分开。 嗯,它总是在你想做什么和你能做什么之间取得平衡。看起来这些是选项 - 在服务器上处理的一种形式,或者一种难以维护的多种形式 - 明智地选择:) 是的,不幸的是,在 html 中存在所有这些限制,我将坚持支持的内容并将整个表单发送到服务器,然后为支持它的客户端不显眼地使用 javascript 来拦截表单提交并且只发送真正需要的东西。这是最好的妥协。【参考方案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,这将使一半的页面无法工作。

【讨论】:

除非我忽略了一些东西,否则这并不比链接到删除操作更好:最终结果将是对删除操作的 GET 请求(不好)。事实上,它有点粗俗,因为它需要 JavaScript 来完成一个普通的旧链接标签可以做的事情。【参考方案5】:

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

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

【讨论】:

【参考方案6】:

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

【讨论】:

关于这个值得链接的任何参考? 参见w3.org/TR/html5/forms.html#form-owner "默认情况下,与表单关联的元素与其最近的祖先表单元素相关联,但是,如果它是可重新关联的,则可以指定一个表单属性来覆盖它。" 【参考方案7】:

或者,您可以动态分配表单 action...可能不是最佳解决方案,但确实可以减轻服务器端逻辑...

<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>

【讨论】:

为什么?我是认真地问,因为没有简单的方法来获取单击了哪个按钮。您是说应该使用 jQuery 的 click() 方法来设置按钮的 onclick 处理程序吗?还是在提交表单之前处理点击事件是问题?【参考方案8】:

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

【讨论】:

使用 iframe 是个好主意,可惜在大多数情况下您需要 javascript 来调整它的大小(因为您不知道用户的文本大小,因此,按钮有多大将会)。 @Nicholas,你如何使用 Javascript 来调整它的大小? iframe 的 HTML 不能与 iframe 通信,反之亦然,除非它们在同一个域中。 @Nicholas,谢谢,只是想确保他们必须在同一个域中【参考方案9】:

针对 Yar 在对他自己的回答的评论中发布的问题,我展示了一些 JavaScript,它将调整 iframe 的大小。对于表单按钮的情况,可以安全地假设 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>

【讨论】:

【参考方案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 属性,因此同一表单中的不同按钮可以提交到不同的 URL。这个例子只是在表单外的工具栏上添加了一个克隆方法,但它在表单中的嵌套也是一样的。

<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。缺点是旧版浏览器不支持它们,因此您必须为旧版浏览器做一些 polyfill。

【讨论】:

+1 -- 但是很高兴知道浏览器对form= 的支持是什么 -- CanIUse 并没有真正说明。 caniuse.com/#feat=forms 是的,这很好,但截至今天,IE 不支持的功能仍然不符合“生产”使用的资格 使用&lt;input&gt; 声明按钮是不好的做法。 &lt;button&gt; 元素是 15 年前为此目的引入的。真正的人。 您的观点是不必要的争议,与 OP 的问题无关。 OP 在不使用 JavaScript 的情况下询问了表单嵌套限制,答案提供了解决方案。无论您使用&lt;input&gt; 还是&lt;button&gt; 元素都无关紧要,尽管如您所说,按钮通常更适合工具栏。顺便说一句,按钮元素已经 15 年没有得到所有浏览器的支持了。 @AKX 虽然是旧声明,但您可以在此处查看对此功能的支持:html5test.com/compare/feature/form-association-form.html。 IE 是一种注销,大多数其他桌面浏览器似乎都可以忍受。【参考方案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>

【讨论】:

【参考方案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>

所以在服务器端你只需寻找以宽度字符串“action:”开头的参数,其余部分告诉你要采取什么行动

所以当你点击按钮 Save 时,浏览器会向你发送 foo=asd&action:save=Save 之类的信息

【讨论】:

【参考方案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() 以保持布局不变,现在它就像一个魅力。

【讨论】:

这对我来说非常有效。我在一个 asp.net 页面上工作,它有一个全封闭的表格。我有一个用于 jQuery Validation 插件 (github.com/jzaefferer/jquery-validation) 的内部嵌套表单,虽然它在 FireFox 和 IE 中运行良好,但它在 Chrome 中失败并出现“TypeError: Cannot call method 'element' of undefined”。原来 validate() 初始化调用失败,因为它找不到内部表单,因为 Chrome 从使用 jQuery.html() 添加的 html 块中删除了它。添加一个小的虚拟表单首先会导致 Chrome 离开真实表单。 这似乎不再起作用了。 Firefox 去掉了第二个
标记,然后用第一个
标记终止了我的表单。这会在页面底部留下一个无效的 标记和一个无法正确提交的表单。 :(
对我非常有用。我正在将包含表单的 Web 小部件添加到 asp.net 页面中。对 WebForm 感到惊讶的是,它总是生成一个包含 HTML 正文中所有内容的表单。封闭的表单成为 Web 小部件令人头疼的问题。对于这种情况,虚拟形式似乎是一种快速有效的解决方案。到目前为止,它在 Chrome 和 Safari 中完美运行,尚未测试 Firefox。感谢您的分享,为我节省了很多时间!【参考方案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>

【讨论】:

【参考方案15】:

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

【讨论】:

欢迎来到Stack Overflow。最好描述一下这是如何解决问题的,而不是仅仅说你做了什么。有关在 Stack Overflow 上创建出色答案的提示,请参阅 How to Answer。谢谢!【参考方案16】:

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

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

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

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

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

【讨论】:

以上是关于您如何克服 HTML 表单嵌套限制?的主要内容,如果未能解决你的问题,请参考以下文章

如何克服根域 CNAME 限制?

django 模型:如何克服“通过”ManyToMany 选项限制

phonegap/cordova 的 Media 类是不是克服了 html5 中 iOS/android 音频的限制?

如何克服 Snowflake Varchar (16,777,216) 加载图像数据的限制

如何克服 Autodesk Forge Viewer 中的窗口选择限制?

Golang - 如何克服 bufio 的 Scan() 缓冲区限制?