element/packages/table/src/table-store.js

541 lines
14 KiB
JavaScript

import Vue from 'vue';
import debounce from 'throttle-debounce/debounce';
import merge from 'element-ui/src/utils/merge';
import { orderBy, getColumnById, getRowIdentity } from './util';
const sortData = (data, states) => {
const sortingColumn = states.sortingColumn;
if (!sortingColumn || typeof sortingColumn.sortable === 'string') {
return data;
}
return orderBy(data, states.sortProp, states.sortOrder, sortingColumn.sortMethod);
};
const getKeysMap = function(array, rowKey) {
const arrayMap = {};
(array || []).forEach((row, index) => {
arrayMap[getRowIdentity(row, rowKey)] = { row, index };
});
return arrayMap;
};
const toggleRowSelection = function(states, row, selected) {
let changed = false;
const selection = states.selection;
const index = selection.indexOf(row);
if (typeof selected === 'undefined') {
if (index === -1) {
selection.push(row);
changed = true;
} else {
selection.splice(index, 1);
changed = true;
}
} else {
if (selected && index === -1) {
selection.push(row);
changed = true;
} else if (!selected && index > -1) {
selection.splice(index, 1);
changed = true;
}
}
return changed;
};
const TableStore = function(table, initialState = {}) {
if (!table) {
throw new Error('Table is required.');
}
this.table = table;
this.states = {
rowKey: null,
_columns: [],
originColumns: [],
columns: [],
fixedColumns: [],
rightFixedColumns: [],
leafColumns: [],
fixedLeafColumns: [],
rightFixedLeafColumns: [],
isComplex: false,
_data: null,
filteredData: null,
data: null,
sortingColumn: null,
sortProp: null,
sortOrder: null,
isAllSelected: false,
selection: [],
reserveSelection: false,
selectable: null,
currentRow: null,
hoverRow: null,
filters: {},
expandRows: [],
defaultExpandAll: false
};
for (let prop in initialState) {
if (initialState.hasOwnProperty(prop) && this.states.hasOwnProperty(prop)) {
this.states[prop] = initialState[prop];
}
}
};
TableStore.prototype.mutations = {
setData(states, data) {
const dataInstanceChanged = states._data !== data;
states._data = data;
Object.keys(states.filters).forEach((columnId) => {
const values = states.filters[columnId];
if (!values || values.length === 0) return;
const column = getColumnById(this.states, columnId);
if (column && column.filterMethod) {
data = data.filter((row) => {
return values.some(value => column.filterMethod.call(null, value, row));
});
}
});
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) {
if (dataInstanceChanged) {
this.clearSelection();
} else {
this.cleanSelection();
}
this.updateAllSelected();
} else {
const rowKey = states.rowKey;
if (rowKey) {
const selection = states.selection;
const selectedMap = getKeysMap(selection, rowKey);
states.data.forEach((row) => {
const rowId = getRowIdentity(row, rowKey);
const rowInfo = selectedMap[rowId];
if (rowInfo) {
selection[rowInfo.index] = row;
}
});
this.updateAllSelected();
} else {
console.warn('WARN: rowKey is required when reserve-selection is enabled.');
}
}
const defaultExpandAll = states.defaultExpandAll;
if (defaultExpandAll) {
this.states.expandRows = (states.data || []).slice(0);
}
Vue.nextTick(() => this.table.updateScrollY());
},
changeSortCondition(states, options) {
states.data = sortData((states.filteredData || states._data || []), states);
if (!options || !options.silent) {
this.table.$emit('sort-change', {
column: this.states.sortingColumn,
prop: this.states.sortProp,
order: this.states.sortOrder
});
}
Vue.nextTick(() => this.table.updateScrollY());
},
filterChange(states, options) {
let { column, values, silent } = options;
if (values && !Array.isArray(values)) {
values = [values];
}
const prop = column.property;
const filters = {};
if (prop) {
states.filters[column.id] = values;
filters[column.columnKey || column.id] = values;
}
let data = states._data;
Object.keys(states.filters).forEach((columnId) => {
const values = states.filters[columnId];
if (!values || values.length === 0) return;
const column = getColumnById(this.states, columnId);
if (column && column.filterMethod) {
data = data.filter((row) => {
return values.some(value => column.filterMethod.call(null, value, row));
});
}
});
states.filteredData = data;
states.data = sortData(data, states);
if (!silent) {
this.table.$emit('filter-change', filters);
}
Vue.nextTick(() => this.table.updateScrollY());
},
insertColumn(states, column, index, parent) {
let array = states._columns;
if (parent) {
array = parent.children;
if (!array) array = parent.children = [];
}
if (typeof index !== 'undefined') {
array.splice(index, 0, column);
} else {
array.push(column);
}
if (column.type === 'selection') {
states.selectable = column.selectable;
states.reserveSelection = column.reserveSelection;
}
this.updateColumns(); // hack for dynamics insert column
this.scheduleLayout();
},
removeColumn(states, column) {
let _columns = states._columns;
if (_columns) {
_columns.splice(_columns.indexOf(column), 1);
}
this.updateColumns(); // hack for dynamics remove column
this.scheduleLayout();
},
setHoverRow(states, row) {
states.hoverRow = row;
},
setCurrentRow(states, row) {
const oldCurrentRow = states.currentRow;
states.currentRow = row;
if (oldCurrentRow !== row) {
this.table.$emit('current-change', row, oldCurrentRow);
}
},
rowSelectedChanged(states, row) {
const changed = toggleRowSelection(states, row);
const selection = states.selection;
if (changed) {
const table = this.table;
table.$emit('selection-change', selection);
table.$emit('select', selection, row);
}
this.updateAllSelected();
},
toggleRowExpanded: function(states, row, expanded) {
const expandRows = states.expandRows;
if (typeof expanded !== 'undefined') {
const index = expandRows.indexOf(row);
if (expanded) {
if (index === -1) expandRows.push(row);
} else {
if (index !== -1) expandRows.splice(index, 1);
}
} else {
const index = expandRows.indexOf(row);
if (index === -1) {
expandRows.push(row);
} else {
expandRows.splice(index, 1);
}
}
this.table.$emit('expand', row, expandRows.indexOf(row) !== -1);
},
toggleAllSelection: debounce(10, function(states) {
const data = states.data || [];
const value = !states.isAllSelected;
const selection = this.states.selection;
let selectionChanged = false;
data.forEach((item, index) => {
if (states.selectable) {
if (states.selectable.call(null, item, index) && toggleRowSelection(states, item, value)) {
selectionChanged = true;
}
} else {
if (toggleRowSelection(states, item, value)) {
selectionChanged = true;
}
}
});
const table = this.table;
if (selectionChanged) {
table.$emit('selection-change', selection);
}
table.$emit('select-all', selection);
states.isAllSelected = value;
})
};
const doFlattenColumns = (columns) => {
const result = [];
columns.forEach((column) => {
if (column.children) {
result.push.apply(result, doFlattenColumns(column.children));
} else {
result.push(column);
}
});
return result;
};
TableStore.prototype.updateColumns = function() {
const states = this.states;
const _columns = states._columns || [];
states.fixedColumns = _columns.filter((column) => column.fixed === true || column.fixed === 'left');
states.rightFixedColumns = _columns.filter((column) => column.fixed === 'right');
if (states.fixedColumns.length > 0 && _columns[0] && _columns[0].type === 'selection' && !_columns[0].fixed) {
_columns[0].fixed = true;
states.fixedColumns.unshift(_columns[0]);
}
const notFixedColumns = _columns.filter(column => !column.fixed);
states.originColumns = [].concat(states.fixedColumns).concat(notFixedColumns).concat(states.rightFixedColumns);
const leafColumns = doFlattenColumns(notFixedColumns);
const fixedLeafColumns = doFlattenColumns(states.fixedColumns);
const rightFixedLeafColumns = doFlattenColumns(states.rightFixedColumns);
states.leafColumnsLength = leafColumns.length;
states.fixedLeafColumnsLength = fixedLeafColumns.length;
states.rightFixedLeafColumnsLength = rightFixedLeafColumns.length;
states.columns = [].concat(fixedLeafColumns).concat(leafColumns).concat(rightFixedLeafColumns);
states.isComplex = states.fixedColumns.length > 0 || states.rightFixedColumns.length > 0;
};
TableStore.prototype.isSelected = function(row) {
return (this.states.selection || []).indexOf(row) > -1;
};
TableStore.prototype.clearSelection = function() {
const states = this.states;
states.isAllSelected = false;
const oldSelection = states.selection;
states.selection = [];
if (oldSelection.length > 0) {
this.table.$emit('selection-change', states.selection);
}
};
TableStore.prototype.setExpandRowKeys = function(rowKeys) {
const expandRows = [];
const data = this.states.data;
const rowKey = this.states.rowKey;
if (!rowKey) throw new Error('[Table] prop row-key should not be empty.');
const keysMap = getKeysMap(data, rowKey);
rowKeys.forEach((key) => {
const info = keysMap[key];
if (info) {
expandRows.push(info.row);
}
});
this.states.expandRows = expandRows;
};
TableStore.prototype.toggleRowSelection = function(row, selected) {
const changed = toggleRowSelection(this.states, row, selected);
if (changed) {
this.table.$emit('selection-change', this.states.selection);
}
};
TableStore.prototype.cleanSelection = function() {
const selection = this.states.selection || [];
const data = this.states.data;
const rowKey = this.states.rowKey;
let deleted;
if (rowKey) {
deleted = [];
const selectedMap = getKeysMap(selection, rowKey);
const dataMap = getKeysMap(data, rowKey);
for (let key in selectedMap) {
if (selectedMap.hasOwnProperty(key) && !dataMap[key]) {
deleted.push(selectedMap[key].row);
}
}
} else {
deleted = selection.filter((item) => {
return data.indexOf(item) === -1;
});
}
deleted.forEach((deletedItem) => {
selection.splice(selection.indexOf(deletedItem), 1);
});
if (deleted.length) {
this.table.$emit('selection-change', selection);
}
};
TableStore.prototype.clearFilter = function() {
const states = this.states;
const { tableHeader, fixedTableHeader, rightFixedTableHeader } = this.table.$refs;
let panels = {};
if (tableHeader) panels = merge(panels, tableHeader.filterPanels);
if (fixedTableHeader) panels = merge(panels, fixedTableHeader.filterPanels);
if (rightFixedTableHeader) panels = merge(panels, rightFixedTableHeader.filterPanels);
const keys = Object.keys(panels);
if (!keys.length) return;
keys.forEach(key => {
panels[key].filteredValue = [];
});
states.filters = {};
this.commit('filterChange', {
column: {},
values: [],
silent: true
});
};
TableStore.prototype.clearSort = function() {
const states = this.states;
if (!states.sortingColumn) return;
states.sortingColumn.order = null;
states.sortProp = null;
states.sortOrder = null;
this.commit('changeSortCondition', {
silent: true
});
};
TableStore.prototype.updateAllSelected = function() {
const states = this.states;
const { selection, rowKey, selectable, data } = states;
if (!data || data.length === 0) {
states.isAllSelected = false;
return;
}
let selectedMap;
if (rowKey) {
selectedMap = getKeysMap(states.selection, rowKey);
}
const isSelected = function(row) {
if (selectedMap) {
return !!selectedMap[getRowIdentity(row, rowKey)];
} else {
return selection.indexOf(row) !== -1;
}
};
let isAllSelected = true;
let selectedCount = 0;
for (let i = 0, j = data.length; i < j; i++) {
const item = data[i];
if (selectable) {
const isRowSelectable = selectable.call(null, item, i);
if (isRowSelectable) {
if (!isSelected(item)) {
isAllSelected = false;
break;
} else {
selectedCount++;
}
}
} else {
if (!isSelected(item)) {
isAllSelected = false;
break;
} else {
selectedCount++;
}
}
}
if (selectedCount === 0) isAllSelected = false;
states.isAllSelected = isAllSelected;
};
TableStore.prototype.scheduleLayout = function() {
this.table.debouncedLayout();
};
TableStore.prototype.setCurrentRowKey = function(key) {
const states = this.states;
const rowKey = states.rowKey;
if (!rowKey) throw new Error('[Table] row-key should not be empty.');
const data = states.data || [];
const keysMap = getKeysMap(data, rowKey);
const info = keysMap[key];
if (info) {
states.currentRow = info.row;
}
};
TableStore.prototype.updateCurrentRow = function() {
const states = this.states;
const table = this.table;
const data = states.data || [];
const oldCurrentRow = states.currentRow;
if (data.indexOf(oldCurrentRow) === -1) {
states.currentRow = null;
if (states.currentRow !== oldCurrentRow) {
table.$emit('current-change', null, oldCurrentRow);
}
}
};
TableStore.prototype.commit = function(name, ...args) {
const mutations = this.mutations;
if (mutations[name]) {
mutations[name].apply(this, [this.states].concat(args));
} else {
throw new Error(`Action not found: ${name}`);
}
};
export default TableStore;