需要形容
有这样一个需要: 页面曾经写了太多的详情表单元素,并且每一个表单元素都应用了 Tooltip
组件来包裹,这样是不合乎需要的,因为用户须要限定当表单元素的文本太多时,也就是说呈现了省略号才会呈现 Tooltip
组件的包裹。比方我的项目采纳的 element UI 组件库。在一个页面有太多的这样的表单元素:
<el-form>
<el-form-item label="姓名" prop="name">
<el-tooltip :content="form.name">
<el-input v-model="form.name" disabled="true"></el-input>
</el-tooltip>
</el-form-item>
..... 后续呈现多个这样的元素
</el-form>
如果我每一个都加 disabled
属性,那么页面模板元素有将近一百个,很显然我这样加是很消耗工夫的,很显然对于谋求高效的我是不喜爱一个一个加,而后一个一个判断的。
在这之前,咱们须要确定一点,那就是咱们管制文本的截断是通过 CSS 代码来实现的。也就是如下这段代码:
.el-input {
text-overflow:ellipsis;
white-space:nowrap;
overflow:hidden;
}
因而,实现以上的需要的第一步就是须要先判断哪些元素满足被截断的条件。那么如何判断文本是否被截断呢?对于这个实现,我想 element ui
的表格组件合乎这种场景,所以我只须要去参考一下 element ui
的表格组件的截断判断实现就晓得了。没错,我在源码中找到了实现形式。
创立 range 元素
外围思路就是创立一个 range 元素,通过获取 range 元素 width 而后与元素的 offsetWidth 进行判断就行了。所以,我依照 element ui 的实现思路实现了这个工具函数。如下所示:
function isTextOverflow(element) {
// use range width instead of scrollWidth to determine whether the text is overflowing
// to address a potential FireFox bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1074543#c3
if (!element || !element.childNodes || !element.childNodes.length) {return false;}
const range = document.createRange();
range.setStart(element, 0);
range.setEnd(element, element.childNodes.length);
const rangeWidth = range.getBoundingClientRect().width;
// if the element has padding style,should add the padding value.
const padding = (parseInt(getStyle(element, 'paddingLeft'), 10) || 0) + (parseInt(getStyle(element, 'paddingRight'), 10) || 0);
return rangeWidth + padding > element.offsetWidth || element.scrollWidth > element.offsetWidth;
}
function hasClass(el, cls) {if (!el || !cls) {return false;}
if (cls.indexOf(" ") > -1) {return console.error(`className should not contain space!`);
}
if (el.classList) {return el.classList.contains(cls);
} else {return ("" + el.className +" ").indexOf(" "+ cls +" ") > -1;
}
}
function camelCase(name) {return name.replace(/([\:\_\-]+(.))/g, (_, separator, letter, offset) => offset ? letter.toUpperCase() : letter).replace(/^moz([A-Z])/, "Moz$1")
}
// IE version more than 9
function getStyle(el, styleName) {if (!element || !styleName) return null;
styleName = camelCase(styleName);
if (styleName === 'float') {styleName = 'cssFloat';}
try {var computed = document.defaultView.getComputedStyle(element, '');
return element.style[styleName] || computed ? computed[styleName] : null;
} catch (e) {return element.style[styleName];
}
}
收集所有的 tooltip 组件实例
这只是实现了第一步,接下来是第二步,就是我须要将页面上所有蕴含 tooltip 组件的 vue 组件实例都要收集起来。首先咱们能够确定页面所有的 toolTip 组件实例应该都是以后这个表单组件实例的所有子组件,因而,我想到了递归去收集所有蕴含 tooltip 组件的组件实例。代码如下:
export function findToolTip(children,tooltips = []){// 这里写代码}
所有的 tooltip 组件实例都是 vue 组件实例的子组件,所以咱们能够晓得咱们去循环组件实例的子组件,即 vm.$children
属性。而后 tooltip 组件有什么标记呢?或者说咱们如何判断该子组件实例就是一个 tooltip 组件呢?我又参考了 element ui tooltip
组件的实现,发现 element ui tooltip
组件都会有一个 doDestory
办法。所以咱们就能够依据这个来做判断。所以递归办法,咱们就能够写好了。如下:
// 参数 1:子组件实例数组
// 参数 2:收集组件实例的数组
export function findToolTip(children,tooltips = []){for(let i = 0,len = children.length;i < len;i++){
// 递归条件
if(Array.isArray(children[i]) && children[i].length){findToolTip(children[i],tooltips);
}else{
// 判断如果 doDestroy 属性是一个办法,则代表是一个 tooltip 组件,增加到数组中
if(typeof children[i].doDestroy === "function"){tooltips.push(children[i]);
}
}
}
// 把收集到的组件实例返回
return tooltips;
}
// 调用形式,传入以后组件实例 this 对象的所有子组件实例
// 如:findToolTip(this.$children)
咱们找到所有 tooltip 组件实例了,接下来,咱们要遍历它,而后判断是否合乎截断条件,不合乎咱们就须要将组件给销毁,事实上,咱们间接设置 tooltip 组件实例的 disabled 属性为 true 就能够了。接下来,就是这个工具函数的实现了。如下:
// 参数 1: 以后组件实例 this 对象
// 参数 2: 用于获取被 tooltip 包裹的子元素的类名
export function isShowToolTip(vm,className="el-input"){// 这里写代码}
如何批改 tooltip 组件?
接下来,咱们就要实现这个工具函数。首先,咱们须要确定的是,因为 disabled 是 props 数据,props 是单向数据流,vue.js 是不倡议咱们间接批改的,那么咱们应该如何做到设置 disabled 呢?我想了很久,想的方法就是从新渲染整个组件,利用 Vue.compie API
也就是编译 API 间接从新编译整个 tooltip 组件,而后替换本来渲染的 tooltip 组件。如下:
const res = Vue.compile(`<el-tooltip disabled><el-input value="${ child && child.innerText}" disabled></el-input></el-tooltip>`);
const reRender = new Vue({
render:res.render,
staticRenderFns:res.staticRenderFns
}).$mount(parent);//parent 为获取的 tooltip 组件实例根元素
所以,接下来在工具函数里,咱们能够这样实现:
// 参数 1: 以后组件实例 this 对象
// 参数 2: 用于获取被 tooltip 包裹的子元素的类名
export function isShowToolTip(vm,className="el-input"){
// 为了确保可能获取到 DOM 元素,须要调用一次 nextTick 办法
vm.$nextTick(() => {const allTooltips = findToolTip(vm.$children);
allTooltips.forEach(item => {
// 获取子元素
const child = item.$el.querySelector("." + className);
// 判断是否被截断
if(!isTextOverflow(child)){
// 获取渲染元素
const parent = item.$el;
const res = Vue.compile(`<el-tooltip disabled><el-input value="${ child && child.innerText}" disabled></el-input></el-tooltip>`);
const reRender = new Vue({
render:res.render,
staticRenderFns:res.staticRenderFns
}).$mount(parent);
}
})
}};
}
如何应用这个工具函数?
如此一来,就做到了一个办法实现了以上的需要。如何应用这个办法呢,很简略,咱们能够在详情组件外面监听表单详情数据的变动。如:
watch:{form(val){if(typeof val === "object" && val && Object.keys(val).length){
// 将 this 当做参数传入即可
isShowToolTip(this);
}
}
}
对于这个需要更好的实现,如有更好的办法,望不吝赐教。