table-store.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574
  1. import Vue from 'vue';
  2. import debounce from 'throttle-debounce/debounce';
  3. import merge from 'element-ui/src/utils/merge';
  4. import { orderBy, getColumnById, getRowIdentity } from './util';
  5. const sortData = (data, states) => {
  6. const sortingColumn = states.sortingColumn;
  7. if (!sortingColumn || typeof sortingColumn.sortable === 'string') {
  8. return data;
  9. }
  10. return orderBy(data, states.sortProp, states.sortOrder, sortingColumn.sortMethod, sortingColumn.sortBy);
  11. };
  12. const getKeysMap = function(array, rowKey) {
  13. const arrayMap = {};
  14. (array || []).forEach((row, index) => {
  15. arrayMap[getRowIdentity(row, rowKey)] = { row, index };
  16. });
  17. return arrayMap;
  18. };
  19. const toggleRowSelection = function(states, row, selected) {
  20. let changed = false;
  21. const selection = states.selection;
  22. const index = selection.indexOf(row);
  23. if (typeof selected === 'undefined') {
  24. if (index === -1) {
  25. selection.push(row);
  26. changed = true;
  27. } else {
  28. selection.splice(index, 1);
  29. changed = true;
  30. }
  31. } else {
  32. if (selected && index === -1) {
  33. selection.push(row);
  34. changed = true;
  35. } else if (!selected && index > -1) {
  36. selection.splice(index, 1);
  37. changed = true;
  38. }
  39. }
  40. return changed;
  41. };
  42. const toggleRowExpansion = function(states, row, expanded) {
  43. let changed = false;
  44. const expandRows = states.expandRows;
  45. if (typeof expanded !== 'undefined') {
  46. const index = expandRows.indexOf(row);
  47. if (expanded) {
  48. if (index === -1) {
  49. expandRows.push(row);
  50. changed = true;
  51. }
  52. } else {
  53. if (index !== -1) {
  54. expandRows.splice(index, 1);
  55. changed = true;
  56. }
  57. }
  58. } else {
  59. const index = expandRows.indexOf(row);
  60. if (index === -1) {
  61. expandRows.push(row);
  62. changed = true;
  63. } else {
  64. expandRows.splice(index, 1);
  65. changed = true;
  66. }
  67. }
  68. return changed;
  69. };
  70. const TableStore = function(table, initialState = {}) {
  71. if (!table) {
  72. throw new Error('Table is required.');
  73. }
  74. this.table = table;
  75. this.states = {
  76. rowKey: null,
  77. _columns: [],
  78. originColumns: [],
  79. columns: [],
  80. fixedColumns: [],
  81. rightFixedColumns: [],
  82. leafColumns: [],
  83. fixedLeafColumns: [],
  84. rightFixedLeafColumns: [],
  85. leafColumnsLength: 0,
  86. fixedLeafColumnsLength: 0,
  87. rightFixedLeafColumnsLength: 0,
  88. isComplex: false,
  89. _data: null,
  90. filteredData: null,
  91. data: null,
  92. sortingColumn: null,
  93. sortProp: null,
  94. sortOrder: null,
  95. isAllSelected: false,
  96. selection: [],
  97. reserveSelection: false,
  98. selectable: null,
  99. currentRow: null,
  100. hoverRow: null,
  101. filters: {},
  102. expandRows: [],
  103. defaultExpandAll: false
  104. };
  105. for (let prop in initialState) {
  106. if (initialState.hasOwnProperty(prop) && this.states.hasOwnProperty(prop)) {
  107. this.states[prop] = initialState[prop];
  108. }
  109. }
  110. };
  111. TableStore.prototype.mutations = {
  112. setData(states, data) {
  113. const dataInstanceChanged = states._data !== data;
  114. states._data = data;
  115. Object.keys(states.filters).forEach((columnId) => {
  116. const values = states.filters[columnId];
  117. if (!values || values.length === 0) return;
  118. const column = getColumnById(this.states, columnId);
  119. if (column && column.filterMethod) {
  120. data = data.filter((row) => {
  121. return values.some(value => column.filterMethod.call(null, value, row, column));
  122. });
  123. }
  124. });
  125. states.filteredData = data;
  126. states.data = sortData((data || []), states);
  127. // states.data.forEach((item) => {
  128. // if (!item.$extra) {
  129. // Object.defineProperty(item, '$extra', {
  130. // value: {},
  131. // enumerable: false
  132. // });
  133. // }
  134. // });
  135. this.updateCurrentRow();
  136. if (!states.reserveSelection) {
  137. if (dataInstanceChanged) {
  138. this.clearSelection();
  139. } else {
  140. this.cleanSelection();
  141. }
  142. this.updateAllSelected();
  143. } else {
  144. const rowKey = states.rowKey;
  145. if (rowKey) {
  146. const selection = states.selection;
  147. const selectedMap = getKeysMap(selection, rowKey);
  148. states.data.forEach((row) => {
  149. const rowId = getRowIdentity(row, rowKey);
  150. const rowInfo = selectedMap[rowId];
  151. if (rowInfo) {
  152. selection[rowInfo.index] = row;
  153. }
  154. });
  155. this.updateAllSelected();
  156. } else {
  157. console.warn('WARN: rowKey is required when reserve-selection is enabled.');
  158. }
  159. }
  160. const defaultExpandAll = states.defaultExpandAll;
  161. if (defaultExpandAll) {
  162. this.states.expandRows = (states.data || []).slice(0);
  163. }
  164. Vue.nextTick(() => this.table.updateScrollY());
  165. },
  166. changeSortCondition(states, options) {
  167. states.data = sortData((states.filteredData || states._data || []), states);
  168. if (!options || !options.silent) {
  169. this.table.$emit('sort-change', {
  170. column: this.states.sortingColumn,
  171. prop: this.states.sortProp,
  172. order: this.states.sortOrder
  173. });
  174. }
  175. Vue.nextTick(() => this.table.updateScrollY());
  176. },
  177. filterChange(states, options) {
  178. let { column, values, silent } = options;
  179. if (values && !Array.isArray(values)) {
  180. values = [values];
  181. }
  182. const prop = column.property;
  183. const filters = {};
  184. if (prop) {
  185. states.filters[column.id] = values;
  186. filters[column.columnKey || column.id] = values;
  187. }
  188. let data = states._data;
  189. Object.keys(states.filters).forEach((columnId) => {
  190. const values = states.filters[columnId];
  191. if (!values || values.length === 0) return;
  192. const column = getColumnById(this.states, columnId);
  193. if (column && column.filterMethod) {
  194. data = data.filter((row) => {
  195. return values.some(value => column.filterMethod.call(null, value, row, column));
  196. });
  197. }
  198. });
  199. states.filteredData = data;
  200. states.data = sortData(data, states);
  201. if (!silent) {
  202. this.table.$emit('filter-change', filters);
  203. }
  204. Vue.nextTick(() => this.table.updateScrollY());
  205. },
  206. insertColumn(states, column, index, parent) {
  207. let array = states._columns;
  208. if (parent) {
  209. array = parent.children;
  210. if (!array) array = parent.children = [];
  211. }
  212. if (typeof index !== 'undefined') {
  213. array.splice(index, 0, column);
  214. } else {
  215. array.push(column);
  216. }
  217. if (column.type === 'selection') {
  218. states.selectable = column.selectable;
  219. states.reserveSelection = column.reserveSelection;
  220. }
  221. this.updateColumns(); // hack for dynamics insert column
  222. this.scheduleLayout();
  223. },
  224. removeColumn(states, column, parent) {
  225. let array = states._columns;
  226. if (parent) {
  227. array = parent.children;
  228. if (!array) array = parent.children = [];
  229. }
  230. if (array) {
  231. array.splice(array.indexOf(column), 1);
  232. }
  233. this.updateColumns(); // hack for dynamics remove column
  234. this.scheduleLayout();
  235. },
  236. setHoverRow(states, row) {
  237. states.hoverRow = row;
  238. },
  239. setCurrentRow(states, row) {
  240. const oldCurrentRow = states.currentRow;
  241. states.currentRow = row;
  242. if (oldCurrentRow !== row) {
  243. this.table.$emit('current-change', row, oldCurrentRow);
  244. }
  245. },
  246. rowSelectedChanged(states, row) {
  247. const changed = toggleRowSelection(states, row);
  248. const selection = states.selection;
  249. if (changed) {
  250. const table = this.table;
  251. table.$emit('selection-change', selection ? selection.slice() : []);
  252. table.$emit('select', selection, row);
  253. }
  254. this.updateAllSelected();
  255. },
  256. toggleAllSelection: debounce(10, function(states) {
  257. const data = states.data || [];
  258. if (data.length === 0) return;
  259. const value = !states.isAllSelected;
  260. const selection = this.states.selection;
  261. let selectionChanged = false;
  262. data.forEach((item, index) => {
  263. if (states.selectable) {
  264. if (states.selectable.call(null, item, index) && toggleRowSelection(states, item, value)) {
  265. selectionChanged = true;
  266. }
  267. } else {
  268. if (toggleRowSelection(states, item, value)) {
  269. selectionChanged = true;
  270. }
  271. }
  272. });
  273. const table = this.table;
  274. if (selectionChanged) {
  275. table.$emit('selection-change', selection ? selection.slice() : []);
  276. }
  277. table.$emit('select-all', selection);
  278. states.isAllSelected = value;
  279. })
  280. };
  281. const doFlattenColumns = (columns) => {
  282. const result = [];
  283. columns.forEach((column) => {
  284. if (column.children) {
  285. result.push.apply(result, doFlattenColumns(column.children));
  286. } else {
  287. result.push(column);
  288. }
  289. });
  290. return result;
  291. };
  292. TableStore.prototype.updateColumns = function() {
  293. const states = this.states;
  294. const _columns = states._columns || [];
  295. states.fixedColumns = _columns.filter((column) => column.fixed === true || column.fixed === 'left');
  296. states.rightFixedColumns = _columns.filter((column) => column.fixed === 'right');
  297. if (states.fixedColumns.length > 0 && _columns[0] && _columns[0].type === 'selection' && !_columns[0].fixed) {
  298. _columns[0].fixed = true;
  299. states.fixedColumns.unshift(_columns[0]);
  300. }
  301. const notFixedColumns = _columns.filter(column => !column.fixed);
  302. states.originColumns = [].concat(states.fixedColumns).concat(notFixedColumns).concat(states.rightFixedColumns);
  303. const leafColumns = doFlattenColumns(notFixedColumns);
  304. const fixedLeafColumns = doFlattenColumns(states.fixedColumns);
  305. const rightFixedLeafColumns = doFlattenColumns(states.rightFixedColumns);
  306. states.leafColumnsLength = leafColumns.length;
  307. states.fixedLeafColumnsLength = fixedLeafColumns.length;
  308. states.rightFixedLeafColumnsLength = rightFixedLeafColumns.length;
  309. states.columns = [].concat(fixedLeafColumns).concat(leafColumns).concat(rightFixedLeafColumns);
  310. states.isComplex = states.fixedColumns.length > 0 || states.rightFixedColumns.length > 0;
  311. };
  312. TableStore.prototype.isSelected = function(row) {
  313. return (this.states.selection || []).indexOf(row) > -1;
  314. };
  315. TableStore.prototype.clearSelection = function() {
  316. const states = this.states;
  317. states.isAllSelected = false;
  318. const oldSelection = states.selection;
  319. states.selection = [];
  320. if (oldSelection.length > 0) {
  321. this.table.$emit('selection-change', states.selection ? states.selection.slice() : []);
  322. }
  323. };
  324. TableStore.prototype.setExpandRowKeys = function(rowKeys) {
  325. const expandRows = [];
  326. const data = this.states.data;
  327. const rowKey = this.states.rowKey;
  328. if (!rowKey) throw new Error('[Table] prop row-key should not be empty.');
  329. const keysMap = getKeysMap(data, rowKey);
  330. rowKeys.forEach((key) => {
  331. const info = keysMap[key];
  332. if (info) {
  333. expandRows.push(info.row);
  334. }
  335. });
  336. this.states.expandRows = expandRows;
  337. };
  338. TableStore.prototype.toggleRowSelection = function(row, selected) {
  339. const changed = toggleRowSelection(this.states, row, selected);
  340. if (changed) {
  341. this.table.$emit('selection-change', this.states.selection ? this.states.selection.slice() : []);
  342. }
  343. };
  344. TableStore.prototype.toggleRowExpansion = function(row, expanded) {
  345. const changed = toggleRowExpansion(this.states, row, expanded);
  346. if (changed) {
  347. this.table.$emit('expand-change', row, this.states.expandRows);
  348. }
  349. };
  350. TableStore.prototype.isRowExpanded = function(row) {
  351. const { expandRows = [], rowKey } = this.states;
  352. if (rowKey) {
  353. const expandMap = getKeysMap(expandRows, rowKey);
  354. return !!expandMap[getRowIdentity(row, rowKey)];
  355. }
  356. return expandRows.indexOf(row) !== -1;
  357. };
  358. TableStore.prototype.cleanSelection = function() {
  359. const selection = this.states.selection || [];
  360. const data = this.states.data;
  361. const rowKey = this.states.rowKey;
  362. let deleted;
  363. if (rowKey) {
  364. deleted = [];
  365. const selectedMap = getKeysMap(selection, rowKey);
  366. const dataMap = getKeysMap(data, rowKey);
  367. for (let key in selectedMap) {
  368. if (selectedMap.hasOwnProperty(key) && !dataMap[key]) {
  369. deleted.push(selectedMap[key].row);
  370. }
  371. }
  372. } else {
  373. deleted = selection.filter((item) => {
  374. return data.indexOf(item) === -1;
  375. });
  376. }
  377. deleted.forEach((deletedItem) => {
  378. selection.splice(selection.indexOf(deletedItem), 1);
  379. });
  380. if (deleted.length) {
  381. this.table.$emit('selection-change', selection ? selection.slice() : []);
  382. }
  383. };
  384. TableStore.prototype.clearFilter = function() {
  385. const states = this.states;
  386. const { tableHeader, fixedTableHeader, rightFixedTableHeader } = this.table.$refs;
  387. let panels = {};
  388. if (tableHeader) panels = merge(panels, tableHeader.filterPanels);
  389. if (fixedTableHeader) panels = merge(panels, fixedTableHeader.filterPanels);
  390. if (rightFixedTableHeader) panels = merge(panels, rightFixedTableHeader.filterPanels);
  391. const keys = Object.keys(panels);
  392. if (!keys.length) return;
  393. keys.forEach(key => {
  394. panels[key].filteredValue = [];
  395. });
  396. states.filters = {};
  397. this.commit('filterChange', {
  398. column: {},
  399. values: [],
  400. silent: true
  401. });
  402. };
  403. TableStore.prototype.clearSort = function() {
  404. const states = this.states;
  405. if (!states.sortingColumn) return;
  406. states.sortingColumn.order = null;
  407. states.sortProp = null;
  408. states.sortOrder = null;
  409. this.commit('changeSortCondition', {
  410. silent: true
  411. });
  412. };
  413. TableStore.prototype.updateAllSelected = function() {
  414. const states = this.states;
  415. const { selection, rowKey, selectable, data } = states;
  416. if (!data || data.length === 0) {
  417. states.isAllSelected = false;
  418. return;
  419. }
  420. let selectedMap;
  421. if (rowKey) {
  422. selectedMap = getKeysMap(states.selection, rowKey);
  423. }
  424. const isSelected = function(row) {
  425. if (selectedMap) {
  426. return !!selectedMap[getRowIdentity(row, rowKey)];
  427. } else {
  428. return selection.indexOf(row) !== -1;
  429. }
  430. };
  431. let isAllSelected = true;
  432. let selectedCount = 0;
  433. for (let i = 0, j = data.length; i < j; i++) {
  434. const item = data[i];
  435. if (selectable) {
  436. const isRowSelectable = selectable.call(null, item, i);
  437. if (isRowSelectable) {
  438. if (!isSelected(item)) {
  439. isAllSelected = false;
  440. break;
  441. } else {
  442. selectedCount++;
  443. }
  444. }
  445. } else {
  446. if (!isSelected(item)) {
  447. isAllSelected = false;
  448. break;
  449. } else {
  450. selectedCount++;
  451. }
  452. }
  453. }
  454. if (selectedCount === 0) isAllSelected = false;
  455. states.isAllSelected = isAllSelected;
  456. };
  457. TableStore.prototype.scheduleLayout = function() {
  458. this.table.debouncedLayout();
  459. };
  460. TableStore.prototype.setCurrentRowKey = function(key) {
  461. const states = this.states;
  462. const rowKey = states.rowKey;
  463. if (!rowKey) throw new Error('[Table] row-key should not be empty.');
  464. const data = states.data || [];
  465. const keysMap = getKeysMap(data, rowKey);
  466. const info = keysMap[key];
  467. if (info) {
  468. states.currentRow = info.row;
  469. }
  470. };
  471. TableStore.prototype.updateCurrentRow = function() {
  472. const states = this.states;
  473. const table = this.table;
  474. const data = states.data || [];
  475. const oldCurrentRow = states.currentRow;
  476. if (data.indexOf(oldCurrentRow) === -1) {
  477. states.currentRow = null;
  478. if (states.currentRow !== oldCurrentRow) {
  479. table.$emit('current-change', null, oldCurrentRow);
  480. }
  481. }
  482. };
  483. TableStore.prototype.commit = function(name, ...args) {
  484. const mutations = this.mutations;
  485. if (mutations[name]) {
  486. mutations[name].apply(this, [this.states].concat(args));
  487. } else {
  488. throw new Error(`Action not found: ${name}`);
  489. }
  490. };
  491. export default TableStore;