Update Table:

1. Add rowKey prop for Table.
2. Add clearSelection method for Table.
2. Add reserveSelection prop for TableColumn[type="selection"]
This commit is contained in:
furybean 2016-10-19 18:53:31 +08:00 committed by cinwell.li
parent e396db7c91
commit 6ab0d57428
6 changed files with 167 additions and 61 deletions

View File

@ -25,6 +25,8 @@
- 全屏 Loading 现在默认不再锁定屏幕滚动。如果需要的话,可添加 `lock` 修饰符
- Table 删除属性 fixedColumnCount, customCriteria, customBackgroundColors
- Table 的 allow-no-selection 属性更名为 allow-no-current-row
- Table 的 selectionchange、cellmouseenter、cellmouseleave、cellclick 事件更名为 selection-change、cell-mouseenter、cell-mouseleave、cell-click。
### 1.0.0-rc.7

View File

@ -670,15 +670,15 @@
选择单行数据时使用色块表示。
:::demo Table 组件提供了选择的支持,只需要配置`selection-mode`属性即可实现单选(`single`)、多选(`multiple`),如果不需要则设置为`none`。之后由`selectionchange`事件来管理选中时触发的事件,它会传入一个`value``value`为生成表格时的对应对象。本例中还使用了`allow-no-selection`属性,它接受一个`Boolean`,若为`true`,则允许为空,默认为`false`,此时将会产生默认值,为填入数组的第一个对象。如果需要显示索引,可以增加一列`el-table-column`,设置`type`属性为`index`即可显示从 1 开始的索引号。
:::demo Table 组件提供了选择的支持,只需要配置`selection-mode`属性即可实现单选(`single`)、多选(`multiple`),如果不需要则设置为`none`。之后由`selection-change`事件来管理选中时触发的事件,它会传入一个`value``value`为生成表格时的对应对象。本例中还使用了`allow-no-current-row`属性,它接受一个`Boolean`,若为`true`,则允许为空,默认为`false`,此时将会产生默认值,为填入数组的第一个对象。如果需要显示索引,可以增加一列`el-table-column`,设置`type`属性为`index`即可显示从 1 开始的索引号。
```html
<template>
<el-table
:data="tableData"
selection-mode="single"
@selectionchange="handleSelectionChange"
@selection-change="handleSelectionChange"
style="width: 100%"
allow-no-selection>
allow-no-current-row>
<el-table-column
type="index"
width="50">
@ -739,14 +739,14 @@
选择多行数据时使用 Checkbox。
:::demo 除了`selection-mode`的设置外,多选与单选并没有太大差别,只是传入`selectionchange`事件中的参数从对象变成了对象数组。此外,需要提供一个列来显示多选框: 手动添加一个`el-table-column`,设`type`属性为`selection`即可。在本例中,为了方便说明其他属性,我们还使用了`inline-template`和`show-tooltip-when-overflow`:设置了`inline-template`属性后,可以通过调用`row`对象中的值取代`prop`属性的设置;默认情况下若内容过多会折行显示,若需要单行显示可以使用`show-tooltip-when-overflow`属性,它接受一个`Boolean`,为`true`时多余的内容会在 hover 时以 tooltip 的形式显示出来。
:::demo 除了`selection-mode`的设置外,多选与单选并没有太大差别,只是传入`selection-change`事件中的参数从对象变成了对象数组。此外,需要提供一个列来显示多选框: 手动添加一个`el-table-column`,设`type`属性为`selection`即可。在本例中,为了方便说明其他属性,我们还使用了`inline-template`和`show-tooltip-when-overflow`:设置了`inline-template`属性后,可以通过调用`row`对象中的值取代`prop`属性的设置;默认情况下若内容过多会折行显示,若需要单行显示可以使用`show-tooltip-when-overflow`属性,它接受一个`Boolean`,为`true`时多余的内容会在 hover 时以 tooltip 的形式显示出来。
```html
<template>
<el-table
:data="tableData3"
selection-mode="multiple"
style="width: 100%"
@selectionchange="handleMultipleSelectionChange">
@selection-change="handleMultipleSelectionChange">
<el-table-column
type="selection"
width="50">
@ -888,17 +888,26 @@
| stripe | 是否为斑马纹 table | boolean | — | false |
| border | 是否带有纵向边框 | boolean | — | false |
| fit | 列的宽度是否自撑开 | boolean | — | true |
| rowClassName | 行的 className 的回调,会传入 row, index。 | Function | - | - |
| row-class-name | 行的 className 的回调,会传入 row, index。 | Function | - | - |
| row-key | 行数据的 Key用来优化 Table 的渲染;在使用 reserve-selection 功能的情况下,该属性是必填的 | Function, String | - | |
| selection-mode | 列表项选择模式 | string | single/multiple/none | none |
| allow-no-selection | 单选模式是否允许选项为空 | boolean | — | false |
| allow-no-current-row | 单选模式是否允许选项为空 | boolean | — | false |
### Table Events
| 事件名 | 说明 | 参数 |
| ---- | ---- | ---- |
| selectionchange | 当选择项发生变化时会触发该事件 | selected |
| cellmouseenter | 当单元格 hover 进入时会触发该事件 | row, column, cell, event |
| cellmouseleave | 当单元格 hover 退出时会触发该事件 | row, column, cell, event |
| cellclick | 当某个单元格被点击时会触发该事件 | row, column, cell, event |
| select | 当用户手动勾选数据行的 Checkbox 时触发的事件 | selection |
| select-all | 当用户手动勾选全选 Checkbox 时触发的事件 | selection |
| selection-change | 当选择项发生变化时会触发该事件 | selection |
| cell-mouseenter | 当单元格 hover 进入时会触发该事件 | row, column, cell, event |
| cell-mouseleave | 当单元格 hover 退出时会触发该事件 | row, column, cell, event |
| cell-click | 当某个单元格被点击时会触发该事件 | row, column, cell, event |
| row-click | 当某一行被点击时会触发该事件 | row, event |
### Table Methods
| 方法名 | 说明 | 参数 |
| ---- | ---- | ---- |
| clearSelection | 清空用户的选择,当使用 reserve-selection 功能的时候,可能会需要使用此方法 | selection |
### Table-column Attributes
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
@ -914,4 +923,5 @@
| show-tooltip-when-overflow | 当过长被隐藏时显示 tooltip | Boolean | — | false |
| inline-template | 指定该属性后可以自定义 column 模板,参考多选的时间列,通过 row 获取行信息JSX 里通过 _self 获取当前上下文。此时不需要配置 prop 属性 | — | — |
| align | 对齐方式 | String | left, center, right | left |
| selectable | 仅对 type=selection 的列有效,类型为 FunctionFunction 的返回值用来决定这一行的 CheckBox 是否可以勾选 | Function | - |
| selectable | 仅对 type=selection 的列有效,类型为 FunctionFunction 的返回值用来决定这一行的 CheckBox 是否可以勾选 | Function | - | - |
| reserve-selection | 仅对 type=selection 的列有效,类型为 Boolean为 true 则代表会保留之前数据的选项,需要配合 Table 的 clearSelection 方法使用。 | Boolean | - | false |

View File

@ -134,7 +134,7 @@ export default {
if (cell) {
const column = getColumnByCell(table, cell);
const hoverState = table.hoverState = { cell, column, row };
table.$emit('cellmouseenter', hoverState.row, hoverState.column, hoverState.cell, event);
table.$emit('cell-mouseenter', hoverState.row, hoverState.column, hoverState.cell, event);
}
// 判断是否text-overflow, 如果是就显示tooltip
@ -145,12 +145,10 @@ export default {
handleCellMouseLeave(event) {
const cell = getCell(event);
if (!cell) return;
if (cell) {
const table = this.$parent;
const oldHoverState = table.hoverState;
table.$emit('cellmouseleave', oldHoverState.row, oldHoverState.column, oldHoverState.cell, event);
}
const oldHoverState = this.$parent.hoverState;
this.$parent.$emit('cell-mouseleave', oldHoverState.row, oldHoverState.column, oldHoverState.cell, event);
},
handleMouseEnter(index) {
@ -164,13 +162,13 @@ export default {
if (cell) {
const column = getColumnByCell(table, cell);
if (column) {
table.$emit('cellclick', row, column, cell, event);
table.$emit('cell-click', row, column, cell, event);
}
}
this.store.commit('setSelectedRow', row);
table.$emit('rowclick', row, event);
table.$emit('row-click', row, event);
},
getCellContent(row, property, columnId) {

View File

@ -49,7 +49,7 @@ const forced = {
headerTemplate: function(h, label) {
return <div>{ label || '#' }</div>;
},
template: function(h, { row, $index }) {
template: function(h, { $index }) {
return <div>{ $index + 1 }</div>;
},
sortable: false
@ -117,7 +117,8 @@ export default {
},
fixed: [Boolean, String],
formatter: Function,
selectable: Function
selectable: Function,
reserveSelection: Boolean
},
render() {},
@ -203,6 +204,7 @@ export default {
showTooltipWhenOverflow: this.showTooltipWhenOverflow,
formatter: this.formatter,
selectable: this.selectable,
reserveSelection: this.reserveSelection,
fixed: this.fixed
});

View File

@ -2,6 +2,15 @@ import Vue from 'vue';
import debounce from 'throttle-debounce/debounce';
import { orderBy } from './util';
const getRowIdentity = (row, rowKey) => {
if (!row) throw new Error('row is required when get row identity');
if (typeof rowKey === 'string') {
return row[rowKey];
} else if (typeof rowKey === 'function') {
return rowKey.call(null, row);
}
};
const TableStore = function(table, initialState = {}) {
if (!table) {
throw new Error('Table is required.');
@ -9,6 +18,7 @@ const TableStore = function(table, initialState = {}) {
this.table = table;
this.states = {
rowKey: null,
_columns: [],
columns: [],
fixedColumns: [],
@ -21,8 +31,9 @@ const TableStore = function(table, initialState = {}) {
direction: null
},
isAllSelected: false,
selection: null,
allowNoSelection: false,
selection: [],
reserveSelection: false,
allowNoCurrentRow: false,
selectionMode: 'none',
selectable: null,
currentRow: null,
@ -43,7 +54,31 @@ TableStore.prototype.mutations = {
data.forEach((item) => Vue.set(item, '$selected', false));
}
states.data = orderBy((data || []), states.sortCondition.property, states.sortCondition.direction);
this.updateSelectedRow();
this.updateCurrentRow();
if (!states.reserveSelection) {
states.isAllSelected = false;
} else {
const rowKey = states.rowKey;
if (rowKey) {
const selectionMap = {};
states.selection.forEach((row) => {
selectionMap[getRowIdentity(row, rowKey)] = row;
});
states.data.forEach((row) => {
const rowId = getRowIdentity(row, rowKey);
if (selectionMap[rowId]) {
row.$selected = true;
selectionMap[rowId] = row;
}
});
this.updateAllSelected();
} else {
console.warn('WARN: rowKey is required when reserve-selection is enabled.');
}
}
if (states.fixedColumns.length > 0 || states.rightFixedColumns.length > 0) Vue.nextTick(() => this.table.syncHeight());
Vue.nextTick(() => this.table.updateScrollY());
@ -65,6 +100,7 @@ TableStore.prototype.mutations = {
}
if (column.type === 'selection') {
states.selectable = column.selectable;
states.reserveSelection = column.reserveSelection;
}
this.scheduleLayout();
@ -83,38 +119,61 @@ TableStore.prototype.mutations = {
states.hoverRow = row;
},
rowSelectedChanged(states) {
let isAllSelected = true;
const data = states.data || [];
for (let i = 0, j = data.length; i < j; i++) {
const item = data[i];
if (states.selectable) {
if (states.selectable.call(null, item, i) && !item.$selected) {
isAllSelected = false;
break;
}
} else {
if (!item.$selected) {
isAllSelected = false;
break;
}
rowSelectedChanged(states, row) {
const selection = states.selection;
if (row.$selected) {
if (selection.indexOf(row) === -1) {
selection.push(row);
}
} else {
const index = selection.indexOf(row);
if (index > -1) {
selection.splice(index, 1);
}
}
states.isAllSelected = isAllSelected;
this.table.$emit('selection-change', selection);
this.table.$emit('select', selection, row);
this.updateAllSelected();
},
toggleAllSelection: debounce(10, function(states) {
const data = states.data || [];
const value = !states.isAllSelected;
const selection = this.states.selection;
let selectionChanged = false;
const setSelected = (item) => {
if (item.$selected !== value) {
selectionChanged = true;
if (value) {
if (selection.indexOf(item) === -1) {
selection.push(item);
}
} else {
const itemIndex = selection.indexOf(item);
if (itemIndex > -1) {
selection.splice(itemIndex, 1);
}
}
}
item.$selected = value;
};
data.forEach((item, index) => {
if (states.selectable) {
if (states.selectable.call(null, item, index)) {
item.$selected = value;
setSelected(item);
}
} else {
item.$selected = value;
setSelected(item);
}
});
if (selectionChanged) {
this.table.$emit('selection-change', selection);
}
this.table.$emit('select-all', selection);
states.isAllSelected = value;
}),
@ -138,25 +197,58 @@ TableStore.prototype.updateColumns = function() {
states.columns = [].concat(states.fixedColumns).concat(_columns.filter((column) => !column.fixed)).concat(states.rightFixedColumns);
};
TableStore.prototype.updateSelectedRow = function() {
TableStore.prototype.clearSelection = function() {
const states = this.states;
const oldSelection = states.selection;
oldSelection.forEach((row) => { row.$selected = false; });
if (this.states.reserveSelection) {
const data = states.data || [];
data.forEach((row) => { row.$selected = false; });
}
states.isAllSelected = false;
states.selection = [];
};
TableStore.prototype.updateAllSelected = function() {
const states = this.states;
let isAllSelected = true;
const data = states.data || [];
for (let i = 0, j = data.length; i < j; i++) {
const item = data[i];
if (states.selectable) {
if (states.selectable.call(null, item, i) && !item.$selected) {
isAllSelected = false;
break;
}
} else {
if (!item.$selected) {
isAllSelected = false;
break;
}
}
}
states.isAllSelected = isAllSelected;
};
TableStore.prototype.updateCurrentRow = function() {
const states = this.states;
const table = this.table;
const data = states.data || [];
if (states.selectionMode === 'single') {
const oldSelectedRow = states.currentRow;
if (oldSelectedRow === null && !states.allowNoSelection) {
const oldCurrentRow = states.currentRow;
if (oldCurrentRow === null && !states.allowNoCurrentRow) {
states.currentRow = data[0];
if (states.currentRow !== oldSelectedRow) {
table.$emit('selectionchange', states.currentRow);
if (states.currentRow !== oldCurrentRow) {
table.$emit('selection-change', states.currentRow);
}
} else if (data.indexOf(oldSelectedRow) === -1) {
if (!states.allowNoSelection) {
} else if (data.indexOf(oldCurrentRow) === -1) {
if (!states.allowNoCurrentRow) {
states.currentRow = data[0];
} else {
states.currentRow = null;
}
if (states.currentRow !== oldSelectedRow) {
table.$emit('selectionchange', states.currentRow);
if (states.currentRow !== oldCurrentRow) {
table.$emit('selection-change', states.currentRow);
}
}
}

View File

@ -123,7 +123,9 @@
default: 'none'
},
allowNoSelection: Boolean,
rowKey: [String, Function],
allowNoCurrentRow: Boolean,
rowClassName: [String, Function]
},
@ -134,6 +136,10 @@
},
methods: {
clearSelection() {
this.store.clearSelection();
},
handleMouseLeave() {
this.store.commit('setHoverRow', null);
if (this.hoverState) this.hoverState = null;
@ -192,8 +198,7 @@
selection() {
if (this.selectionMode === 'multiple') {
const data = this.tableData || [];
return data.filter(item => item.$selected === true);
return this.store.selection;
} else if (this.selectionMode === 'single') {
return this.store.currentRow;
}
@ -218,10 +223,6 @@
},
watch: {
selection(val) {
this.$emit('selectionchange', val);
},
height(value) {
this.layout.setHeight(value);
},
@ -247,8 +248,9 @@
data() {
const store = new TableStore(this, {
allowNoSelection: this.allowNoSelection,
selectionMode: this.selectionMode
allowNoCurrentRow: this.allowNoCurrentRow,
selectionMode: this.selectionMode,
rowKey: this.rowKey
});
const layout = new TableLayout({
store,