preface

The use of tables is a big part of ElementUI, but it always feels inconvenient to use and some of the desired functionality is missing. This prompted me to do one more wrapper on the ElementUI table component. Add the desired functionality and make it easier to use, but without breaking the functionality.

This project has been uploaded to GitHub, welcome to exchange, hope you can give a star to encourage, thank you ^-^. The online Demo

The effect

The main content

  • Data structure (less HTML code)
  • Editable cells (for different types of editing)
  • Custom display cell contents (Data structure version of el-table-column scope slot)
  • Custom drop-down filter (to obtain remote drop-down filter conditions, custom drop-down filter form)
  • Selection data simplification (click Select/Cancel, quick multiple selection, continuous multiple selection)
  • Ensure original function

Because the code is too much so I did not put all, only part of the explanation, interested partners can go to GitHub download a source.

Data structure

Turn the original HTML structure into a data structure and simply write an array pass in to generate the desired structure

  • The original
 <el-table :data="tableData" style="width: 100%">
    <el-table-column prop="date" label="Date" width="150"></el-table-column>
    <! -- Multilevel header -->
    <el-table-column label="Delivery information">
      <el-table-column prop="name" label="Name" width="120"></el-table-column>
      <el-table-column label="Address">
        <el-table-column prop="province" label="Province" width="120"></el-table-column>
        <el-table-column prop="city" label="Downtown" width="120"></el-table-column>
        <el-table-column prop="address" label="Address" width="300"></el-table-column>
        <el-table-column prop="zip" label="Zip code" width="120"></el-table-column>
      </el-table-column>
    </el-table-column>
  </el-table>
 </el-table>
Copy the code
  • now
// HTML e-table indicates the encapsulated component name <e-table :data="tableDate" :columns="columns"></e-table> // Data columns: [{prop:"date",
        label: "Date",
        width: 150
      },
      {
        label: "Delivery information",
        childrens: [
          {
            label: "Address",
            childrens: [
              {
                label: "Province",
                prop: "province",
                with:120
              },
              {
                label: "Downtown",
                prop: "city"}... ] }}]]Copy the code

In this way, the data can be more convenient to operate, and the structure is clearer. Each el-table-column corresponds to an element in the columns array, which can be nested to achieve multi-level table headers. The element can contain all attributes of an el-table-column.

implementation

Through the render function loop nested generation, can also write. Vue single file generated with HTML structure, but not as much as the render function generated high manipulation, the render function can be more accurate manipulation of elements, but also more close to the compiler. Haven’t used the students quickly learn render

// Render function part of the code
render(h){
    let _this = this,
    columnRender = function(col,h){
    if(! hasOwn(col,'childrens')) {...// The main content is separate
    }else{
        if (Array.isArray(col.childrens) && col.childrens.length > 0) {
                    return h('el-table-column', {
                        attrs: {
                            label: col.label || col.prop
                        }
                    }, [...col.childrens.map(function (column) { / / recursion
                        return columnRender(column, h)
                    })])
                }
         return null; }}return h('el-table', {
            ref: 'elTable'.props: {
                ...this.$attrs, // Pass all bound attributes
                
                // To add functionality, you need to use properties, override, and accept those properties as props, and then add them internally to make sure functionality is not missing
                rowStyle: this.rowStyle_, 
                cellClassName: this.cellClassName_,
                rowClassName: this.rowClassName_,
            },
           
            on: {
                ...this.$listeners, // Pass all monitored events. this.eListeners// Add the event internally to ensure that the function is not missing
            },
            
            scopedSlots: { // Keep its named slot
                empty: function () { 
                    return _this.$slots.empty
                },
                append: function () {
                    return _this.$slots.append
                }
            },
        }, [ this.columns.map(function (col) { // Render the passed columns array
                return columnRender(col, h) // Loop recursion column is separated into a function (defined above)
            }),
            this.$slots.default // Leave the default HTML])}Copy the code

Editable cells

Many of the ElementUI table editable table tutorials on the web have a full line of toggle edit states, which is not what I want. Each cell should be able to control the edit state

// Add the attributes added by the edit function
columns:[
    {
     prop:'name'.label:'name'.edit:true.// This column opens editable mode
     editType:'selection'.// Select edit form, default, i.e. input box can not write this property
     editControl:function(value,row,column){return true},// More precisely control whether each cell of the column is editable
     editComponent:customCompontent,// Custom editing components can be passed inEditAttrs: {size:'mini'. },// Attributes can be passed in
     editListeners: { focus:function(){},... }// Acceptable events}]Copy the code
implementation
// The main code
 if(! hasOwn(col,'childrens')) {... return h('el-table-column', {props: {
                   ...col // Assign attributes}, scopedSlots: {default:function (props) { // Overwrite the default content
                            let {
                                row,
                                column
                            } = props;

                            let funControl = isFunction(col.editControl) ? col.editControl.call(null, row[col.prop], row, column) : true.// Controls the editability of each cellisCan = (_this.tableConfig.cellEdit ! = =false && col.edit && funControl) ? true : false;

                            isCan ? _this.setEditMap({ // Generate the specific location set of the editable table when clicking the cell, display the edit box
                                x: row.rowIndex,
                                y: column.property
                            }) : null;

                            if (isCan && _this.editX === row.rowIndex && _this.editY === column.property){// Click the cell to meet the criteria to display the edit box
                                let options = { / /... Functions and Listeners bindingattrs: { ... col.editAttrs },props: { // Pass data to the edit component
                                        value_: row[col.prop],
                                        column: column,
                                        columnObj: col,
                                        row: row
                                    },
                                    on: {
                                        ...col.editListeners,
                                        setValue: (v) = > { The custom edit component should also provide this event to change the original data
                                            row[col.prop] = v
                                        },
                                        change: (v) = > { // Override the change event to move to the table body event
                                            _this.$emit('cell-value-change', v, row, column, col)
                                        }
                                    },
                                if (col.editComponent && typeof col.editComponent === 'object') { // Use custom edit components
                                    return h(col.editComponent, options)
                                } else { // Use the built-in editing component
                                    return h(editComponents[col.editType || 'default'], options)
                                }

                            } else {
                                / /... The default content}}}})}Copy the code

Custom cell display

In the use of HTML structure can use the scope slot for their own custom display, and now the data structure can only be used after the use of data, the most reasonable and the only should be to call the render function render custom content. (If written as a string parsing to HTML, there are a lot of problems, can’t use components, can’t bind events…)

columns:[
    {
        prop:'render'.label:'Custom display'.renderCell:function(h,{value,row,column}){ // Customize the render content
                    return h('el-button',
                            {
                              attrs: {size:'mini'},
                              on: {click:e= >{... },... }... },'Custom')}// Customize the render content
        formatter:function(value){return `<b>${value}</b>`}// Format the content, which can be written to an HTML string}]Copy the code
implementation
// Main code is followed by editable code
 if (isCan && _this.editX === row.rowIndex && _this.editY === column.property){
     ....
 }else{
     if (col.renderCell && typeof col.renderCell === 'function') { // Display custom content
         return col.renderCell.call(null, h, { // Implement the render function
                                        value: row[col.prop],
                                        row: row,
                                        column: column,
                                    })
                                } else { // Display the default content
                                    return h('span', {
                                        domProps: { // Parse the content as HTML
                                            innerHTML: (col.formatter && typeof col.formatter === 'function')? col.formatter(row[col.prop]) : row[col.prop] } }) } }Copy the code

Custom drop-down filter

The ElementUI table has a free choice of two drop-down filters and needs to be given a value first, which cannot meet the need for the drop-down filter data to be asynchronously fetched from the server. So in order to keep the built-in drop-down function, you need to separate the two methods. Add the defaultHeader attribute. If true, use the default list header, otherwise use the custom header.

//<e-table :columns="columns" :getFilters="getFilters"></e-table>

getFilters:function(){ // The async function returns promise.resolve (data)
    return new Promse((resolve,reject) = >{
        req('/api/getFilters').then(res= >{
            resolve(res.data)
        })
    })
},
columns:[
    {
        prop:'filter'.label:'Custom drop-down filter'.defaultHeader:false.// Default is false. If this is true, custom filters are useless. If you add an array of filters to the default list header, the default table drop-down filters will be used
        
        filter:true.// Enable filtering
        filterType:'selection'.// Built-in drop-down filter type default selection multiple selection
        filterComponent:customCp // Customize the drop-down filter component
        getFilters:function(){ // Control the retrieval of drop-down filter data for each column
            return new Promise(...). }, filterAttrs: {// Pass attributes
            size:'mini'. },filterListeners: {// Bind events
            change:function(){}... }}]Copy the code
implementation

Because the drop-down content is a separate part, the el-Popover component group is used as a carrier to display the drop-down filter content, and the popover is wrapped separately as a component and then instantiated when the drop-down filter button is clicked.

// The main code when the button is clicked
async headFilterBtnClick(columnObj, column, event) {
            let colKey = column.columnKey || column.property || column.id;

            if (this.filterLoads.some(fd= > fd === colKey)) return; // Clicking in loading state is invalid
            const target = event.target;
            let cell = target.tagName === 'I' ? target : target.parentNode,
                filterPanel = this.filterPanels[colKey],
                filtersData = [];
            cell = cell.querySelector('.e-filter-tag') || cell;

            if (filterPanel && this.headFCNs.some(f= > f === colKey)) { // The filter panel already exists and the panel is already open
                filterPanel.doClose()
                return
            }

            this.filterLoads.push(colKey) / / display loading

            try { //await an exception while receiving filtered data asynchronously
                if (columnObj.getFilters && typeof columnObj.getFilters === 'function') {
                    filtersData = (await columnObj.getFilters(columnObj, column)) || [] // Each column can individually and asynchronously fetch the drop-down data
                } else if (this.getFilters) {
                    filtersData = (await this.getFilters(columnObj, column)) || [] // Drop down data fetch}}catch (error) {
                this.filterLoads.splice(this.filterLoads.findIndex(fd= > fd === colKey), 1)
                throw new Error(error)
                return
            }

            if (filterPanel) { // It exists but is not currently open
                this.filters = filtersData;
                filterPanel.filtedList = this.filtedList;
                filterPanel.filters = filtersData;
                this.filterLoads.splice(this.filterLoads.findIndex(fd= > fd === colKey), 1);
                filterPanel.doShow();
                return
            }

            if(! filterPanel) {// There is no filter panel
                filterPanel = new Vue(FilterPanel) // Instantiate the popover component
                this.filterPanels[colKey] = filterPanel // Save each drop-down popover inside the table for easy operation
                filterPanel.reference = cell  //popover displays dependencies
                filterPanel.columnId = colKey // Pass data
                filterPanel.column = column
                filterPanel.columnObj = columnObj
                filterPanel.table = this._self
                filterPanel.filters = filtersData,
                    filterPanel.filtedList = this.filtedList,
                    filterPanel.$mount(document.createElement('div')); / / a mount
                this.filterLoads.splice(this.filterLoads.findIndex(fd= > fd === colKey), 1)
                filterPanel.doShow() // Display the component}},Copy the code
  • The filterPanel component group requires code
 render(h) {
        let _this = this
        return h('el-popover', {
            props: {
                reference: this.reference,
               
            },
            on: {
                hide: this.hide,
                show: this.show,
            },
            ref: "filterPane".scopedSlots: {
                default: function (props) {
                    let options = {
                        attrs: {
                            ..._this.columnObj.filterAttrs // Pass attributes
                        },
                        props: {
                            filters: _this.filters,
                            filtedList: _this.filtedList,
                            column: _this.column,
                            columnObj: _this.columnObj,
                            showPopper: _this.showPopper
                        },
                        on: {
                            ..._this.columnObj.filterListeners, // Pass events
                            filterChange: _this.filterChange // Triggered when data changes}};if (_this.columnObj.filterComponent && typeof _this.columnObj.filterComponent === 'object') { // Customize the drop-down filter component
                        return h(_this.columnObj.filterComponent, options)
                    }
                    // Built-in drop-down filter component
                    return h(filterComponents[_this.columnObj.filterType || 'selection'], options)
                }
            }
        })
        
// Main event, pass data to table
 filterChange(value, columnObj, column) {
            this.table.filterChange(value, columnObj, column)
            this.doClose() // Close the panel
},
Copy the code

Select data simplification

We’ve covered this separately before, the Element UI table with select row/deselect Quick multiple and Quick consecutive multiple, highlighting selected rows;

conclusion

Modify these functions mainly rely on the render function features, although the structure looks unreadable (can use JSX syntax), but to better complete the requirements, also calculate to make up for it, in general, the completion is relatively satisfactory. If you have any suggestions or better ways to fulfill these requirements, please feel free to share.