Element-ui table
Table displays multiple pieces of data with similar structures
Table Attributes
parameter | instructions | type | An optional value | The default value |
---|---|---|---|---|
data | Displayed data | Array | – | – |
fit | Whether the width of the column spreads itself | boolean | – | true |
show-header | Whether to display the table header | boolean | – | true |
row-key | Row data Key, used to optimize Table rendering | Function(row) / string | – | |
empty-text | The text content to be displayed when the data is empty, or slot=”empty” | String | – | Temporarily no data |
Table-column Attributes
parameter | instructions | type | An optional value | The default value |
---|---|---|---|---|
label | Display title | string | – | – |
prop | The name of the field corresponding to the column content | string | – | – |
width | Corresponding to the column width | string | – | – |
Basic table presentation usage
<template>
<el-table
:data="tableData"
style="width: 100%">
<el-table-column prop="date" label="Date" width="180"></el-table-column>
<el-table-column prop="name" label="Name" width="180"></el-table-column>
<el-table-column prop="address" label="Address"></el-table-column>
</el-table>
</template>
Copy the code
The Table structure
Implementation approach
- Table. Vue controls table rendering, table-column controls column rendering, table-header header rendering, table-body rendering, Table-layout defines attributes and methods for sharing the overall table. Table-observer A table observer (a callback after changes to the overall table, notifies the table header and body to make corresponding updates, and ensures that the table-header and table-body change synchronizes).
- Create the table in table.vue
State manager store
To share the table-column, table-header, and table-body components. - The table-column component inserts the attributes and methods associated with the column into
store.states.columns
, table-header and table-body render cells according to store; - By setting the table
Width property of col
Ensure that the table head and body cells are the same width, there is no need to repeatedly set the width of each cell;
The directory structure
- table.vue
- table-column.js
- table-header.js
- table-body.js
- table-layout.js
- table-observer.js
table.vue
Table rendering implementation
Tamplate rendering
<template> <! How does the table component render the tabel-column child? Columns y-table: data submitted to a public datapurp Y-table: Data submitted to a public datapurp Table layout ---------------------- How to ensure th and TD are the same width? Select * from table_column where colGroup = colgroup; select * from table_column where colGroup = colgroup;<div class="y-table"
:class="[{ 'y-table__border': border }]">
<! -- Native -- child -- column rendering -->
<div class="hidden-columns" ref="hiddenColumns">
<slot></slot>
</div>
<! -- -- -- > meter
<div v-if="showHeader"
class="y-table__header-wrapper"
ref="headerWrapper">
<table-header
ref="tableHeader"
:store="store"
:style="{ width: bodyWidth }"></table-header>
</div>
<! - table body -- -- >
<div
class="y-table__body-wrapper"
ref="bodyWrapper">
<table-body
:store="store"
:style="{ width: bodyWidth }"></table-body>
</div>
<! -- Table data is empty -->
<div
v-if=! "" data || data.length === 0"
class="y-table__empty-block"
ref="emptyBolck">
<span class="y-table__empty-text">
<! -- Custom table null data display -->
<slot name="empty">{{emptyText}}</slot>
</span>
</div>
<! - footer - >
</div>
</template>
Copy the code
script
<script>
import TableLayout from './table-layout';
import TableHeader from './table-header';
import TableBody from './table-body';
import {
createStore,
mapStates
} from './store/helper';
import {
addResizeListener,
removeEventListener
} from '.. /.. /.. /src/utils/resize-event';
let tableIdSeed = 1;
export default {
name: 'YTable'.components: {
TableHeader,
TableBody,
},
props: {
// Data must be Array, and the default value is empty Array []
data: {
// Specify the array type of data. If the data passed is not an array type, vue will report an error
type: Array.// Object or array defaults must be obtained from a factory function
default: function () {
return[]; }},// border must be a Boolean type
border: Boolean.// Whether to display the header, Boolean type, default true
showHeader: {
type: Boolean.default: true
},
// The row key is used to optimize table rendering
rowKey: [String.Function].// Whether the width of the column is automatically spread
fit: {
type: Boolean.default: true
},
/** ** The text to be displayed when the table data is empty. */ can also be set with slot="empty"
emptyText: {
type: String.default: 'No data at present',}},computed: {
/ * * * reference: an operator * https://vuex.vuejs.org/zh/guide/state.html#mapstate-%E8%BE%85%E5%8A%A9%E5%87%BD%E6%95%B0 * Bablrc file :{"plugins": ["transform-object-rest-spread"]} */. mapStates({columns: 'columns'.tableData: 'data',}}),data() {
this.store = createStore(this, {
rowKey: this.rowKey,
});
// TableLayout Sets the related properties of the table
const layout = new TableLayout({
store: this.store,
table: this.showHeader: this.showHeader,
});
return {
layout,
resizeState: {
width: null.height: null,}}},methods: {
doLayout() {
this.layout.updateColumnsWidth(); }},watch: {
/ / to monitor data
data: {
// Listen immediately
immediate: true.handler(value) {
// Commit data to the public datapool
this.store.commit('setData', value); }}},/** * child components: table-header and table-body * $children: [] empty array */
created() {
// debugger;
this.tableId = 'y-table_' + tableIdSeed++;
},
beforeMount() {
// debugger;
},
/** * $children: [table-column, table-header, table-body] */
mounted() {
// debugger;
/ / update the column
this.store.updateColumns();
// Set the width of col
this.doLayout();
this.resizeState = {
width: this.$el.offsetWidth,
height: this.$el.offsetHeight,
}
this.$ready = true;
},
}
</script>
Copy the code
table-column.js
/** * table-column:table sub-component, column ** Note: this file is not a Vue file, but a JS file * JSX language render DOM (need to install the corresponding plug-in to support JSX) * https://cn.vuejs.org/v2/guide/render-function.html * the js is table - column components, so when using the component, will be passed two attributes prop and label * How do these two attributes work in order to add the attributes of the column to the columns? * /
import {
mergeOptions,
parseWidth,
parseMinWidth,
compose,
} from './utils';
import {
defaultRenderCell,
} from './config';
let columnIdSeed = 1;
export default {
name: 'YTableColumn'./ / prop by value
props: {
// Table data item field name
prop: String.property: String./ / title
label: String./ / column width
width: {},
minWidth: {},},data() {
return {
isSubColumn: false.columns: [].}},// Compute attributes for caching
computed: {
// owner Chinese meaning the parent of the table component
owner() {
let parent = this.$parent;
while(parent && ! parent.tableId) { parent = parent.$parent; }return parent;
},
/** * Is the parent component of this table-column table or table-column * if it is table-column, it is used for cell combination */
columnOrTableParent() {
let parent = this.$parent;
while(parent && ! parent.tableId && ! parent.columnId) { parent = parent.$parent; }return parent;
},
/ / width
realWidth() {
return parseWidth(this.width);
},
// Minimum width
realMinWidth() {
return parseMinWidth(this.minWidth); }},methods: {
// reduce pairwise comparison operation, concatenation between arrays
getPropsData(. props) {
return props.reduce((prev,cur) = > {
if(Array.isArray(cur)) {
cur.forEach((key) = > {
prev[key] = this[key]; // Assign the value corresponding to the key in the data})}return prev;
}, {});
},
// Get the position of child in children
getColumnElIndex(children, child) {
// console.log('getColumnElIndex', children, child);
return [].indexOf.call(children, child);
},
// Set the column width
setColumnWidth(column) {
if(this.realWidth) {
column.width = this.realWidth;
}
if(this.realMinWidth) {
column.minWidth = this.realMinWidth;
}
if(! column.minWidth) { column.minWidth =80;
}
column.realWidth = column.width === undefined ? column.minWidth : column.width;
return column;
},
/** * Set the render function for the column in the table-column * the render of the column is called with the table-header *@param {Object} column
* @returns column* /
setColumnRenders(column) {
// console.log('setColumnRenders', column);
let originRenderCell = column.renderCell;
// console.log('setColumnRenders', originRenderCell, defaultRenderCell)
originRenderCell = originRenderCell || defaultRenderCell;
// Wrap renderCell
column.renderCell = (h, data) = > {
let children = null;
// console.log('renderCell', h, data, this.$scopeSlots);
// console.log('renderCell', this.$scopedSlots, this)
// If there is a default slot for the column, the default slot is rendered so that the custom rendering for each column is collated to each column of the table-body
// The rendering of the cells in the table body needs to be separated separately and the rendering function for each column needs to be called so that the customization of each column in the table column is synchronized to the table body
// Otherwise, only the corresponding data values of the column are displayed
if(this.$scopedSlots.default) {
// vm.$scopedSlots Sample scope slots and pass in data
children = this.$scopedSlots.default(data);
}else {
children = originRenderCell(h, data);
}
const props = {
class: 'cell'.style: {}};return (
<div {. props} >
{children}
</div>)}returncolumn; }},/ / initialization
beforeCreate() {
// debugger;
this.column = {};
this.columnId = ' ';
},
/ / initialization
created() {
// debugger;
// Define the columnId of the table-column component
const parent = this.columnOrTableParent;
// Is a child column, if the two are not equal, it is a child column (the direct parent component is also table-column)
this.isSubColumn = this.owner ! == parent;this.columnId = (parent.tableId || parent.columnId) + '_column_' + columnIdSeed++;
// Define the default attribute, temporarily only id
const defaults = {
id: this.columnId,
// The name of the field corresponding to the contents of the column. You can also use the property attribute
property: this.prop || this.property,
}
// Base attributes
// const basicProps = ['label', 'prop',];
const basicProps = ['label',];
Collect the column attribute
let column = this.getPropsData(basicProps);
column = mergeOptions(defaults, column);
"// Note that compose is executed from right to left, and now chains equals a function
// const chains = compose(this.setColumnRenders, this.setColumnWidth, this.setColumnForcedProps);
const chains = compose(this.setColumnRenders, this.setColumnWidth);
column = chains(column);
this.columnConfig = column;
},
mounted() {
// debugger;
const owner = this.owner;
const parent = this.columnOrTableParent;
// vm.$children direct child of the current instance (virtual node)
// vm.$el.children direct children of the current DOM node (real DOM node)
// If the direct parent component is table-column
const children = this.isSubColumn ? parent.$el.children : parent.$refs.hiddenColumns.children;
const columnIndex = this.getColumnElIndex(children, this.$el);
// It is critical to store the attributes of this column into the table's public pool
// Store of the parent component table
owner.store.commit('insertColumn'.this.columnConfig, columnIndex, this.isSubColumn ? parent.columnConfig : null);
},
/ / render rendering
render(h) {
console.log('table-column', h, this.$slots);
return h('div'.this.$slots.default); }}Copy the code
[note] :
- Key code: Store the column’s attributes into the common pool of the table
owner.store.commit('insertColumn'.this.columnConfig, columnIndex, this.isSubColumn ? parent.columnConfig : null);
Copy the code
- Render function
render(h) {
return h('div'.this.$slots.default);
}
Copy the code
table-header.js
JSX rendering
render(h) {
let columnRows = convertToRows(this.columns);
return (
<table
class="y-table__header"
cellspacing="0"
cellpadding="0"
border="0">
<colgroup>
{
this.columns.map(column => <col name={column.id} key={column.id} />)}</colgroup>
<thead>
<tr>
{
this._l(columnRows, (column, cellIndex) => (
<th
key={cellIndex}
colspan={column.colspan}
rowspan={column.rowspan}>
<div
class="cell">
{column.label}
</div>
</th>))}</tr>
</thead>
</table>)}Copy the code
[note] :
- This. _l is a function that renders a list, renderList
table-body.js
JSX rendering
render(h) {
const data = this.data || [];
const columns = this.columns || [];
return (
<table
class="y-table__body"
cellspacing="0"
cellpadding="0"
border="0">
<colgroup>
{
columns.map(column => <col name={column.id} key={column.id} />)}</colgroup>
<tbody>
{
data.reduce((acc, row) => {
return acc.concat(this.wrappedRowRender(row, acc.length))
}, [])
}
</tbody>
</table>)}Copy the code
[note]
- JSX requires plug-in support
- The official document: cn.vuejs.org/v2/guide/re…
- npm install @vue/babel-preset-jsx @vue/babel-helper-vue-jsx-merge-props
- {“plugins”: [“transform-object-rest-spread”,”transform-vue-jsx”]}
table-layout.js
import Vue from 'vue';
/** * TableLayout defines attributes and methods related to the table as a whole * used in table.vue to control the table as a whole */
class TableLayout {
/ / the constructor
constructor(options) {
console.log('tableLayout', options);
this.table = null;
this.store = null;
this.showHeader = true;
this.fit = true; / / what meaning?
this.bodyWidth = null;
this.observers = [];
/ / assignment
for(let name in options) {
if(options.hasOwnProperty(name)) {
this[name] = options[name]; }}if(!this.table) {
throw new Error('table is required for Table Layout');
}
if(!this.store) {
throw new Error('store is required for Table Layout'); }}/** * public table handling methods * such as: setHeight setHeight, etc. */
/** * Vue. Prototype.$isServer * Whether a Vue instance is running on the server, the value of the attribute true indicates that the instance is running on the server, each Vue instance can be determined by this attribute. This property is typically used for server rendering to distinguish whether code is running on the server. * * Dynamically calculates the width (columns with no width assigned) * If there is only one such column, allocate the remaining width to it * If there are more than one such column, * * Update the realWidth of columns * After updating the columns, both the header and the footer DOM need to be changed accordingly */
updateColumnsWidth() {
// console.log('updateColumnsWidth', this.table.columns, Vue.prototype.$isServer);
// console.log('updateColumnsWidth', this.table.$el);
// Returns if running on a server
if(Vue.prototype.$isServer) return;
const fit = this.fit;
const bodyWidth = this.table.$el.clientWidth;
let bodyMinWidth = 0;
const flattenColumns = this.table.columns;
// filter does not change the original array and returns the pointer to the address of the corresponding array item
// So if you change the returned array value, the original array value will change accordingly
let flexColumns = flattenColumns.filter((column) = > typeofcolumn.width ! = ='number');
// If width exists and realWidth also exists, set realWdith to null
flattenColumns.forEach((column) = > {
if(typeof column.width === 'number' && column.realWidth) column.realWidth = null;
})
if(flexColumns.length > 0 && fit) {
flattenColumns.forEach((column) = > {
bodyMinWidth += column.width || column.minWidth || 80;
})
const scrollYWidth = 0;
// If there is no scroll bar
// Check by bodyMinWidth
// console.log('999', bodyMinWidth <= bodyWidth - scrollYWidth)
if(bodyMinWidth <= bodyWidth - scrollYWidth) {
// Total width of columns with no allocated width
const totalFlexWidth = bodyWidth - scrollYWidth - bodyMinWidth;
if(flexColumns.length === 1) {
flexColumns[0].realWidth = (flexColumns[0].minWidth || 80) + totalFlexWidth;
}else {
// Calculate the minimum width of all columns with no width
const allColumnsWidth = flexColumns.reduce((prev, column) = > prev + (column.minWidth || 80), 0);
// console.log('allColumnsWidth', flexColumns, allColumnsWidth)
// Calculate the multiple
const flexWidthPerPixel = totalFlexWidth / allColumnsWidth;
// console.log('3333', flexWidthPerPixel, allColumnsWidth, totalFlexWidth);
// Width of non-first column
let noneFirstWidth = 0;
flexColumns.forEach((column, index) = > {
if(index === 0) return;
// console.log('7', column, index)
// Take an integer not greater than this value, which will allocate the extra width to the first column
const flexWidth = Math.floor((column.minWidth || 80) * flexWidthPerPixel);
// console.log('7', flexWidth, flexWidthPerPixel)
noneFirstWidth += flexWidth;
// console.log('7', noneFirstWidth)
column.realWidth = (column.minWidth || 80) + flexWidth;
})
// Calculate the realWidth of the first one
flexColumns[0].realWidth = (flexColumns[0].minWidth || 80) + totalFlexWidth - noneFirstWidth; }}this.bodyWidth = Math.max(bodyMinWidth, bodyWidth);
this.table.resizeState.width = this.bodyWidth;
// console.log('999', this.table.columns)
// console.log('999', bodyWidth, scrollYWidth, bodyMinWidth, bodyWidth - scrollYWidth - bodyMinWidth)
} else {
// There are horizontal scroll bars}}}export default TableLayout;
Copy the code
table-observer.js
/** * mixin layout viewer * LayoutObserver layout viewer * for table layout components: table-header and table-body */
export default {
computed: {
// Table layout properties
tableLayout() {
let layout = this.layout;
// console.log('tableLayout', layout);
if(! layout &&this.table) {
layout = this.table.layout;
}
// If there is still none, an error is thrown
if(! layout) {throw new Error('Can not find table layout.');
}
returnlayout; }},methods: {
/** * Set the width of col in colGroup * bind the table cell width by controlling the col in colgroup * Without having to set the style repeatedly for each cell or row * col property: * -width: specifies the width of the cell * th/ TD property height: * -colspan: specifies the number of columns that a cell can span * -rowSPAN: specifies the number of rows that a cell can span * * vue: Gets the actual DOM of the current component this.$el * js: one of the ways to get dom nodes * js: Gets all elements that match the specified CSS selector QuerySelectorAll (querySelector gets the first matched element) * js: getAttribute * js: sets the DOM attribute setAttribute *@param {Object} layout
* @returns undefined* /
onColumnsChange(layout) {
// console.log('layout', layout);
// Get the real DOM, filtered by querySelectorAll
// The querySelectorAll method returns all elements in the document that match the specified CSS selector, returning the NodeList object
const cols = this.$el.querySelectorAll('colgroup > col');
// console.log('77', cols, this.columns);
const flattenColumns = this.columns;
const columnsMap = {};
flattenColumns.forEach((column) = > {
columnsMap[column.id] = column;
})
// console.log('flatcolumnsMaptenColumns', columnsMap)
if(! cols.length)return;
for(let i = 0, j = cols.length; i < j; i++) {
const col = cols[i];
const name = col.getAttribute('name');
const column = columnsMap[name];
// console.log(i, col, name, columnsMap, column);
if(column) {
col.setAttribute('width', column.realWidth || column.width); }}}},mounted() {
this.onColumnsChange(this.tableLayout);
},
updated() {
// console.log(' module update ', this.$el);
this.onColumnsChange(this.tableLayout); }},Copy the code
conclusion
Implementation approach
- Table. Vue controls table rendering, table-column controls column rendering, table-header header rendering, table-body rendering, Table-layout defines attributes and methods for sharing the overall table. Table-observer A table observer (a callback after changes to the overall table, notifies the table header and body to make corresponding updates, and ensures that the table-header and table-body change synchronizes).
- Create the table in table.vue
State manager store
To share the table-column, table-header, and table-body components. - The table-column component inserts the attributes and methods associated with the column into
store.states.columns
, table-header and table-body render cells according to store; - By setting the table
Width property of col
Ensure that the table head and body cells are the same width, there is no need to repeatedly set the width of each cell;
The directory structure
- table.vue
- table-column.js
- table-header.js
- table-body.js
- table-layout.js
- table-observer.js