背景
最近几个项目都有流程式的表单输入。产品提出,在上一步的表单提交完毕之后,不能够修改但是依然要展示给用户(用户在填写下一步的表单时可能需要参照上一步表单中的内容)。
最初因为项目工期紧张,直接采用了对表单容器添加pointer-events: none;的作法。这样导致了很多问题,表单元素的样式并没有变为disabled状态,看上去还是可以点击的;使用Tab键索引到元素之后,还是可以触发表单行为;容器内的元素的滚动条失效。
方案
解决这个问题有一个很直接的思路,就是遍历所有的表单元素,添加disabled属性。但是项目中的表单元素很多,一个个添加,非常麻烦。有没有简单一点的办法呢?
利用fieldset元素
无意中看到张鑫旭老师个人空间中的一篇文章如何disabled禁用所有表单input输入框元素。阅读之后发现可以给form表单下添加一个fieldset元素,通过给这个fieldset元素设置disabled属性来统一禁用所有的表单元素。
表单标题
复制代码
自定义指令
因为项目使用的是vue框架,对于添加fieldset元素这种dom操作,我第一时间想到了自定义指令。
在 Vue2.0 中,代码复用和抽象的主要形式是组件。然而,有的情况下,你仍然需要对普通 DOM 元素进行底层操作,这时候就会用到自定义指令
自定义指令在平时也用过一些,例如常见的transfer-dom,clickoutside。借此机会也来自己实践一下。
因为我们的需求是要在form元素下插入fieldset,因此只要使用inserted钩子即可(保证form已经插入到dom当中),类似官网文档给出的autofocus的例子。
export default {
inserted(el, {
value
}) {
if (el.tagName.toLocaleLowerCase() === "form") {
wrapInner(el, value)
}
Array.from(el.querySelectorAll("form")).forEach(child => {
wrapInner(child, value)
})
}
};
复制代码
inserted钩子的第一个参数代表被绑定的元素。遍历这个元素的所有子代元素,找到form元素,执行包裹逻辑。需要注意的是,有可能这个元素本身就是form,所以增加一个判断。从第二个参数中解构出value属性(指令的绑定值,例如:v-disabled="true" 中,绑定值为 true)。
相信大家也比较少用原生api操作dom,这里详细说下如何完成对form内元素包裹fieldset标签。
jquery当中恰好有一个比较冷门的api:wrapInner可以满足我们的需求,来看下jquery的实现,核心逻辑如下
// The elements to wrap the target around
// 创建一个包裹元素
wrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true );
// 插入到目标元素的第一个子元素之前
if ( this[ 0 ].parentNode ) {
wrap.insertBefore( this[ 0 ] );
}
wrap.map( function() {
var elem = this;
while ( elem.firstElementChild ) {
elem = elem.firstElementChild;
}
return elem;
} ).append( this );
复制代码
参考jquery的实现思路,代码如下
function wrapInner(el, disabled) {
const wrapper = document.createElement('fieldset');
wrapper.disabled = disabled
//先插入一个空的fieldset标签到第一个元素之前
el.insertBefore(wrapper, el.firstElementChild);
const children = el.children,
len = children.length;
//把原来的每个子元素移入到fieldset内
//不是children[i],insertBefore是同步操作
//每次插入的都是第一个元素
for (let i = 1; i < len; i++) {
wrapper.insertBefore(children[1], null);
}
}
复制代码
总结
这样的方案要求表单元素被嵌套在form内。这里也可以看出HTML语义化的好处,除了代码可读性好,SEO有帮助,无障碍优化外,还能充分发挥出标签本身的特性。
如果项目使用一些第三方框架,例如element-ui,使用封装过的form组件(自带disabled属性)会更好一些。
代码传送门