前端开发的同学们应该对 Element UI
不陌生,而日常开发中,多少会存在一些组件无法完全满足我们需求的情况,或者说想在原组件的基础上附加一些功能,当遇到需要附加一些不是特别复杂的小东西的时候,采用 Vue 的自定义指令来实现,将会显得十分优雅
前景
我们知道 Element UI
的 el-input
组件 type
为 text
获取 textarea
时,可通过设置 show-word-limit
属性来展示字数统计。但是我们当前项目使用的 Element UI
版本为 2.5.21
, 是没有该属性的,而我们暂时又不方便升级 Element UI
版本,而产品经理坚持要求这个小东西,那么看看如何解决这个问题
实际上这个小功能十分简单,如下
<template>
<div class="el-input-wrap">
<el-input
type="textarea"
:autosize="{ minRows: 4, maxRows: 4 }"
v-model="desc"
maxlength="30">
</el-input>
<span class="el-input-count">{{ desc.length }}/30</span>
</div>
</template>
<script type="text/ecmascript-6">
export default {
data() {
return {
desc: ''
}
}
}
</script>
<style>
.el-input-wrap {
position: relative;
}
.el-input-count {
position: absolute;
line-height: 1.5;
color: #909399;
background: #fff;
font-size: 12px;
bottom: 5px;
right: 10px;
}
</style>
通过手动的方式添加父元素设置定位属性,再添加 span
元素设置定位和样式,来展示动态的长度限制效果,这么做就可以实现和新版本的 show-word-limit
一模一样的效果,然而始终有些许麻烦,每次用到的地方都要这么来一遍,且不太好抽出来封装组件,那么是不是可以考虑一下使用自定义指令来做呢?
思路
最终效果期望是通过一个指令来实现上面的效果,如
<el-input
v-limit
type="textarea"
:autosize="{ minRows: 4, maxRows: 4 }"
v-model="desc"
maxlength="30">
</el-input>
和之前对比少了些什么?我们需要做些什么?
1,创建一个父元素 设置定位属性,这一步是展示文本是否定位在输入框的右下角的关键
2,创建一个 span
元素 设置样式,用于展示效果
3,将 el-input
绑定的值的长度填到 span
元素中
4,每次 el-input
绑定的值的长度改变时,更新 span
元素的值
思路有了,并不是很复杂,那么接下来我们看看,如何来编写这个代码
实战代码
首先创建一个js文件 showWordLimit.js
, 然后 export
一个可以注册指令的对象,然后在钩子函数 inserted
时,我们来实现第一步,创建一个父元素,并设置定位属性
export default{
inserted: function () {
let wrap = document.createElement('div')
wrap.style.position = 'relative'
}
}
既然是父元素,我们还需要将使用了该指令的 el-input
元素插入到这个父元素内,通过钩子的参数来拿到 dom
元素
export default{
inserted: function (el) {
let wrap = document.createElement('div')
wrap.style.position = 'relative'
el.parentNode.replaceChild(wrap, el)
wrap.appendChild(el)
}
}
这样下来,第一步就算完成了,下面继续创建一个 span
元素,并设置好样式,同时还要插入到刚刚创建的父元素内
export default{
inserted: function (el) {
let wrap = document.createElement('div')
wrap.style.position = 'relative'
el.parentNode.replaceChild(wrap, el)
wrap.appendChild(el)
let oSpan = document.createElement('span')
oSpan.style.lineHeight = 1.5
oSpan.style.color = '#909399'
oSpan.style.position = 'absolute'
oSpan.style.background = '#FFF'
oSpan.style.fontSize = '12px'
oSpan.style.bottom = '5px'
oSpan.style.right = '10px'
oSpan.innerText = '0/30'
wrap.appendChild(oSpan)
}
}
注意到 innerText
的值,应该是个变量,根据 el-input
的 value
值长度来决定的,这里就需要用到动态指令的做法,在使用指令的时候,将这个值传过来,例如
<el-input
v-limit="desc.length"
type="textarea"
:autosize="{ minRows: 4, maxRows: 4 }"
v-model="desc"
maxlength="30">
</el-input>
回到 showWordLimit.js
,怎么接收这个值呢?同样也是通过钩子的参数 binding
能拿到,不清楚的可以参考Vue官方文档,自定义指令这一块,写得很清楚,拿到了之后,替换掉 span
元素的值
export default{
inserted: function (el, binding) {
let wrap = document.createElement('div')
wrap.style.position = 'relative'
el.parentNode.replaceChild(wrap, el)
wrap.appendChild(el)
let oSpan = document.createElement('span')
oSpan.style.lineHeight = 1.5
oSpan.style.color = '#909399'
oSpan.style.position = 'absolute'
oSpan.style.background = '#FFF'
oSpan.style.fontSize = '12px'
oSpan.style.bottom = '5px'
oSpan.style.right = '10px'
// binding.value 等于使用该指令的 el-input 组件的值的长度, 此处使用的ES6字符串模板拼接
oSpan.innerText = `${binding.value}/30`
wrap.appendChild(oSpan)
}
}
OK,到目前为止,姑且可以试用一下了,在 man.js
导入并注册该指令
// main.js 如果没有使用 vue-cli 脚手架的同学,需要找到对应的入口文件导入注册
import showWordLimit from '@/directive/showWordLimit'
Vue.directive('limit', showWordLimit)
<!-- 在需要用到的页面中 使用指令 -->
<el-input
v-limit="desc.length"
type="textarea"
:autosize="{ minRows: 4, maxRows: 4 }"
v-model="desc"
maxlength="30">
</el-input>
这个时候会发现,展示效果的数值,是不会随着输入框的内容长度变换而更新的,虽然是做了传递文本框内容长度的处理,但是在 inserted
内,它只会执行一次,如果文本框默认值的长度就有10,那么它显示的就将是10/30,然后我们编辑了内容,长度改变了,它依然还是10/30,如果想要每次编辑内容都更新这个展示效果的数值,那么久要用到另外一个钩子函数 update
export default{
inserted: function (el, binding) {
let wrap = document.createElement('div')
wrap.style.position = 'relative'
el.parentNode.replaceChild(wrap, el)
wrap.appendChild(el)
let oSpan = document.createElement('span')
oSpan.style.lineHeight = 1.5
oSpan.style.color = '#909399'
oSpan.style.position = 'absolute'
oSpan.style.background = '#FFF'
oSpan.style.fontSize = '12px'
oSpan.style.bottom = '5px'
oSpan.style.right = '10px'
// binding.value 等于使用该指令的 el-input 组件的值的长度, 此处使用的ES6字符串模板拼接
oSpan.innerText = `${binding.value}/30`
// 为了在 update 生命周期函数内方便获取到它,添加个 id
oSpan.setAttribute('id', 'oSpan')
wrap.appendChild(oSpan)
},
update: function (el, binding) {
let oSpan = document.getElementById('oSpan')
oSpan.innerText = `${binding.value}/30`
}
}
现在展示效果的数值应该已经可以实现实时更新了,细心的同学应该会发现,有个地方有遗漏,那就是展示效果的最大字符限制的数字,目前是固定死的30,那如果想限制字符为其他数值,就不行了,那要解决这个问题也非常简单,将传递过来的值,改为数组的形式即可
<el-input
v-limit="[desc.length, 50]"
type="textarea"
:autosize="{ minRows: 4, maxRows: 4 }"
v-model="desc"
maxlength="30">
</el-input>
export default{
inserted: function (el, binding) {
let wrap = document.createElement('div')
wrap.style.position = 'relative'
el.parentNode.replaceChild(wrap, el)
wrap.appendChild(el)
let oSpan = document.createElement('span')
oSpan.style.lineHeight = 1.5
oSpan.style.color = '#909399'
oSpan.style.position = 'absolute'
oSpan.style.background = '#FFF'
oSpan.style.fontSize = '12px'
oSpan.style.bottom = '5px'
oSpan.style.right = '10px'
// binding.value 等于使用该指令的 el-input 组件的值的长度, 此处使用的ES6字符串模板拼接
oSpan.innerText = `${binding.value[0]}/${binding.value[1]}`
// 为了在 update 生命周期函数内方便获取到它,添加个 id
oSpan.setAttribute('id', 'oSpan')
wrap.appendChild(oSpan)
},
update: function (el, binding) {
let oSpan = document.getElementById('oSpan')
oSpan.innerText = `${binding.value[0]}/${binding.value[1]}`
}
}
至此,这个自定义指令就算完全做好了,当然难度不大,这篇文章的目的主要是希望可以给一些还没有接触过自定义指令应用的同学做个引导,讲述一个完整的应用流程和思路,希望能帮助到一些人,也留于自己笔记
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。