闲话多提交按钮的HTML表单
在 Web 应用中,最常用的服务器端与客户端的数据交互是通过提交 HTML 表单实现的。以 HTML 4.01 为例,规定当表单提交时,会将 <form>
元素中的 Successful controls 的当前取值提交到服务器。所谓成功控件需要有以下几个条件(但不仅限于此):
- 必须有控件名字,即必须包含 name 属性
- 如果表单中有多个提交按钮,只有被点击的那个按钮可以是成功的
- 不能是禁用状态,即不含 disabled 属性
- 不是 Reset button
现在我们考虑一下这样的场景,在某个表单中我们需要用到多个提交按钮(Submit button),就如同 WordPress 后台写文章那样的表单,我们在编辑完文章内容后,可以选择保存草稿或是直接发布。下面图中所示是一个简化的发布页面,可以填写文章标题、内容之后选某一种提交形式:
对应的 HTML 片段可以是这样:
1 2 3 4 5 6 | <form action="http://localhost/blog/new-post" method="POST"> <input type="text" name="title" id="title" /><br /> <textarea name="content" id="content" cols="30" rows="10"></textarea><br /> <input type="submit" id="save-post" value="Save Draft" /> <input type="submit" id="publish" value="Publish" /> </form> |
为了在提交时能区分是保存还是发布,需要通过 JavaScript 来为不同的按钮绑定不同的操作:
1 2 3 4 5 6 7 8 | //using jQuery $('#save-post').click(function() { //按保存草稿方式提交 }); $('#publish').click(function() { //按发布方式提交 }); |
那么提交的数据中如何来区分是保存草稿还是发布?通常我们会在 <form>
中添加一个隐藏的控件来保存这个状态,像这样:
<!-- value 可以是 publish 或 draft,默认为 publish --> <input type="hidden" name="post-status" id="post-status" value="publish" />
这样在 JS 中就只需要为按保存方式提交作相应处理:
1 2 3 4 | //using jQuery $('#save-post').click(function() { $('#post-status').val('draft'); }); |
这样如果按 Save Draft,那么 POST 出去的数据就应该是:
title=blah&content=blahblah&post-status=draft
OK,这样的确已经可以正常工作了,但是总觉得让人有些不爽:这么简单的工作为什么还要通过脚本来实现?有没有想过为什么要这样实现呢?事实上,因为在文章开始的地方提到的 HTML 规范中定义了一个表单中有多个提交按钮的情况下,只有一个可以是成功控件从而提交自己的数据值,也就是说,我们给提交按钮加上相同的 name 属性,只有一个按钮会提交相应的数据。那何必还需要隐藏控件?应该只需要这样:
1 2 3 4 5 6 | <form action="http://localhost/blog/post" method="POST"> <input type="text" name="title" id="title" /><br /> <textarea name="content" id="content" cols="30" rows="10"></textarea><br /> <input type="submit" name="post-status" id="save-post" value="Save Draft" /> <input type="submit" name="post-status" id="publish" value="Publish" /> </form> |
像这样把两个提交按钮的 name 属性都设为 post-status,就可以像服务器发送唯一的 post-status 值了,但有一个问题:post-status 的值只能和按钮上的文字一样了,给 Web 应用的 i18n 带来了很大的不便。其实 HTML 规范也考虑到了这个问题,这就是为什么我们会有 <button>
元素。HTML 中的按钮共有三种类型:
- submit buttons
- reset buttons
- push buttons
前两个不用说,第三种按钮没有默认的数据交互行为,需要依赖脚本才能起到作用。一般情况下我们使用的都是 type 属性分别为 submit / reset / button 的 <input>
元素,而 <button>
元素就是用来为这三类 <input>
提供更多渲染可能性的,<button>
中可以插入其他的 HTML 元素,比如可以加入 <img>
来给按钮增加一个图标,所以显然也可以使用任何你想使用的文本作为按钮上显示的文字。这个文字和按钮的取值 value 应当是分离的:
<button type="submit" name="post-type" id="save" value="draft">Save Draft</button> <button type="submit" name="post-type" id="publish" value="publish">Publish</button>
看上去很不错,不是么?可是很不幸,我们又遇到麻烦了:在 IE6/7 下,这样的方法完全不能工作。在 IE6 下,用这样的方式按 Save Draft 按钮,POST 出的参数值为:
title=blah&content=blahblah&post-status=Save+Draft&post-status=Publish
在 IE7 下,则是:
title=blah&content=blahblah&post-status=Save+Draft
可以看到,IE6/7 错误地把 <button>
的 innerHTML 当作了 value 发送出去了,而 IE6 甚至把没有点击的按钮也看作成功。WTF!本来很优雅的代码在现实中却是无法完美工作的。有人写了个针对 IE6/7 的 hack 来解决这个问题,即在 <button>
的点击事件中,disable 同一表单内的其他 <button>
并且用其 value 替换 innerHTML,但这样在网速比较慢的情况下(就是大多数情况下)会出现下面这样的画面:
这显然也是不能让人接受的。就是没有一个完美的方案,就是这样,所以又回到我们开头的地方了。用隐藏控件来 hold 住提交方式参数,用脚本来给它赋值,又变成了看起来最通用最简单的方案了。
另外有一种观点认为,一个表单就不该有多个提交按钮,提交按钮不该携带任何参数值,完全可以换成单选框、下拉菜单等等等等方式来实现。但实际上多个提交按钮在很多时候可以减少用户的交互操作,并且更为直观,绝对是有其存在价值的。拿 WordPress 的文章发布方式来说,现在就是多提交按钮的:
如果改成下面这样,就感觉十分别扭了:
总结一下:如果你可以抛弃 IE6/7,那么恭喜你,用最简单优雅的方式去实现吧!如果不能,那么,还是老老实实地用隐藏控件吧,慢慢等着 IE6/7 入土为安吧。
很久没写东西了,有点虎头蛇尾,其实没多少内容,发发牢骚而已。