background

Element UI is a popular VUe. js UI framework on PC, and its component library can basically meet most common business requirements. But sometimes there are more customizable requirements that the component itself may not be able to fulfill. I ran into it recently on a project.

Many pages require the table component EL-Table. If the width is not specified for an el-table-column, by default the remaining columns are evenly allocated. In the case of a large number of columns, if the width of the el-table is limited to the container, the contents of the cell are wrapped. Force no line breaks, and content will either scroll through the cell or overflow or be truncated.

The product wants content to be displayed in a single row, column spacing to be consistent, and tables to scroll horizontally out of the container. El-table-column supports fixed widths, even if the content width is predictable. The problem is how to dynamically adapt the column width to the content width. There is no such option found in the official documentation, but the component itself does not support it.

Technical solution

So I came up with a scheme to dynamically calculate the content width. The idea is to calculate the width according to the number of characters in the content. There are several limitations to this approach:

  1. The content must be text
  2. Different character width is different, the settlement result is not accurate
  3. The need to manipulate data prior to rendering is not conducive to decoupling

I took a different approach, again calculating the content width dynamically, but based on the actual rendered DOM element width, which solved the above three problems.

So how do we do that? By looking at the rendered DOM elements,el-tableThe header and content of the table respectively use a nativetableThrough thecolgroupSets the width of each column. Let’s start with that,colthenameProperty value and corresponding totdtheclassThe values are consistent, so you can walk through all the cells of the corresponding column, find the cell with the widest width, and use its content width plus a margin as the width of the column.

The specific implementation

How do you calculate the content width? This is a critical step. Each rendered cell has a.cell class, using white-space: nowrap; overflow: auto; Display: inline-block; display: inline-block; To calculate the actual content width. Thus, the final width can be obtained from the scrollWidth property of the. Cell element.

function adjustColumnWidth(table) { const colgroup = table.querySelector("colgroup"); const colDefs = [...colgroup.querySelectorAll("col")]; colDefs.forEach((col) => { const clsName = col.getAttribute("name"); const cells = [ ...table.querySelectorAll(`td.${clsName}`), ...table.querySelectorAll(`th.${clsName}`), ]; // Ignore the "leave-alone" column if (cells[0]? .classList? .contains? .("leave-alone")) { return; } const widthList = cells.map((el) => { return el.querySelector(".cell")? .scrollWidth || 0; }); const max = Math.max(... widthList); const padding = 32; table.querySelectorAll(`col[name=${clsName}]`).forEach((el) => { el.setAttribute("width", max + padding); }); }); }Copy the code

The middle of the exploration process is tedious, but the final code implementation is very simple. When is column width calculation triggered? Naturally after the component is rendered. In order to facilitate reuse, I use the way of Vue custom instruction.

Vue.directive("fit-columns", {
  update() {},
  bind() {},
  inserted(el) {
    setTimeout(() => {
      adjustColumnWidth(el);
    }, 300);
  },
  componentUpdated(el) {
    el.classList.add("r-table");
    setTimeout(() => {
      adjustColumnWidth(el);
    }, 300);
  },
  unbind() {},
});
Copy the code

Further, I have packaged a Vue plugin called V-Fit-Columns, which has been published to the NPM repository and can be used directly by installation. Installation:

npm install v-fit-columns --save
Copy the code

Introduction:

import Vue from 'vue';
import Plugin from 'v-fit-columns';
Vue.use(Plugin);
Copy the code

Use:

<el-table v-fit-columns>
  <el-table-column label="No." type="index" class-name="leave-alone"></el-table-column>
  <el-table-column label="Name" prop="name"></el-table-column>
  <el-table-column label="Age" prop="age"></el-table-column>
</el-table>

Copy the code

The source repository is here: github.com/kaysonli/v-… , we welcome your comments and Star!

conclusion

This solution is a bit of a Hack, just fulfilling the requirements, and may be a bit flawed in other ways, such as a slight flash after rendering (reflow due to resetting the width). However, from the point of view of the effect of the final realization, still satisfactory, at least the product manager took his two meters of broadsword away… He is for this effect, squatted in front of my computer half the afternoon, insisted that I can not achieve! Under the coercion of hand touch hand, finally finished the job…

For the sake of the logo, don’t you care?