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.