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;