2
头图

Description of Requirement

There is such a requirement: the page has written too many detailed form elements, and each form element is Tooltip component, which does not meet the requirements, because the user needs to limit when the text of the form element is too much, that is It is said that the ellipsis will appear in the package of Tooltip For example, the element UI component library used by the project. There are too many such form elements on one page:

<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>

If I add the disabled attribute to each of them, there will be nearly a hundred page template elements. Obviously, it is very time-consuming for me to add this way. Obviously, I don't like to add them one by one and judge them one by one for the pursuit of high efficiency.

Before that, we need to make sure that we control the truncation of the text through CSS code. That is, the following code:

.el-input {
    text-overflow:ellipsis;
    white-space:nowrap;
    overflow:hidden;
}

Therefore, the first step to complete the above requirements is to determine which elements meet the condition of being truncated. So how to judge whether the text is truncated? Regarding this implementation, I think element ui fits this scenario, so I only need to refer to the truncation judgment implementation of the table component of element ui Yes, I found the implementation source code

Create the range element

The core idea is to create a range element, by obtaining the width of the range element and then judging it with the offsetWidth of the element. So, I completed this tool function in accordance with the implementation of element ui. As follows:

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];
    }
}

Collect all tooltip component instances

This is just the first step, and the next step is the second step, that is, I need to collect all the vue component instances that contain tooltip components on the page. First of all, we can determine that all toolTip component instances on the page should be all subcomponents of the current form component instance. Therefore, I thought of recursively collecting all component instances that contain tooltip components. code show as below:

export function findToolTip(children,tooltips = []){
    //这里写代码
}

All tooltip component instances are subcomponents of vue component instances, so we can know that we are going to loop the subcomponents of component instances, that is, the vm.$children attribute. Then what is the mark of the tooltip component? Or how do we judge that the subcomponent instance is a tooltip component? I also referred to element ui tooltip component and found that the element ui tooltip component will have a doDestory method. So we can make judgments based on this. So the recursive method, we can write it. as follows:

//参数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)

We have found all tooltip component instances. Next, we need to traverse it and determine whether it meets the truncation condition. If it does not meet the truncation condition, we need to destroy the component. In fact, we can directly set the disabled attribute of the tooltip component instance to true. . Next, is the realization of this utility function. as follows:

// 参数1:当前组件实例this对象
// 参数2:用于获取被tooltip包裹的子元素的类名
export function isShowToolTip(vm,className="el-input"){
    //这里写代码
}

How to modify the tooltip component?

Next, we will implement this tool function. First of all, we need to make sure that because disabled is props data and props is a one-way data flow, vue.js does not recommend us to modify it directly, so how should we set disabled? I thought for a long time, and the solution I thought was to re-render the entire component, using Vue.compie API which is the compilation API, to directly recompile the entire tooltip component, and then replace the original rendered tooltip component. as follows:

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组件实例根元素

So, next in the utility function, we can achieve this:

// 参数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);
            }
        })
    }};
}

How to use this utility function?

In this way, one method has been achieved to fulfill the above requirements. How to use this method is very simple. We can monitor the changes of the form detail data in the detail component. like:

watch:{
    form(val){
        if(typeof val === "object" && val && Object.keys(val).length){
            //将this当做参数传入即可
            isShowToolTip(this);
        }
    }
}

Regarding the better realization of this demand, if there is a better way, please feel free to enlighten me.


夕水
5.2k 声望5.7k 粉丝

问之以是非而观其志,穷之以辞辩而观其变,资之以计谋而观其识,告知以祸难而观其勇,醉之以酒而观其性,临之以利而观其廉,期之以事而观其信。