123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460 |
- import { arrayFindIndex } from 'element-ui/src/utils/util';
- import { getCell, getColumnByCell, getRowIdentity } from './util';
- import { getStyle, hasClass, removeClass, addClass } 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';
- import { mapStates } from './store/helper';
- import TableRow from './table-row.js';
- export default {
- name: 'ElTableBody',
- mixins: [LayoutObserver],
- components: {
- ElCheckbox,
- ElTooltip,
- TableRow
- },
- props: {
- store: {
- required: true
- },
- stripe: Boolean,
- context: {},
- rowClassName: [String, Function],
- rowStyle: [Object, Function],
- fixed: String,
- highlight: Boolean
- },
- render(h) {
- const data = this.data || [];
- return (
- <table
- class="el-table__body"
- cellspacing="0"
- cellpadding="0"
- border="0">
- <colgroup>
- {
- this.columns.map(column => <col name={column.id} key={column.id} />)
- }
- </colgroup>
- <tbody>
- {
- data.reduce((acc, row) => {
- return acc.concat(this.wrappedRowRender(row, acc.length));
- }, [])
- }
- <el-tooltip effect={this.table.tooltipEffect} placement="top" ref="tooltip" content={this.tooltipContent}></el-tooltip>
- </tbody>
- </table>
- );
- },
- computed: {
- table() {
- return this.$parent;
- },
- ...mapStates({
- data: 'data',
- columns: 'columns',
- treeIndent: 'indent',
- leftFixedLeafCount: 'fixedLeafColumnsLength',
- rightFixedLeafCount: 'rightFixedLeafColumnsLength',
- columnsCount: states => states.columns.length,
- leftFixedCount: states => states.fixedColumns.length,
- rightFixedCount: states => states.rightFixedColumns.length,
- hasExpandColumn: states => states.columns.some(({ type }) => type === 'expand')
- }),
- columnsHidden() {
- return this.columns.map((column, index) => this.isColumnHidden(index));
- },
- firstDefaultColumnIndex() {
- return arrayFindIndex(this.columns, ({ type }) => type === 'default');
- }
- },
- watch: {
- // don't trigger getter of currentRow in getCellClass. see https://jsfiddle.net/oe2b4hqt/
- // update DOM manually. see https://github.com/ElemeFE/element/pull/13954/files#diff-9b450c00d0a9dec0ffad5a3176972e40
- 'store.states.hoverRow'(newVal, oldVal) {
- if (!this.store.states.isComplex || this.$isServer) return;
- let raf = window.requestAnimationFrame;
- if (!raf) {
- raf = (fn) => setTimeout(fn, 16);
- }
- raf(() => {
- const rows = this.$el.querySelectorAll('.el-table__row');
- const oldRow = rows[oldVal];
- const newRow = rows[newVal];
- if (oldRow) {
- removeClass(oldRow, 'hover-row');
- }
- if (newRow) {
- addClass(newRow, 'hover-row');
- }
- });
- }
- },
- data() {
- return {
- tooltipContent: ''
- };
- },
- created() {
- this.activateTooltip = debounce(50, tooltip => tooltip.handleShowPopper());
- },
- methods: {
- getKeyOfRow(row, index) {
- const rowKey = this.table.rowKey;
- if (rowKey) {
- return getRowIdentity(row, rowKey);
- }
- return index;
- },
- isColumnHidden(index) {
- if (this.fixed === true || this.fixed === 'left') {
- return index >= this.leftFixedLeafCount;
- } else if (this.fixed === 'right') {
- return index < this.columnsCount - this.rightFixedLeafCount;
- } else {
- return (index < this.leftFixedLeafCount) || (index >= this.columnsCount - this.rightFixedLeafCount);
- }
- },
- getSpan(row, column, rowIndex, columnIndex) {
- let rowspan = 1;
- let colspan = 1;
- const fn = this.table.spanMethod;
- if (typeof fn === 'function') {
- const result = fn({
- row,
- column,
- rowIndex,
- columnIndex
- });
- if (Array.isArray(result)) {
- rowspan = result[0];
- colspan = result[1];
- } else if (typeof result === 'object') {
- rowspan = result.rowspan;
- colspan = result.colspan;
- }
- }
- return { rowspan, colspan };
- },
- getRowStyle(row, rowIndex) {
- const rowStyle = this.table.rowStyle;
- if (typeof rowStyle === 'function') {
- return rowStyle.call(null, {
- row,
- rowIndex
- });
- }
- return rowStyle || null;
- },
- getRowClass(row, rowIndex) {
- const classes = ['el-table__row'];
- if (this.table.highlightCurrentRow && row === this.store.states.currentRow) {
- classes.push('current-row');
- }
- if (this.stripe && rowIndex % 2 === 1) {
- classes.push('el-table__row--striped');
- }
- const rowClassName = this.table.rowClassName;
- if (typeof rowClassName === 'string') {
- classes.push(rowClassName);
- } else if (typeof rowClassName === 'function') {
- classes.push(rowClassName.call(null, {
- row,
- rowIndex
- }));
- }
- if (this.store.states.expandRows.indexOf(row) > -1) {
- classes.push('expanded');
- }
- return classes;
- },
- getCellStyle(rowIndex, columnIndex, row, column) {
- const cellStyle = this.table.cellStyle;
- if (typeof cellStyle === 'function') {
- return cellStyle.call(null, {
- rowIndex,
- columnIndex,
- row,
- column
- });
- }
- return cellStyle;
- },
- getCellClass(rowIndex, columnIndex, row, column) {
- const classes = [column.id, column.align, column.className];
- if (this.isColumnHidden(columnIndex)) {
- classes.push('is-hidden');
- }
- const cellClassName = this.table.cellClassName;
- if (typeof cellClassName === 'string') {
- classes.push(cellClassName);
- } else if (typeof cellClassName === 'function') {
- classes.push(cellClassName.call(null, {
- rowIndex,
- columnIndex,
- row,
- column
- }));
- }
- classes.push('el-table__cell');
- return classes.join(' ');
- },
- getColspanRealWidth(columns, colspan, index) {
- if (colspan < 1) {
- return columns[index].realWidth;
- }
- const widthArr = columns.map(({ realWidth }) => realWidth).slice(index, index + colspan);
- return widthArr.reduce((acc, width) => acc + width, -1);
- },
- handleCellMouseEnter(event, row) {
- const table = this.table;
- const cell = getCell(event);
- if (cell) {
- const column = getColumnByCell(table, cell);
- const hoverState = table.hoverState = { cell, column, row };
- table.$emit('cell-mouse-enter', hoverState.row, hoverState.column, hoverState.cell, event);
- }
- // 判断是否text-overflow, 如果是就显示tooltip
- const cellChild = event.target.querySelector('.cell');
- if (!(hasClass(cellChild, 'el-tooltip') && cellChild.childNodes.length)) {
- return;
- }
- // use range width instead of scrollWidth to determine whether the text is overflowing
- // to address a potential FireFox bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1074543#c3
- const range = document.createRange();
- range.setStart(cellChild, 0);
- range.setEnd(cellChild, cellChild.childNodes.length);
- const rangeWidth = range.getBoundingClientRect().width;
- const padding = (parseInt(getStyle(cellChild, 'paddingLeft'), 10) || 0) +
- (parseInt(getStyle(cellChild, 'paddingRight'), 10) || 0);
- if ((rangeWidth + padding > cellChild.offsetWidth || cellChild.scrollWidth > cellChild.offsetWidth) && this.$refs.tooltip) {
- const tooltip = this.$refs.tooltip;
- // TODO 会引起整个 Table 的重新渲染,需要优化
- this.tooltipContent = cell.innerText || cell.textContent;
- tooltip.referenceElm = cell;
- tooltip.$refs.popper && (tooltip.$refs.popper.style.display = 'none');
- tooltip.doDestroy();
- tooltip.setExpectedState(true);
- this.activateTooltip(tooltip);
- }
- },
- handleCellMouseLeave(event) {
- const tooltip = this.$refs.tooltip;
- if (tooltip) {
- tooltip.setExpectedState(false);
- tooltip.handleClosePopper();
- }
- const cell = getCell(event);
- if (!cell) return;
- const oldHoverState = this.table.hoverState || {};
- this.table.$emit('cell-mouse-leave', oldHoverState.row, oldHoverState.column, oldHoverState.cell, event);
- },
- handleMouseEnter: debounce(30, function(index) {
- this.store.commit('setHoverRow', index);
- }),
- handleMouseLeave: debounce(30, function() {
- this.store.commit('setHoverRow', null);
- }),
- handleContextMenu(event, row) {
- this.handleEvent(event, row, 'contextmenu');
- },
- handleDoubleClick(event, row) {
- this.handleEvent(event, row, 'dblclick');
- },
- handleClick(event, row) {
- this.store.commit('setCurrentRow', row);
- this.handleEvent(event, row, 'click');
- },
- handleEvent(event, row, name) {
- const table = this.table;
- const cell = getCell(event);
- let column;
- if (cell) {
- column = getColumnByCell(table, cell);
- if (column) {
- table.$emit(`cell-${name}`, row, column, cell, event);
- }
- }
- table.$emit(`row-${name}`, row, column, event);
- },
- rowRender(row, $index, treeRowData) {
- const { treeIndent, columns, firstDefaultColumnIndex } = this;
- const rowClasses = this.getRowClass(row, $index);
- let display = true;
- if (treeRowData) {
- rowClasses.push('el-table__row--level-' + treeRowData.level);
- display = treeRowData.display;
- }
- // 指令 v-show 会覆盖 row-style 中 display
- // 使用 :style 代替 v-show https://github.com/ElemeFE/element/issues/16995
- let displayStyle = display ? null : {
- display: 'none'
- };
- return (
- <TableRow
- style={[displayStyle, this.getRowStyle(row, $index)]}
- class={rowClasses}
- key={this.getKeyOfRow(row, $index)}
- nativeOn-dblclick={($event) => this.handleDoubleClick($event, row)}
- nativeOn-click={($event) => this.handleClick($event, row)}
- nativeOn-contextmenu={($event) => this.handleContextMenu($event, row)}
- nativeOn-mouseenter={_ => this.handleMouseEnter($index)}
- nativeOn-mouseleave={this.handleMouseLeave}
- columns={columns}
- row={row}
- index={$index}
- store={this.store}
- context={this.context || this.table.$vnode.context}
- firstDefaultColumnIndex={firstDefaultColumnIndex}
- treeRowData={treeRowData}
- treeIndent={treeIndent}
- columnsHidden={this.columnsHidden}
- getSpan={this.getSpan}
- getColspanRealWidth={this.getColspanRealWidth}
- getCellStyle={this.getCellStyle}
- getCellClass={this.getCellClass}
- handleCellMouseEnter={this.handleCellMouseEnter}
- handleCellMouseLeave={this.handleCellMouseLeave}
- isSelected={this.store.isSelected(row)}
- isExpanded={this.store.states.expandRows.indexOf(row) > -1}
- fixed={this.fixed}
- >
- </TableRow>
- );
- },
- wrappedRowRender(row, $index) {
- const store = this.store;
- const { isRowExpanded, assertRowKey } = store;
- const { treeData, lazyTreeNodeMap, childrenColumnName, rowKey } = store.states;
- if (this.hasExpandColumn && isRowExpanded(row)) {
- const renderExpanded = this.table.renderExpanded;
- const tr = this.rowRender(row, $index);
- if (!renderExpanded) {
- console.error('[Element Error]renderExpanded is required.');
- return tr;
- }
- // 使用二维数组,避免修改 $index
- return [[
- tr,
- <tr key={'expanded-row__' + tr.key}>
- <td colspan={ this.columnsCount } class="el-table__cell el-table__expanded-cell">
- { renderExpanded(this.$createElement, { row, $index, store: this.store }) }
- </td>
- </tr>]];
- } else if (Object.keys(treeData).length) {
- assertRowKey();
- // TreeTable 时,rowKey 必须由用户设定,不使用 getKeyOfRow 计算
- // 在调用 rowRender 函数时,仍然会计算 rowKey,不太好的操作
- const key = getRowIdentity(row, rowKey);
- let cur = treeData[key];
- let treeRowData = null;
- if (cur) {
- treeRowData = {
- expanded: cur.expanded,
- level: cur.level,
- display: true
- };
- if (typeof cur.lazy === 'boolean') {
- if (typeof cur.loaded === 'boolean' && cur.loaded) {
- treeRowData.noLazyChildren = !(cur.children && cur.children.length);
- }
- treeRowData.loading = cur.loading;
- }
- }
- const tmp = [this.rowRender(row, $index, treeRowData)];
- // 渲染嵌套数据
- if (cur) {
- // currentRow 记录的是 index,所以还需主动增加 TreeTable 的 index
- let i = 0;
- const traverse = (children, parent) => {
- if (!(children && children.length && parent)) return;
- children.forEach(node => {
- // 父节点的 display 状态影响子节点的显示状态
- const innerTreeRowData = {
- display: parent.display && parent.expanded,
- level: parent.level + 1
- };
- const childKey = getRowIdentity(node, rowKey);
- if (childKey === undefined || childKey === null) {
- throw new Error('for nested data item, row-key is required.');
- }
- cur = { ...treeData[childKey] };
- // 对于当前节点,分成有无子节点两种情况。
- // 如果包含子节点的,设置 expanded 属性。
- // 对于它子节点的 display 属性由它本身的 expanded 与 display 共同决定。
- if (cur) {
- innerTreeRowData.expanded = cur.expanded;
- // 懒加载的某些节点,level 未知
- cur.level = cur.level || innerTreeRowData.level;
- cur.display = !!(cur.expanded && innerTreeRowData.display);
- if (typeof cur.lazy === 'boolean') {
- if (typeof cur.loaded === 'boolean' && cur.loaded) {
- innerTreeRowData.noLazyChildren = !(cur.children && cur.children.length);
- }
- innerTreeRowData.loading = cur.loading;
- }
- }
- i++;
- tmp.push(this.rowRender(node, $index + i, innerTreeRowData));
- if (cur) {
- const nodes = lazyTreeNodeMap[childKey] || node[childrenColumnName];
- traverse(nodes, cur);
- }
- });
- };
- // 对于 root 节点,display 一定为 true
- cur.display = true;
- const nodes = lazyTreeNodeMap[key] || row[childrenColumnName];
- traverse(nodes, cur);
- }
- return tmp;
- } else {
- return this.rowRender(row, $index);
- }
- }
- }
- };
|