You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
250 lines
7.8 KiB
250 lines
7.8 KiB
import Vue from 'vue'; |
|
import scrollbarWidth from 'element-ui/src/utils/scrollbar-width'; |
|
import { parseHeight } from './util'; |
|
|
|
class TableLayout { |
|
constructor(options) { |
|
this.observers = []; |
|
this.table = null; |
|
this.store = null; |
|
this.columns = null; |
|
this.fit = true; |
|
this.showHeader = true; |
|
|
|
this.height = null; |
|
this.scrollX = false; |
|
this.scrollY = false; |
|
this.bodyWidth = null; |
|
this.fixedWidth = null; |
|
this.rightFixedWidth = null; |
|
this.tableHeight = null; |
|
this.headerHeight = 44; // Table Header Height |
|
this.appendHeight = 0; // Append Slot Height |
|
this.footerHeight = 44; // Table Footer Height |
|
this.viewportHeight = null; // Table Height - Scroll Bar Height |
|
this.bodyHeight = null; // Table Height - Table Header Height |
|
this.fixedBodyHeight = null; // Table Height - Table Header Height - Scroll Bar Height |
|
this.gutterWidth = scrollbarWidth(); |
|
|
|
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'); |
|
} |
|
} |
|
|
|
updateScrollY() { |
|
const height = this.height; |
|
if (height === null) return false; |
|
const bodyWrapper = this.table.bodyWrapper; |
|
if (this.table.$el && bodyWrapper) { |
|
const body = bodyWrapper.querySelector('.el-table__body'); |
|
const prevScrollY = this.scrollY; |
|
const scrollY = body.offsetHeight > this.bodyHeight; |
|
this.scrollY = scrollY; |
|
return prevScrollY !== scrollY; |
|
} |
|
return false; |
|
} |
|
|
|
setHeight(value, prop = 'height') { |
|
if (Vue.prototype.$isServer) return; |
|
const el = this.table.$el; |
|
value = parseHeight(value); |
|
this.height = value; |
|
|
|
if (!el && (value || value === 0)) return Vue.nextTick(() => this.setHeight(value, prop)); |
|
|
|
if (typeof value === 'number') { |
|
el.style[prop] = value + 'px'; |
|
this.updateElsHeight(); |
|
} else if (typeof value === 'string') { |
|
el.style[prop] = value; |
|
this.updateElsHeight(); |
|
} |
|
} |
|
|
|
setMaxHeight(value) { |
|
this.setHeight(value, 'max-height'); |
|
} |
|
|
|
getFlattenColumns() { |
|
const flattenColumns = []; |
|
const columns = this.table.columns; |
|
columns.forEach((column) => { |
|
if (column.isColumnGroup) { |
|
flattenColumns.push.apply(flattenColumns, column.columns); |
|
} else { |
|
flattenColumns.push(column); |
|
} |
|
}); |
|
|
|
return flattenColumns; |
|
} |
|
|
|
updateElsHeight() { |
|
if (!this.table.$ready) return Vue.nextTick(() => this.updateElsHeight()); |
|
const { headerWrapper, appendWrapper, footerWrapper } = this.table.$refs; |
|
this.appendHeight = appendWrapper ? appendWrapper.offsetHeight : 0; |
|
|
|
if (this.showHeader && !headerWrapper) return; |
|
|
|
// fix issue (https://github.com/ElemeFE/element/pull/16956) |
|
const headerTrElm = headerWrapper ? headerWrapper.querySelector('.el-table__header tr') : null; |
|
const noneHeader = this.headerDisplayNone(headerTrElm); |
|
|
|
const headerHeight = this.headerHeight = !this.showHeader ? 0 : headerWrapper.offsetHeight; |
|
if (this.showHeader && !noneHeader && headerWrapper.offsetWidth > 0 && (this.table.columns || []).length > 0 && headerHeight < 2) { |
|
return Vue.nextTick(() => this.updateElsHeight()); |
|
} |
|
const tableHeight = this.tableHeight = this.table.$el.clientHeight; |
|
const footerHeight = this.footerHeight = footerWrapper ? footerWrapper.offsetHeight : 0; |
|
if (this.height !== null) { |
|
this.bodyHeight = tableHeight - headerHeight - footerHeight + (footerWrapper ? 1 : 0); |
|
} |
|
this.fixedBodyHeight = this.scrollX ? (this.bodyHeight - this.gutterWidth) : this.bodyHeight; |
|
|
|
const noData = !(this.store.states.data && this.store.states.data.length); |
|
this.viewportHeight = this.scrollX ? tableHeight - (noData ? 0 : this.gutterWidth) : tableHeight; |
|
|
|
this.updateScrollY(); |
|
this.notifyObservers('scrollable'); |
|
} |
|
|
|
headerDisplayNone(elm) { |
|
if (!elm) return true; |
|
let headerChild = elm; |
|
while (headerChild.tagName !== 'DIV') { |
|
if (getComputedStyle(headerChild).display === 'none') { |
|
return true; |
|
} |
|
headerChild = headerChild.parentElement; |
|
} |
|
return false; |
|
} |
|
|
|
updateColumnsWidth() { |
|
if (Vue.prototype.$isServer) return; |
|
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; |
|
}); |
|
|
|
const scrollYWidth = this.scrollY ? this.gutterWidth : 0; |
|
|
|
if (bodyMinWidth <= bodyWidth - scrollYWidth) { // DON'T HAVE SCROLL BAR |
|
this.scrollX = false; |
|
|
|
const totalFlexWidth = bodyWidth - scrollYWidth - bodyMinWidth; |
|
|
|
if (flexColumns.length === 1) { |
|
flexColumns[0].realWidth = (flexColumns[0].minWidth || 80) + totalFlexWidth; |
|
} else { |
|
const allColumnsWidth = flexColumns.reduce((prev, column) => prev + (column.minWidth || 80), 0); |
|
const flexWidthPerPixel = totalFlexWidth / allColumnsWidth; |
|
let noneFirstWidth = 0; |
|
|
|
flexColumns.forEach((column, index) => { |
|
if (index === 0) return; |
|
const flexWidth = Math.floor((column.minWidth || 80) * flexWidthPerPixel); |
|
noneFirstWidth += flexWidth; |
|
column.realWidth = (column.minWidth || 80) + flexWidth; |
|
}); |
|
|
|
flexColumns[0].realWidth = (flexColumns[0].minWidth || 80) + totalFlexWidth - noneFirstWidth; |
|
} |
|
} else { // HAVE HORIZONTAL SCROLL BAR |
|
this.scrollX = true; |
|
flexColumns.forEach(function(column) { |
|
column.realWidth = column.minWidth; |
|
}); |
|
} |
|
|
|
this.bodyWidth = Math.max(bodyMinWidth, bodyWidth); |
|
this.table.resizeState.width = this.bodyWidth; |
|
} else { |
|
flattenColumns.forEach((column) => { |
|
if (!column.width && !column.minWidth) { |
|
column.realWidth = 80; |
|
} else { |
|
column.realWidth = column.width || column.minWidth; |
|
} |
|
|
|
bodyMinWidth += column.realWidth; |
|
}); |
|
this.scrollX = bodyMinWidth > bodyWidth; |
|
|
|
this.bodyWidth = bodyMinWidth; |
|
} |
|
|
|
const fixedColumns = this.store.states.fixedColumns; |
|
|
|
if (fixedColumns.length > 0) { |
|
let fixedWidth = 0; |
|
fixedColumns.forEach(function(column) { |
|
fixedWidth += column.realWidth || column.width; |
|
}); |
|
|
|
this.fixedWidth = fixedWidth; |
|
} |
|
|
|
const rightFixedColumns = this.store.states.rightFixedColumns; |
|
if (rightFixedColumns.length > 0) { |
|
let rightFixedWidth = 0; |
|
rightFixedColumns.forEach(function(column) { |
|
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}.`); |
|
} |
|
}); |
|
} |
|
} |
|
|
|
export default TableLayout;
|
|
|