庞杂的表单控件元素
-
- 2、表单体系中的HTML属性
-
- 2.1、表单中的name属性与行为
- 2.2、深入讲讲autocomplete属性
-
- [2.2.1、autocomplete off无效怎么办](#2.2.1、autocomplete off无效怎么办)
- 2.2.2、新时代的autocomplete
- 2.2.3、自动填充样式的重置
- 2.3、表单验证属性及方法
2、表单体系中的HTML属性
2.1、表单中的name属性与行为
如果你是通过手动拼接数据来处理表单提交的,那么name属性的重要性就一般,但如果你是使用表单行为来处理表单提交的,那么name属性就极为重要,因为此时name属性值就是POST或GET请求数据的提交字段。例如:

此时我们可以使用FormData对象获取提交的数据,遍历之,就可以看到数据的key值就是表单元素的name属性值,代码如下所示:


此时点击"提交"按钮,就可以看到即将POST提交的数据的键值对了,结果如图所示,可以看到_token正好是hidden类型的<input>元素的name属性值,而comment正好是<textarea>元素的name属性值。

如果你希望借助浏览器的原生能力简化开发的成本,就需要将用户数据交互的字段名称写在HTML元素上,因此,面向行为的表单交互是重HTML而轻JavaScript的,和面向数据的处理方法有所不同。很多开发人员不习惯这一点,建议大家保持耐心,多积累,然后多实践,你会打开新世界的大门。
name属性的分组行为
在表单控件体系中,name属性还有一个非常重要的作用,那就是分组,此行为尤其适用于单选按钮组和复选框组。
例如,只有name属性值一致的单选按钮元素才会归为一组,只会有最多一项元素被选中,但是其中有个细节有必要讲一下,那就是<form>元素是可以中断这种分组行为的,例如下面的HTML代码:

表单外有两个单选按钮元素,表单内有一个单选按钮元素,这三个单选按钮元素的name属性值是一样的,但它们并不是一组,而是两组,表单外的是一组,表单内的是一组。如果此时希望在不改变DOM元素位置的情况下,让所有单选按钮元素归为一组,则可以使用form属性。具体而言,就是将表单外的单选按钮元素的form属性值设置为目标<form>元素的id属性值,例如:

此时,三个单选按钮又是一组了。
最后再讲讲复选框成组后的数据提交细节,例如有如下所示的HTML代码:

假设我们选择了复选框的前三项,那么提交的数据结构会是什么样的呢?结果是三个同字段的不同值的数据,布局效果和输出结果如图所示。

而目前主流的实现都是一个键值对,值使用逗号分隔,也就是hobby:"1,2,3",因此,这种场景往往需要额外处理一下,或由后端开发人员来做,或由前端人员自己来做,例如:


2.2、深入讲讲autocomplete属性
表单自动填充是个好功能,因为可以省去用户自己输入的麻烦。
比如,一个name属性值是email的输入框,聚焦点击后,可能会出现下图所示的邮箱列表。

奇怪,怎么出现了手机号?
可能是某个产品的name属性值是email,却支持手机号输入,然后被浏览器记住了。
大多数时候,用户很喜欢这个功能。但是有时候,用户并不需要它。
比如验证码输入框,每次的验证码都是随机的,没有任何理由需要浏览器进行记忆。
还有,如果采用了浏览器的自动填充input输入框的样式,比如背景色,就会变成浏览器默认的自动填充背景色,如图所示(我的Windows系统下显示为淡蓝色)。

而这种浏览器默认的色值往往和产品的主题色并不一致,产品负责人或者设计师往往会要求进行重置。此时,表单自动填充的行为就变得不美丽了,用户希望能取消该行为。
浏览器也确实提供了取消自动填充的HTML属性autocomplete以及匹配自动填充状态的:autofill伪类,既然浏览器有了能力,似乎问题到此结束了。
然而并非如此,有很多时候,明明设置了autocomplete="off",但是依然有自动填充。这是怎么回事呢?
2.2.1、autocomplete off无效怎么办
这一点分情况而定。一种情况是阻止非交互自动填充,另一种情况是阻止交互式自动填充。
(1)阻止非交互自动填充
所谓非交互自动填充,指的是无须用户操作,一进入页面,浏览器就帮忙把对应的数据填充好了。
账号和密码自动填充是比较常见的。例如,有如下所示的HTML代码:

如果你曾让浏览器记住当前域名下的密码,则一进入页面就有可能会看到下图所示的自动填充示意。

此时,你给输入框元素,哪怕给外面的<form>元素(如果有)设置autocomplete=off都是无效的,那该怎么办?
根据我的测试,效果比较好的是一种名为"李代桃僵"的方法,就是在表单元素的前面插入两个替换元素,就像下面这样:


还有另外的方法,就是设置autocomplete="new-password",此方法适用于Firefox浏览器,对于Chrome浏览器是无效的。此时,可以考虑下面这种方法,就是给密码输入框设置readonly属性,并在第一次按下的时候移除以规避自动填充。

虽然上面的方法可以实现在默认进入的时候没有自动填充,但是,当用户点击输入框的时候,又会出现类似下图这样的提示,那么又该如何规避呢?

无解!没必要规避,建议无视此密码提示框。
如果非要解决也可以,使用普通<input>输入框覆盖在密码输入框上面,将颜色设置为透明,将光标颜色设置为文字颜色,隐藏边框和背景色。代码如下:


然后将密码输入框的value值和覆盖的输入框实时同步即可。此时,密码框聚焦的时候就不会有系统的密码管理提示了。
(2)阻止交互式自动填充
该问题解决起来相对简单,多出现在相同name属性值的输入框,或者相邻输入框有着相同name历史或者相同类型的数据记录的时候。
此时,要么使用autocomplete="off",要么使用autocomplete="new-password"。
2.2.2、新时代的autocomplete
过去,autocomplete支持的属性值比较单纯,只有on和off,而现在autocomplete支持的属性值有几十个。
对,你没看错,有几十个,容易理解的有username、new-password、current-password等,长得像"妖魔鬼怪"的有cc-name、bday-day、impp等。autocomplete支持的属性值和释义参见下表。



我自己测试了几个属性值,比如username,发现和设置属性值为on的效果一样:

如果设置的是随机的乱七八糟的值,则和off的效果一样:

这个表现......有些出乎意料,按照历史经验,随着浏览器版本的迭代,这些行为大概率会发生变化。
2.2.3、自动填充样式的重置
下图所示的是Firefox浏览器下的自动填充效果,是非常显眼的淡黄色背景(最终颜色与操作系统和浏览器版本相关,此颜色表现仅作参考)。

CSS中有个名为:autofill的伪类,可以匹配已经自动填充的输入框,因此,理论上是可以用来重置自动填充时的高亮背景色的:

然而,结果很遗憾,上面的代码并不能重置系统的自动填充背景色。其实仔细一想就好理解了,既然浏览器要保证输入框高亮背景色能够显示,那么肯定是不可能被普普通通一个CSS背景样式重置的。
所以这里想要让自动填充的高亮背景色不显示,需要使用其他方法,目前业界用得比较多的就是使用巨大的box-shadow内阴影进行覆盖,代码如下:

此时,自动填充带来的高亮背景色就不可见了。
2.3、表单验证属性及方法
表单验证是个完整的体系,包括HTML属性、CSS伪类和JavaScript方法。想要全部学通透,需要巨大的学习成本,不建议花大量的时间和精力学习,至少要等到往专家级别发展的时候才细致深入,因为它并不实用,有两个限制。其一,也是最大的问题,其样式无法自定义,也就是浏览器自带的表单验证提示样式是无法自定义的,不能在实际项目中使用。其二,它无法覆盖所有的验证场景,例如,密码重复输入时,前后输入的值如果不同,后面的输入框是无法匹配:invalid伪类的,最终的验证提示效果也需要使用JavaScript代码进行判断并调用提示方法才行。
那么问题就来了,既然都需要动用大量的JavaScript代码开发,为何不使用自定义的组件呢?至少样式还能自定义呢。
从实用角度看,我们并不需要全盘接受表单验证体系的所有知识,只需要了解其投入产出比较高的精华部分即可,且听我慢慢道来。
CSS代码中有不少伪类是与表单验证密切相关的,如:enabled伪类、:disabled伪类、:required伪类、:optional伪类或是:valid、:invalid、:user-valid、:user-invalid伪类。其中,设置了disabled属性的表单控件元素是不参与表单验证的,应该跳过,这些元素可以使用:disabled伪类匹配。设置了required属性的表单元素是必填项,可以使用:required伪类匹配,我们可以利用此特性实现必填项的标题后面有红色的星号提示。例如:


此时,必填的"邮箱"和"评论"这两个标题后面就出现了如图所示的红色星号提示了,日后维护起来就会相对简单一些。

如果表单控件元素有任何不合法的地方,都会匹配:invalid伪类,同时,<form>元素也会匹配,我们可以利用此特性在简易表单中实现内容未输入则提交按钮禁用的效果。例如:

此时,默认情况下,按钮会表现得如禁用一般,如图左侧所示。当用户输入合法的内容后,禁用按钮自动恢复,如图右侧所示。整个交互效果不需要任何JavaScript代码就可以实现,这就是前面提到的投入产出比较高的精华知识。

不过,上述小技巧只能用在简易表单中,主要原因不是技术限制,而是出于交互体验的考虑。对于复杂表单,验证规则也必然复杂,很容易出现"明明用户该写的内容都写了,但是用户依然不知道哪里写错"的情况,因为按钮一直都是被禁用的。这种场景更适合后置验证,也就是按钮一直是可点击的,点击后再验证,好处是用户提交后可以知道出错的原因,不会进入交互死胡同。
不合法行为经过表单提交识别,会匹配:user-invalid伪类,这特别适合用于对表单控件元素本身进行出错提示,例如将边框标记为红色,例如:

这样既提示了用户,又不会像:invalid伪类一样,出现用户明明什么内容还没写就提示出错的尴尬局面。
无论是:invalid伪类,还是:user-invalid伪类,匹配的HTML规则都是一样的,包括下面这些:
- epub:type="email"
- epub:type="number"
- epub:type="tel"
- epub:type="url"
- step
- min
- max
- required
- pattern
- minlength
- maxlength
- multiple
这些HTML属性的作用都一目了然,没什么好说的,pattern属性除外,毕竟这可是在HTML属性中直接支持正则表达式啊。例如:

表示验证码只能是4~6位的数字,pattern属性的正则和JavaScript中的正则语法一致,区别在于,pattern属性是完全匹配的,也就是你可以理解自动在正则的前面添加了开始符号"^"和结束符号"$"。另外还有个细节,pattern属性对<textarea>元素是无效的。
总之,目前表单元素实用的部分就是基于现有的HTML验证属性和CSS配合,在小范围内节约开发成本,提升用户体验。
下面简单过一下与表单验证相关的一些JavaScript属性和方法,共三种方法和三个属性。
三种方法分别是checkValidity()、reportValidity()和setCustomValidity()方法,三个属性是validity、validationMessage和willValidate属性。其中:
- checkValidity()方法可以用来验证当前表单控件元素,或者整个表单是否验证通过,返回值是布尔值,true或者false。
- reportValidity()方法可以触发浏览器的内置的验证提示交互,返回布尔值,true或者false。例如:

会触发下拉框的错误提示,如图所示。

- setCustomValidity()方法可以让开发者自定义出错的提示信息。
例如上面的reportValidity()方法的下拉出错提示是"请在列表中选择一项"。如果我们希望改成"请选择城市",则可以像下面这样:

此时的提示效果就会如图所示。

在属性部分,validity属性可以返回当前元素各种验证状态,例如:

在Chrome浏览器下返回的结果是一个ValidityState对象,包含的属性和属性值如下:

validationMessage属性表示当前输入框元素如果要显示出错提示,则出错提示的文字内容是什么。例如一个普通的输入框:

input.validationMessage的返回值是空字符串''。
如果输入框设置了required属性:

则input.validationMessage的返回值是"请填写此字段。"(注意:提示文案会随着浏览器版本升级而变化,仅供参考)。
如果输入框设置了pattern属性,同时输入框里面的值格式不符:

则input.validationMessage的返回值是"请与所请求的格式保持一致。"(注意:提示文案会随着浏览器版本升级而变化,仅供参考)。
willValidate属性表示元素能够被验证。输入框元素都是true,但如果输入框是disabled(禁用的),则该属性的返回值就是false。
从功能上讲,与表单验证相关的JavaScript属性和方法可谓非常强大,考虑到了表单验证的方方面面。可惜,浏览器内置的提示效果实在难登大雅之堂,加上这些API方法名称冗长,难以记忆,所以在圈子内并不受待见。当然,也不是没有解决方法,这些内置的属性和方法都是可以使用JavaScript代码重置的,分两步操作:
- (1)重置表单元素内置的验证行为,方法是给元素设置novalidate属性,这样,浏览器内置的验证提示框就不会出现了。
- (2)代理与验证相关的属性与方法(在不改变原生API的前提下调用自定义的提示效果),由于这块内容非本书重点,因此,不展示实现细节,仅示意实现原理。

