最近学会了 HTML 的 form 属性的使用方式,在此分享。
需求
应用有这样一个页面:
需求是提交按钮、主体字段、次要字段放在布局的三个位置,点击保存按钮的时候,主体字段和次要字段一起提交。
原始的想法是用一个大的 <form> 标签包含整个页面,实现如下:
<form action="...">
<nav>
<button type="submit">Save</button>
</nav>
<main>
<input type="text" name="title">
<textarea name="content">
</main>
<aside>
<input type="text" name="tags">
</aside>
</form>
如果页面只有一个表单,那这个实现没问题。如果有其他小的表单,例如发布文章、删除文章等,就会遇到 form 不能嵌套的问题。
一种解决思路是使用前端 JavaScript 提交,那么能不能通过 HTML 原生功能实现呢?
解决
经过搜索我发现 HTML 原生控件有个 form 属性可以做这件事。它的用法是:
<form id="some-form"></form>
<input form="some-form">
即使 <input> 在 <form> 的外部,也可以通过 form 属性把表单和控件关联起来。在表单提交的时候,外部的 <input> 也会作为表单的一部分一起提交。
所以我把上面的代码修改为:
<nav>
<button type="submit" form="editor-form">Save</button>
</nav>
<main>
<form id="editor-form" action="...">
<input type="text" name="title">
<textarea name="content">
</form>
</main>
<aside>
<input type="text" name="tags" form="editor-form">
</aside>
现在编辑器的表单只包含主要部分,次要内容都是通过 form 属性关联。这样页面也可以放下其他表单而不用担心嵌套。
其他
虽然 form 属性可以把表单和外部控件关联起来,但在 DOM 树上外部控件不是 form 的子节点,如果之前有前端逻辑依赖于元素的父子关系,或者依赖事件冒泡,那么这部分逻辑会需要改写。例如:
// 不可用
const form = input.closest("form")
// 可用
const form = input.form
如果你使用 Rails 的 ActiveStorage,那么内置 helper 的 direct_upload: ture 不可用,因为它依赖于 form.querySelector("input"),需要另外处理。