Requirements describe

There is a requirement that the page already has too many detailed form elements, and that each form element is wrapped in a Tooltip component, which is not a requirement because the user needs to limit the Tooltip component to be wrapped when the form element has too much text, i.e. ellipses. For example, the Element UI component library used by the project. There are too many form elements in one page:

<el-form>
   <el-form-item label="Name" prop="name">
       <el-tooltip :content="form.name">
           <el-input v-model="form.name" disabled="true"></el-input>
       </el-tooltip>
   </el-form-item>. Multiple such elements follow</el-form>
Copy the code

If I added the disabled attribute to each of them, there would be nearly a hundred page template elements, which would obviously be time consuming, and obviously I don’t like to add them one by one and judge them one by one.

Before we do that, we need to make sure that we control text truncation using CSS code. Here’s the code:

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

Therefore, the first step in fulfilling the above requirements is to determine which elements meet the criteria for truncation. So how do you tell if the text is truncated? For this implementation, I think element UI’s table component fits this scenario, so I just need to look at the truncation judgment implementation of Element UI’s table component. Yes, I found the implementation in the source code.

Creating a Range element

The idea is to create a range element by getting the width of the range element and checking with the offsetWidth of the element. So, I implemented the tool function along the lines 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) {
        returnelement.style[styleName]; }}Copy the code

Collect all tooltip component instances

This is just the first step, followed by the second, which is that I need to collect all the VUE component instances on the page that contain the Tooltip component. The first step was to determine that all toolTip component instances on the page should be all child components of the current form component instance, so I thought of recursively collecting all the component instances that contain the toolTip component. The code is as follows:

export function findToolTip(children,tooltips = []){
    // Write the code here
}
Copy the code

All tooltip component instances are children of vue component instances, so we know that we are going to loop through the child component of the component instance, the vm.$children property. Then what is the logo of the Tooltip component? Or how do we determine that the child component instance is a tooltip component? I looked at the implementation of the Element UI Tooltip component and found that every element UI Tooltip component has a doDestory method. So we can make a judgment based on that. So the recursive method, we can write it. As follows:

// Argument 1: array of child component instances
// Parameter 2: Collects an array of component instances
export function findToolTip(children,tooltips = []){
    for(let i = 0,len = children.length; i < len; i++){// recursive condition
        if(Array.isArray(children[i]) && children[i].length){
            findToolTip(children[i],tooltips);
        }else{
            // Determine if the doDestroy attribute is a method, then it represents a tooltip component to add to the array
            if(typeof children[i].doDestroy === "function"){ tooltips.push(children[i]); }}}// Returns an array of child component instances
    return tooltips;
}
// All child component instances of the current component instance this object are passed in
/ / such as: findToolTip (enclosing $children)
Copy the code

Now that we’ve found all the tooltip component instances, we’re going to iterate over them and determine if they fit the truncation criteria. If they don’t, we’re going to have to destroy the component. In fact, we’re going to set the Tooltip component instance to True as disabled. Next, it is the implementation of the utility function. As follows:

// Parameter 1: the current component instance this object
// Argument 2: Gets the class name of the child element wrapped in tooltip
export function isShowToolTip(vm,className="el-input"){
    // Write the code here
}
Copy the code

How do I modify the Tooltip component?

Next, we implement the utility function. First, we need to make sure that since disabled is the props data and the props isa one-way data stream, vue.js is not recommended to be modified directly, so how do we set disabled? The solution I came up with was to rerender the entire tooltip component using the Vue.com Pile API, which is the compile API, and then replace the tooltip component that was originally rendered. 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 is the root element of the obtained Tooltip component instance
Copy the code

So, next in the utility function, we can implement it like this:

// Parameter 1: the current component instance this object
// Argument 2: Gets the class name of the child element wrapped in tooltip
export function isShowToolTip(vm,className="el-input"){
    To ensure that the DOM element is retrieved, the nextTick method needs to be called once
    vm.$nextTick(() = > {
        const allTooltips = findToolTip(vm.$children);
        allTooltips.forEach(item= > {
            // Get the child element
            const child = item.$el.querySelector("." + className);
            // Determine whether truncation occurs
            if(! isTextOverflow(child)){// Get render elements
                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); }}}}; }Copy the code

How do I use this utility function?

In this way, there is one way to fulfill the above requirements. How to use this method is very simple. We can listen for changes in the form details data in the details component. Such as:

watch:{
    form(val){
        if(typeof val === "object" && val && Object.keys(val).length){
            // Pass this as an argument
            isShowToolTip(this); }}}Copy the code

As for the better realization of this demand, if there is a better way, please kindly advise.