Not long ago for the company’s background system, the development of a set of background system component library. For the table component of the implementation of ideas and details to share. If you have other thoughts and ideas, please leave them in the comments section.

Components are based on Element UI

The component passes properties and event methods

Before we start writing the component, consider that the el-Table component can pass many properties and event methods. It is impossible to list them all in the component. What is the solution to this problem? This leads to two attributes in vUE, $attrs and $Listeners, which are useful for encapsulating high-level components. Let’s take a look at these two properties:

  • $Listeners: Contains V-on event listeners in the parent scope (without.native modifiers) that can be downloaded

    V-on =”$listeners” are passed to internal components

  • $attrs: contains attribute bindings (except class and style) that are not recognized and retrieved as prop in the parent scope, and can be passed to internal components via v-bind=”$attrs”. Often used in conjunction with inheritAttrs.

    The inheritAttrs attribute defaults to true, and the attributes in $attrs are rolled back to the root element of the child component as normal attributes.

    / / the parent component
    <son name="Zhang" :age="18" say="Hello World"></son>
    
    / / child component
    export default {
      props: {
        name: String,
      },
      created () {
        console.log(this.$attrs)
      }
    }
    
    Copy the code

    When inheritAttrs is set to false, the age and say attributes are not bound on the root element.

Component implementation

Column Specifies the attributes to contain

Because the el-Table is wrapped, the header column needs to be passed inside the component as array columns for traversal.

The attributes contained in column can be broken down into raw and custom attributes:

  • Raw attributes are attributes that el-table-column contains, such as prop, label, width, and so on. These primitive attributes can be placed in an object named attrs.

    attrs: {
      prop: ' '.label: ' '.width: ' '.'render-header': function(){}}Copy the code
  • Custom attributes include:

    • The header column can be set to custom display content by slot, so set a slot: ‘slotName’ property
    • The header column may have children, so set a children:[] property
    • Nested children also contain custom display content set by slot, so set slotChildren: [‘slotName’, ‘slotName’] (ps: Explains why later)
    • Customizing header content with slot=’header’ requires setting a slotHeader=true property

    So the header column contains the following attributes:

    columns: [
      {
        attrs: {},
        slot: slotName,
        children: [].slotChildren: [slotName],
        slotHeader: true}]Copy the code

Render enter the render function

Because column has subitems, the template template cannot be used to render subitems, while the render function can give full play to the programming ability of JavaScript and can well replace the template form. If you are not familiar with the render function, please refer to the official documentation for a tutorial.

Without further ado, go directly to the code:

const RenderTableColumn = {
  props: {
    column: {
      type: Object,}},render(createElement) {
    // The second argument to createElement is the data object corresponding to the attribute in the template, which contains a scopedSlots attribute
    The createScopedSlots method generates the value of this attribute
    const createScopedSlots = (slot, slotHeader) = > {
      let scopedSlots = {}
      // Custom headers exist
      // On the elder-UI el-table-column component, you can set a slot named header
      // So the corresponding data object property name should be header
      if (slotHeader) {
        Object.assign(scopedSlots, {
          // $scopedslots. header is the v-slot:header for rendering
          header: scope= > this.$scopedSlots.header(scope)
        })
      }
      // Custom content slot exists
      // On the elder-UI el-table-column component, you can set an unnamed slot
      // The name of the unnamed slot is default, so the internal property name is default
      if (slot) {
        Object.assign(scopedSlots, {
          default: scope= > {
            // Because more than one named slot may be passed in when a component is called, dynamic property names are used
            if (this.$scopedSlots[slot]) {
              return this.$scopedSlots[slot](scope)
            }
          }
        })
      }
      return { scopedSlots }
    }
    // The method to generate the final column component
    const renderColumn = column= > {
      // Get attributes
      const { attrs, slot, slotHeader, children } = column
      // In the second object argument of the createElement function, both props and attrs can be tested
      let props = { props: { ...attrs } } // attrs: {... attrs}
      // Get the custom slot
      Object.assign(props, createScopedSlots(slot, slotHeader))
      // Recursively iterate over nested table headers
      const hasChildren = Array.isArray(children) && children.length
      // Nodes is an array of child virtual nodes
      const nodes = hasChildren ? children.map(col= > {
        return renderColumn(col)
      }) : []
      
      return createElement(TableColumn, props, nodes)
     }
     return renderColumn(this.column)
   }
}
Copy the code

The implementation of the CustomTable component

Start with the CustomTable component code (you can add your own functionality if you want to use it) :

<template>
  <div class="CustomTable">
    <el-table v-bind="$attrs" v-on="$listeners" ref="CustomTable">
      <template v-for="(column, index) in columns">
        <render-table-column
        v-if="column.slotHeader || column.slot || column.slotChildren"
        :key="column.attrs.prop"
        :column="column">
        <! -- Custom header -->
          <template
          v-if="column.slotHeader"
          v-slot:header="scope">
            <slot :name="column.attrs.prop + 'Header'" :scope="{... scope, index}" />
          </template>
          <! -- Set slot properties -->
          <template
          v-if="column.slot"
          v-slot:[column.slot] ="scope">
            <slot :name="column.slot" :scope="{... scope, index}">
              <! -- Default value when slot is not passed -->
              {{scope.row[column.attrs.prop]}}
            </slot>
          </template>
          <! -- Child table header has custom slot -->
          <template
          v-if="column.slotChildren && column.slotChildren.length"
          v-for="name in column.slotChildren"
          v-slot:[name] ="scope">
            <slot :name="name" :scope="{... scope, index}"></slot>
          </template>
        </render-table-column>
        <render-table-column
        v-else
        :column="column"
        :key="column.attrs.prop">
        </render-table-column>
      </template>
    </el-table>
  </div>
</template>

<script>
import { Table } from 'element-ui'

export default {
  name: 'CustomTable'.components: {
    ElTable: Table,
    RenderTableColumn
  },
  inheritAttrs: false.props: {
    // The incoming attribute is the same as the el-table-column component attribute
    columns: {
      type: Array.required: true.default: () = > {
        return[]}},tableRef: {
      type: Object
    },
  },
  mounted () {
    this.$emit('update:tableRef'.this.$refs.CustomTable)
  }
}
</script>

Copy the code

TableRef =”tableRef”; tableRef =”tableRef”; tableRef =”tableRef”;

Components can be used as follows:

<template> 
  <custom-table
  :data="tableData"
  :columns="columns"
  :tableRef.sync="tableRef">
    <template v-slot:name="{scope}">
      <span style="color:#ff8f0a">{{scope.row.detail}}</span>
    </template>
    <template v-slot:city="{scope}">
      <el-tag>{{scope.row.city}}</el-tag>
    </template>
  </custom-table>
</template>

<script>
export default {
  component: {
    CustomTable
  },
  data () {
    return {
      tableRef: null.tableData: [{date: '2016-05-03'.name: 'Joe'.province: 'XXX province'.city: 'XXX city'.detail: 'XXX area'.address: 'XXX District, XXX City, XXX Province '},].columns: [{attrs: {prop: "date".label: "Date".width: "100"},
          slotHeader: true
        },
        {
          attrs: {prop: "name".label: "Name".width: "80"},
          slot: 'name'
        },
        {
          attrs: {prop: "address".label: "Address".width: '260'},
          // Corresponds to the slot attribute in the child
          slotChildren: ['city'].children: [{attrs: {prop: "province".label: "Province".width: "70"}},
            {
              attrs: {prop: "city".label: "City".width: "80"},
              slot: 'city'}]}],}}}</script>
Copy the code

The generated result is:

Why is slotChildren set when a child has custom content

Take a look at the component code above. All the **slot ** slots are defined in the CustomTable scope. Now that we know about this, let’s look at the different scenarios.

  • Do not set slotChildren property:

    <template
    v-if="column.slotChildren && column.slotChildren.length"
    v-for="name in column.slotChildren"
    v-slot:[name]="scope">
      <slot :name="name" :scope="{... scope, index}"></slot>
    </template>
    Copy the code

    The above code will not execute.

    <template v-slot:city="{scope}">
      <el-tag>{{scope.row.city}}</el-tag>
    </template>
    Copy the code

    The v-slot=city slot is in the address column’s scope. If address does not have slotChildren, the city slot will be ignored and will not be rendered. When the column is looped to City, the city column has no data in the page because the city slot was not added to the $scopedSlots attribute.

  • Set the slotChildren property:

    If the slotChildren attribute is set, then the v-if condition above is true, and the City slot is rendered and added to the $scopedSlots attribute. When the column is looping to city, the slot can be found in the $scopedSlots attribute.

Ps: Custom headers using the slotHeader: true attribute in subentries are invalid, no solution is in place yet. If you have a solution, welcome to advise.