Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update Table: add reserveSelection #517

Merged
merged 1 commit into from
Oct 19, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,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

Expand Down
34 changes: 22 additions & 12 deletions examples/docs/zh-cn/table.md
Original file line number Diff line number Diff line change
Expand Up @@ -663,15 +663,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">
Expand Down Expand Up @@ -732,14 +732,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">
Expand Down Expand Up @@ -881,17 +881,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
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
Expand All @@ -907,4 +916,5 @@
| show-tooltip-when-overflow | 当过长被隐藏时显示 tooltip | Boolean | — | false |
| inline-template | 指定该属性后可以自定义 column 模板,参考多选的时间列,通过 row 获取行信息,JSX 里通过 _self 获取当前上下文。此时不需要配置 prop 属性 | — | — |
| align | 对齐方式 | String | left, center, right | left |
| selectable | 仅对 type=selection 的列有效,类型为 Function,Function 的返回值用来决定这一行的 CheckBox 是否可以勾选 | Function | - |
| selectable | 仅对 type=selection 的列有效,类型为 Function,Function 的返回值用来决定这一行的 CheckBox 是否可以勾选 | Function | - | - |
| reserve-selection | 仅对 type=selection 的列有效,类型为 Boolean,为 true 则代表会保留之前数据的选项,需要配合 Table 的 clearSelection 方法使用。 | Boolean | - | false |
14 changes: 6 additions & 8 deletions packages/table/src/table-body.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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) {
Expand All @@ -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) {
Expand Down
6 changes: 4 additions & 2 deletions packages/table/src/table-column.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -117,7 +117,8 @@ export default {
},
fixed: [Boolean, String],
formatter: Function,
selectable: Function
selectable: Function,
reserveSelection: Boolean
},

render() {},
Expand Down Expand Up @@ -203,6 +204,7 @@ export default {
showTooltipWhenOverflow: this.showTooltipWhenOverflow,
formatter: this.formatter,
selectable: this.selectable,
reserveSelection: this.reserveSelection,
fixed: this.fixed
});

Expand Down
152 changes: 122 additions & 30 deletions packages/table/src/table-store.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,23 @@ 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.');
}
this.table = table;

this.states = {
rowKey: null,
_columns: [],
columns: [],
fixedColumns: [],
Expand All @@ -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,
Expand All @@ -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());
Expand All @@ -65,6 +100,7 @@ TableStore.prototype.mutations = {
}
if (column.type === 'selection') {
states.selectable = column.selectable;
states.reserveSelection = column.reserveSelection;
}

this.scheduleLayout();
Expand All @@ -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;
}),

Expand All @@ -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);
}
}
}
Expand Down
Loading