diff --git a/packages/table/src/layout-observer.js b/packages/table/src/layout-observer.js new file mode 100644 index 0000000000..6e9a1c30bc --- /dev/null +++ b/packages/table/src/layout-observer.js @@ -0,0 +1,68 @@ +export default { + created() { + this.tableLayout.addObserver(this); + }, + + destroyed() { + this.tableLayout.removeObserver(this); + }, + + computed: { + tableLayout() { + let layout = this.layout; + if (!layout && this.table) { + layout = this.table.layout; + } + if (!layout) { + throw new Error('Can not find table layout.'); + } + return layout; + } + }, + + mounted() { + this.onColumnsChange(this.tableLayout); + this.onScrollableChange(this.tableLayout); + }, + + updated() { + if (this.__updated__) return; + this.onColumnsChange(this.tableLayout); + this.onScrollableChange(this.tableLayout); + this.__updated__ = true; + }, + + methods: { + onColumnsChange() { + const cols = this.$el.querySelectorAll('colgroup > col'); + if (!cols.length) return; + const flattenColumns = this.tableLayout.getFlattenColumns(); + const columnsMap = {}; + flattenColumns.forEach((column) => { + columnsMap[column.id] = column; + }); + for (let i = 0, j = cols.length; i < j; i++) { + const col = cols[i]; + const name = col.getAttribute('name'); + const column = columnsMap[name]; + if (column) { + col.setAttribute('width', column.realWidth || column.width); + } + } + }, + + onScrollableChange(layout) { + const cols = this.$el.querySelectorAll('colgroup > col[name=gutter]'); + for (let i = 0, j = cols.length; i < j; i++) { + const col = cols[i]; + col.setAttribute('width', layout.scrollY ? layout.gutterWidth : '0'); + } + const ths = this.$el.querySelectorAll('th.gutter'); + for (let i = 0, j = ths.length; i < j; i++) { + const th = ths[i]; + th.style.width = layout.scrollY ? layout.gutterWidth + 'px' : '0'; + th.style.display = layout.scrollY ? '' : 'none'; + } + } + } +}; diff --git a/packages/table/src/table-body.js b/packages/table/src/table-body.js index f859eb1c55..26da721e59 100644 --- a/packages/table/src/table-body.js +++ b/packages/table/src/table-body.js @@ -3,8 +3,13 @@ import { hasClass, addClass, removeClass } from 'element-ui/src/utils/dom'; import ElCheckbox from 'element-ui/packages/checkbox'; import ElTooltip from 'element-ui/packages/tooltip'; import debounce from 'throttle-debounce/debounce'; +import LayoutObserver from './layout-observer'; export default { + name: 'ElTableBody', + + mixins: [LayoutObserver], + components: { ElCheckbox, ElTooltip @@ -16,9 +21,6 @@ export default { }, stripe: Boolean, context: {}, - layout: { - required: true - }, rowClassName: [String, Function], rowStyle: [Object, Function], fixed: String, @@ -35,11 +37,7 @@ export default { border="0">
- : '' + this.hasGutter ? | : '' } ) @@ -157,9 +148,6 @@ export default { store: { required: true }, - layout: { - required: true - }, border: Boolean, defaultSort: { type: Object, @@ -211,7 +199,7 @@ export default { }, hasGutter() { - return !this.fixed && this.layout.gutterWidth; + return !this.fixed && this.tableLayout.gutterWidth; } }, diff --git a/packages/table/src/table-layout.js b/packages/table/src/table-layout.js index 1276145ffd..9f229cb327 100644 --- a/packages/table/src/table-layout.js +++ b/packages/table/src/table-layout.js @@ -1,7 +1,9 @@ import scrollbarWidth from 'element-ui/src/utils/scrollbar-width'; +import Vue from 'vue'; class TableLayout { constructor(options) { + this.observers = []; this.table = null; this.store = null; this.columns = null; @@ -43,7 +45,7 @@ class TableLayout { const bodyWrapper = this.table.bodyWrapper; if (this.table.$el && bodyWrapper) { const body = bodyWrapper.querySelector('.el-table__body'); - this.scrollY = body.offsetHeight > bodyWrapper.offsetHeight; + this.scrollY = body.offsetHeight > this.bodyHeight; } } @@ -52,19 +54,19 @@ class TableLayout { if (typeof value === 'string' && /^\d+$/.test(value)) { value = Number(value); } - this.height = value; - if (!el) return; + if (!el && value) return Vue.nextTick(() => this.setHeight(value, prop)); + if (typeof value === 'number') { el.style[prop] = value + 'px'; - this.updateHeight(); + this.updateElsHeight(); } else if (typeof value === 'string') { if (value === '') { el.style[prop] = ''; } - this.updateHeight(); + this.updateElsHeight(); } } @@ -72,37 +74,33 @@ class TableLayout { return this.setHeight(value, 'max-height'); } - updateHeight() { - const height = this.tableHeight = this.table.$el.clientHeight; - const noData = !this.table.data || this.table.data.length === 0; + updateElsHeight() { + if (!this.table.$ready) return Vue.nextTick(() => this.updateElsHeight()); const { headerWrapper, appendWrapper, footerWrapper } = this.table.$refs; - const footerHeight = this.footerHeight = footerWrapper ? footerWrapper.offsetHeight : 0; this.appendHeight = appendWrapper ? appendWrapper.offsetHeight : 0; + if (this.showHeader && !headerWrapper) return; - if (!this.showHeader) { - this.headerHeight = 0; - if (this.height !== null && (!isNaN(this.height) || typeof this.height === 'string')) { - this.bodyHeight = height - footerHeight + (footerWrapper ? 1 : 0); - } - this.fixedBodyHeight = this.scrollX ? height - this.gutterWidth : height; - } else { - const headerHeight = this.headerHeight = headerWrapper.offsetHeight; - const bodyHeight = height - headerHeight - footerHeight + (footerWrapper ? 1 : 0); - if (this.height !== null && (!isNaN(this.height) || typeof this.height === 'string')) { - this.bodyHeight = bodyHeight; - } - this.fixedBodyHeight = this.scrollX ? bodyHeight - this.gutterWidth : bodyHeight; + const headerHeight = this.headerHeight = !this.showHeader ? 0 : headerWrapper.offsetHeight; + if (this.showHeader && headerWrapper.offsetWidth > 0 && headerHeight < 2) { + return Vue.nextTick(() => this.updateElsHeight()); } - this.viewportHeight = this.scrollX ? height - (noData ? 0 : this.gutterWidth) : height; - } + const tableHeight = this.tableHeight = this.table.$el.clientHeight; + if (this.height !== null && (!isNaN(this.height) || typeof this.height === 'string')) { + const footerHeight = this.footerHeight = footerWrapper ? footerWrapper.offsetHeight : 0; + this.bodyHeight = tableHeight - headerHeight - footerHeight + (footerWrapper ? 1 : 0); + } + this.fixedBodyHeight = this.scrollX ? this.bodyHeight - this.gutterWidth : this.bodyHeight; - update() { - const fit = this.fit; - const columns = this.table.columns; - const bodyWidth = this.table.$el.clientWidth; - let bodyMinWidth = 0; + const noData = !this.table.data || this.table.data.length === 0; + this.viewportHeight = this.scrollX ? tableHeight - (noData ? 0 : this.gutterWidth) : tableHeight; + this.updateScrollY(); + this.notifyObservers('scrollable'); + } + + getFlattenColumns() { const flattenColumns = []; + const columns = this.table.columns; columns.forEach((column) => { if (column.isColumnGroup) { flattenColumns.push.apply(flattenColumns, column.columns); @@ -111,8 +109,21 @@ class TableLayout { } }); + return flattenColumns; + } + + updateColumnsWidth() { + const fit = this.fit; + const bodyWidth = this.table.$el.clientWidth; + let bodyMinWidth = 0; + + const flattenColumns = this.getFlattenColumns(); let flexColumns = flattenColumns.filter((column) => typeof column.width !== 'number'); + flattenColumns.forEach((column) => { // Clean those columns whose width changed from flex to unflex + if (typeof column.width === 'number' && column.realWidth) column.realWidth = null; + }); + if (flexColumns.length > 0 && fit) { flattenColumns.forEach((column) => { bodyMinWidth += column.width || column.minWidth || 80; @@ -169,7 +180,7 @@ class TableLayout { if (fixedColumns.length > 0) { let fixedWidth = 0; fixedColumns.forEach(function(column) { - fixedWidth += column.realWidth; + fixedWidth += column.realWidth || column.width; }); this.fixedWidth = fixedWidth; @@ -179,11 +190,40 @@ class TableLayout { if (rightFixedColumns.length > 0) { let rightFixedWidth = 0; rightFixedColumns.forEach(function(column) { - rightFixedWidth += column.realWidth; + rightFixedWidth += column.realWidth || column.width; }); this.rightFixedWidth = rightFixedWidth; } + + this.notifyObservers('columns'); + } + + addObserver(observer) { + this.observers.push(observer); + } + + removeObserver(observer) { + const index = this.observers.indexOf(observer); + if (index !== -1) { + this.observers.splice(index, 1); + } + } + + notifyObservers(event) { + const observers = this.observers; + observers.forEach((observer) => { + switch (event) { + case 'columns': + observer.onColumnsChange(this); + break; + case 'scrollable': + observer.onScrollableChange(this); + break; + default: + throw new Error(`Table Layout don't have event ${event}.`); + } + }); } } diff --git a/packages/table/src/table-store.js b/packages/table/src/table-store.js index c551611485..58333a53b2 100644 --- a/packages/table/src/table-store.js +++ b/packages/table/src/table-store.js @@ -94,7 +94,6 @@ const TableStore = function(table, initialState = {}) { fixedLeafColumnsLength: 0, rightFixedLeafColumnsLength: 0, isComplex: false, - _data: null, filteredData: null, data: null, sortingColumn: null, @@ -137,15 +136,6 @@ TableStore.prototype.mutations = { states.filteredData = data; states.data = sortData((data || []), states); - // states.data.forEach((item) => { - // if (!item.$extra) { - // Object.defineProperty(item, '$extra', { - // value: {}, - // enumerable: false - // }); - // } - // }); - this.updateCurrentRow(); if (!states.reserveSelection) { @@ -252,8 +242,10 @@ TableStore.prototype.mutations = { states.reserveSelection = column.reserveSelection; } - this.updateColumns(); // hack for dynamics insert column - this.scheduleLayout(); + if (this.table.$ready) { + this.updateColumns(); // hack for dynamics insert column + this.scheduleLayout(); + } }, removeColumn(states, column, parent) { @@ -266,8 +258,10 @@ TableStore.prototype.mutations = { array.splice(array.indexOf(column), 1); } - this.updateColumns(); // hack for dynamics remove column - this.scheduleLayout(); + if (this.table.$ready) { + this.updateColumns(); // hack for dynamics remove column + this.scheduleLayout(); + } }, setHoverRow(states, row) { @@ -370,7 +364,9 @@ TableStore.prototype.clearSelection = function() { const states = this.states; states.isAllSelected = false; const oldSelection = states.selection; - states.selection = []; + if (states.selection.length) { + states.selection = []; + } if (oldSelection.length > 0) { this.table.$emit('selection-change', states.selection); } @@ -531,8 +527,11 @@ TableStore.prototype.updateAllSelected = function() { states.isAllSelected = isAllSelected; }; -TableStore.prototype.scheduleLayout = function() { - this.table.debouncedLayout(); +TableStore.prototype.scheduleLayout = function(updateColumns) { + if (updateColumns) { + this.updateColumns(); + } + this.table.debouncedUpdateLayout(); }; TableStore.prototype.setCurrentRowKey = function(key) { diff --git a/packages/table/src/table.vue b/packages/table/src/table.vue index 5de902a393..eac3394d9b 100644 --- a/packages/table/src/table.vue +++ b/packages/table/src/table.vue @@ -7,154 +7,213 @@ 'el-table--hidden': isHidden, 'el-table--group': isGroup, 'el-table--fluid-height': maxHeight, + 'el-table--scrollable-x': layout.scrollX, + 'el-table--scrollable-y': layout.scrollY, 'el-table--enable-row-hover': !store.states.isComplex, 'el-table--enable-row-transition': (store.states.data || []).length !== 0 && (store.states.data || []).length < 100 }, tableSize ? `el-table--${ tableSize }` : '']" @mouseleave="handleMouseLeave($event)"> |
---|