Personally, I feel that writing Vue is annoying because it is tedious to write template code, such as Table component, which has to write as many columns as there are. With JS code, you can be flexible. Fortunately, Vue supports rendering functions, which means you can use JS code to implement components.

Let’s first clarify our packaging goals

  1. For normal display-only columns, specify prop and label.
  2. Retain the slot capability of the original Column, allowing users to customize the display Column.
  3. Optional controls whether a column is displayed.

Use and effect

Let’s see what we can finally do with this repackaged component

Simple data presentation

If it’s purely data, just write it like this

<EluiDynTable :desc="tableDesc" :data="tableData" />
Copy the code

Then you can give it the columns and data you need, which is very convenient

export default {
  name: "app".components: {
    EluiDynTable,
  },
  data() {
    return {
      tableDesc: [{prop: "name".label: "Name" },
        { prop: "city".label: "City" },
        { prop: "born".label: "Time of birth".formatter: "ts"},].tableData: [{name: "Alice".city: "Shanghai".born: 946656000000}, {name: "Bob".city: "Hongkong".born: 946699994000,}]}; }};Copy the code

Results the following

The custom column

You can do this if you want to customize the column, such as adding a function action button in the right-most test

<EluiDynTable :desc="tableDesc" :data="tableData">
  <EluiDynColumn prop="operation">
    <span slot="header">The custom</span>
    <div slot-scope="{ row, $index }">
      <el-button type="danger" size="mini" @click="handleClick(row, $index)"
        >Delete < / el - button ></div>
  </EluiDynColumn>
</EluiDynTable>
Copy the code
export default {
  name: "app".components: {
    EluiDynTable,
    EluiDynColumn,
  },
  data() {
    return {
      tableDesc: [{prop: "name".label: "Name" },
        { prop: "city".label: "City" },
        { prop: "born".label: "Time of birth".formatter: "ts" },
        { prop: "operation".label: "Operation".fixed: "right"},].tableData: [{name: "Alice".city: "Shanghai".born: 946656000000}, {name: "Bob".city: "Hongkong".born: 946699994000,}]}; },methods: {
    handleClick(row, index) {
      /* eslint-disable no-console */
      console.log(`deleting ${row.name} at ${index}`); ,}}};Copy the code

rendering

Control column display

Call the component’s toggle function, signed as follows

toggle(prop, hidden)
Copy the code

Prop is used to specify which column. If hidden value (Boolean) is not specified, it toggles back and forth between showing and not showing, and if specified, the specified value is used.

implementation

We will first prepare some common formatters for various data types. I have some built in the box.

function formatNumber(prop) {}function formatArray(array, extra) {}function formatTimestamp(ts) {}function formatSecond(second) {}function formatterByType(prop) {}export function format({ formatter, prop, scope, extra }) {}Copy the code

If the built-in formatter does not meet the requirements, it can also be customized. Pass custom to formatter and specify a Formatter in extra, which should be a function type

extra.formatter(prop, scope)
Copy the code

We then simply wrap the ElColumn, with the default slot calling the formatter to format the data by default, and the header slot displaying the label value. Make sure that the original slot is exposed again so that it can be customized when used.

<template>
  <el-table-column v-bind="$attrs">
    <template slot="header" slot-scope="h">
      <slot name="header" v-bind="h">{{ h.column.label }}</slot>
    </template>
    <template slot-scope="scope">
      <slot v-bind="scope">
        <span>{{ formatRow(scope.row[prop], scope) }}</span>
      </slot>
    </template>
  </el-table-column>
</template>
Copy the code
<script>
import { format } from ".. /utils/format";

export default {
  name: "EluiDynColumn".props: ["prop"."formatter"."extra"].methods: {
    formatRow(prop, scope) {
      return format({
        formatter: this.formatter,
        prop,
        scope,
        extra: this.extra, }); ,}}}; </script>Copy the code

Finally, use the rendering function to achieve a Table component

const EluiDynTable = {
  name: "EluiDynTable".props: {
    data: {
      type: Array.default: () = >[],},desc: {
      type: Array.default: () = >[],}},methods: {
    toggle(prop, hidden) {
      const d = this.desc.find((e) = > e.prop === prop);
      if (d) {
        if(hidden ! = =undefined) { d.hidden = !! hidden; }else{ d.hidden = ! d.hidden; }this.$forceUpdate(); }}},render: function (h) {
    const isDynColumn = (c) = >
      c.componentOptions.Ctor.extendOptions.name === "EluiDynColumn";
    const dynColumns = (this.$slots.default || []).filter(isDynColumn);
    const keyOf = (c) = > c.componentOptions.propsData.prop;
    const columnGroups = group(dynColumns, keyOf);

    const children = [];
    for (let d of this.desc) {
      if (d.hidden) {
        continue;
      }
      let child = columnGroups[d.prop];
      if (child) {
        const propKeys = Object.keys(child.componentOptions.Ctor.options.props);
        for (let k in d) {
          if (propKeys.indexOf(k) >= 0) {
            child.componentOptions.propsData[k] = d[k];
          } else{ child.data.attrs[k] = d[k]; }}}else {
        const propKeys = Object.keys(EluiDynColumn.props);
        const props = {};
        const attrs = {};
        for (let k in d) {
          if (propKeys.indexOf(k) >= 0) {
            props[k] = d[k];
          } else {
            attrs[k] = d[k];
          }
        }
        child = h(EluiDynColumn, {
          props,
          attrs,
        });
      }
      children.push(child);
    }
    return h(Table, { props: this.$props, attrs: this.$attrs }, children); }};Copy the code

As you can see in the code details, I will only filter out the EluiDynColumn component in the default slot as a custom column, and then iterate through the table description desc. If the user has a custom component, the custom component will be used, otherwise the EluiDynColumn component will be automatically generated. Assign props and attrs to EluiDynColumn, because EluiDynColumn will bind attrs to ElColumn through V-bind, so it perfectly retains the features of the original ElColumn. Another detail is the Hidden property, which controls the display of columns. The render function returns an ElTable VNode at the end.

Front end small white, if any questions please kindly point out 🙂

See the full code here