說(shuō)明
這一部分是為 table
相關(guān)組件實(shí)現(xiàn)的對(duì)應(yīng)的狀態(tài)信息的管理。
源碼解讀
import Vue from 'vue';
import debounce from 'throttle-debounce/debounce';
import { orderBy, getColumnById, getRowIdentity } from './util';
/**
* 對(duì)數(shù)據(jù)進(jìn)行排序
* @param data 要排序的數(shù)據(jù)
* @param states 狀態(tài)信息
*/
const sortData = (data, states) => {
const sortingColumn = states.sortingColumn; // 要排序的列
// 如果不存在要排序的列或者 sortable 不是 true
if (!sortingColumn || typeof sortingColumn.sortable === 'string') {
return data;
}
// 排序
return orderBy(data, states.sortProp, states.sortOrder, sortingColumn.sortMethod);
};
/**
* 獲取值和 行、索引 的映射
* @param array 要處理的數(shù)列
* @param rowKey 對(duì)應(yīng)的鍵名或者搜索函數(shù)
* @returns {{}}
*/
const getKeysMap = function(array, rowKey) {
const arrayMap = {};
(array || []).forEach((row, index) => {
arrayMap[getRowIdentity(row, rowKey)] = { row, index };
});
return arrayMap;
};
/**
* 切換被選中的行
* @param states 狀態(tài)信息
* @param row 要處理的行
* @param selected 是否選中
* @returns {boolean} 是否改變
*/
const toggleRowSelection = function(states, row, selected) {
let changed = false;
const selection = states.selection; // 選中的數(shù)組
const index = selection.indexOf(row); // 查找相應(yīng)的行
if (typeof selected === 'undefined') { // 如果沒(méi)有規(guī)定是否選中
if (index === -1) { // 如果之前沒(méi)有選中過(guò)
selection.push(row); // 加入到選擇列表中
changed = true;
} else { // 否則移除
selection.splice(index, 1);
changed = true;
}
} else { // 如果指定了是否選中
if (selected && index === -1) { // 選中,并且之前沒(méi)選中
selection.push(row);
changed = true;
} else if (!selected && index > -1) { // 沒(méi)選中,并且之前選中了
selection.splice(index, 1);
changed = true;
}
}
return changed;
};
const TableStore = function(table, initialState = {}) {
if (!table) { // 必須傳入 vue 實(shí)例
throw new Error('Table is required.');
}
this.table = table;
this.states = {
rowKey: null,
_columns: [],
originColumns: [],
columns: [],
fixedColumns: [],
rightFixedColumns: [],
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
};
// 初始化相應(yīng)的狀態(tài)信息
for (let prop in initialState) {
if (initialState.hasOwnProperty(prop) && this.states.hasOwnProperty(prop)) {
this.states[prop] = initialState[prop];
}
}
};
/**
* mutations
*/
TableStore.prototype.mutations = {
/**
* 設(shè)置數(shù)據(jù)信息
* @param states 狀態(tài)信息
* @param data 數(shù)據(jù)
*/
setData(states, data) { // 設(shè)置數(shù)據(jù)
const dataInstanceChanged = states._data !== data; // 數(shù)據(jù)發(fā)生改變
states._data = data; // 保存原始值
states.data = sortData((data || []), states); // 排序
// states.data.forEach((item) => {
// if (!item.$extra) {
// Object.defineProperty(item, '$extra', {
// value: {},
// enumerable: false
// });
// }
// });
this.updateCurrentRow(); // 更新當(dāng)前行的信息
if (!states.reserveSelection) { // 如果有保留選擇的項(xiàng)
if (dataInstanceChanged) { // 如果數(shù)據(jù)實(shí)例發(fā)生了改變
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) => {
// 行 id
const rowId = getRowIdentity(row, rowKey);
const rowInfo = selectedMap[rowId];
if (rowInfo) {
selection[rowInfo.index] = row;
}
});
// 更新全選信息
this.updateAllSelected();
} else { // 報(bào)錯(cuò),因?yàn)楸A暨x擇模式必須有主鍵
console.warn('WARN: rowKey is required when reserve-selection is enabled.');
}
}
const defaultExpandAll = states.defaultExpandAll;
if (defaultExpandAll) { // 如果默認(rèn)全部展開(kāi)
this.states.expandRows = (states.data || []).slice(0);
}
// 在下次 DOM 更新結(jié)束后,更新垂直滾動(dòng)的信息
Vue.nextTick(() => this.table.updateScrollY());
},
/**
* 更改排序條件
* @param states
*/
changeSortCondition(states) {
states.data = sortData((states.filteredData || states._data || []), states);
// 觸發(fā) sort-change 事件
this.table.$emit('sort-change', {
column: this.states.sortingColumn,
prop: this.states.sortProp,
order: this.states.sortOrder
});
Vue.nextTick(() => this.table.updateScrollY());
},
/**
* 更改篩選器信息
* @param states
* @param options
*/
filterChange(states, options) {
let { column, values } = 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;
// 對(duì)全部有篩選器器的列進(jìn)行處理
Object.keys(states.filters).forEach((columnId) => {
const values = states.filters[columnId];
if (!values || values.length === 0) return;
const column = getColumnById(this.states, columnId);
// 如果存在列,并且該列上有過(guò)濾的方法
if (column && column.filterMethod) {
data = data.filter((row) => {
// 至少符合一條篩選條件
return values.some(value => column.filterMethod.call(null, value, row));
});
}
});
// 篩選后的信息
states.filteredData = data;
// 重新排列數(shù)據(jù)
states.data = sortData(data, states);
this.table.$emit('filter-change', filters);
Vue.nextTick(() => this.table.updateScrollY());
},
/**
* 插入列
* @param states 狀態(tài)信息
* @param column 列
* @param index 索引
* @param parent 父級(jí)
*/
insertColumn(states, column, index, parent) {
let array = states._columns; // 全部列
if (parent) { // 如果有父級(jí)
array = parent.children;
if (!array) array = parent.children = []; // 如果還是不存在,則表明目前該父級(jí)還沒(méi)有孩子
}
if (typeof index !== 'undefined') { // 如果指定了插入的位置
array.splice(index, 0, column);
} else { // 否則加入到最后
array.push(column);
}
if (column.type === 'selection') { // 如果當(dāng)前列是多選框的列
states.selectable = column.selectable;
states.reserveSelection = column.reserveSelection;
}
// 重新規(guī)劃布局
this.scheduleLayout();
},
/**
* 移除列
* @param states 狀態(tài)信息
* @param column 要移除的列
*/
removeColumn(states, column) {
let _columns = states._columns;
if (_columns) { // 如果存在列的數(shù)組,移除對(duì)應(yīng)列
_columns.splice(_columns.indexOf(column), 1);
}
this.scheduleLayout();
},
/**
* 設(shè)置鼠標(biāo)懸浮的行
* @param states 狀態(tài)信息
* @param row 鼠標(biāo)懸浮的行
*/
setHoverRow(states, row) {
states.hoverRow = row;
},
/**
* 設(shè)置當(dāng)前行
* @param states 狀態(tài)信息
* @param row 要更新的行
*/
setCurrentRow(states, row) {
const oldCurrentRow = states.currentRow;
states.currentRow = row;
// 如果發(fā)生了改變,則觸發(fā) current-change 事件
if (oldCurrentRow !== row) {
this.table.$emit('current-change', row, oldCurrentRow);
}
},
/**
* 改變選中行
* @param states 狀態(tài)信息
* @param row 要改變的行
*/
rowSelectedChanged(states, row) {
const changed = toggleRowSelection(states, row);
const selection = states.selection;
if (changed) { // 如果發(fā)生了改變
const table = this.table;
table.$emit('selection-change', selection);
table.$emit('select', selection, row);
}
// 更新全選信息
this.updateAllSelected();
},
/**
* 切換展開(kāi)行
* @param states 狀態(tài)信息
* @param row 行
* @param expanded 是否展開(kāi)
*/
toggleRowExpanded: function(states, row, expanded) {
const expandRows = states.expandRows;
if (typeof expanded !== 'undefined') { // 如果指定了是否展開(kāi)
const index = expandRows.indexOf(row); // 看看之前是否處理過(guò)
if (expanded) { // 展開(kāi)
if (index === -1) expandRows.push(row); // 不存在就添加當(dāng)前航
} else { // 不展開(kāi)
if (index !== -1) expandRows.splice(index, 1); // 存在就移除當(dāng)前行
}
} else { // 如果沒(méi)有指定是否展開(kāi)
const index = expandRows.indexOf(row);
if (index === -1) { // 如果之前不存在,就加入
expandRows.push(row);
} else { // 之前存在就移除
expandRows.splice(index, 1);
}
}
// 觸發(fā) expand 事件
this.table.$emit('expand', row, expandRows.indexOf(row) !== -1);
},
/**
* 切換全選,有 10ms 的限制
*/
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) { // 如果可選
// 如果當(dāng)前行可選,就切換當(dāng)前行的選項(xiàng)
if (states.selectable.call(null, item, index) && toggleRowSelection(states, item, value)) {
selectionChanged = true;
}
} else { // 如果不可選
if (toggleRowSelection(states, item, value)) { // 切換該行的選項(xiàng)
selectionChanged = true;
}
}
});
const table = this.table;
if (selectionChanged) { // 如果所選項(xiàng)發(fā)生了改變
table.$emit('selection-change', selection);
}
// 觸發(fā)全選事件
table.$emit('select-all', selection);
states.isAllSelected = value;
})
};
/**
* 平整化列,即降嵌套的列變成平級(jí)的
*/
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 || [];
// 固定在左側(cè)的列
states.fixedColumns = _columns.filter((column) => column.fixed === true || column.fixed === 'left');
// 固定在右側(cè)的列
states.rightFixedColumns = _columns.filter((column) => column.fixed === 'right');
// 如果有左側(cè)的固定列,并且第一列為未指定固定的勾選列,則讓其固定在左側(cè)
if (states.fixedColumns.length > 0 && _columns[0] && _columns[0].type === 'selection' && !_columns[0].fixed) {
_columns[0].fixed = true;
states.fixedColumns.unshift(_columns[0]);
}
// 原始列
states.originColumns = [].concat(states.fixedColumns).concat(_columns.filter((column) => !column.fixed)).concat(states.rightFixedColumns);
// 將原始列平整化
states.columns = doFlattenColumns(states.originColumns);
// 如果存在固定列就是復(fù)雜的
states.isComplex = states.fixedColumns.length > 0 || states.rightFixedColumns.length > 0;
};
/**
* 判斷某行是否選中
* @param row 要判斷的行
* @returns {boolean}
*/
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) { // 如果之前有選擇過(guò),觸發(fā) selection-change
this.table.$emit('selection-change', states.selection);
}
};
/**
* 設(shè)置要展開(kāi)的行
* @param rowKeys 要展開(kāi)行的主鍵
*/
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);
// 一次對(duì)每一行進(jìn)行處理
rowKeys.forEach((key) => {
const info = keysMap[key];
if (info) {
expandRows.push(info.row);
}
});
this.states.expandRows = expandRows;
};
/**
* 切換行選擇
* @param row 要切換的行
* @param selected 是否選擇
*/
TableStore.prototype.toggleRowSelection = function(row, selected) {
const changed = toggleRowSelection(this.states, row, selected);
if (changed) { // 如果改變了,就觸發(fā)相應(yīng)事件
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) { // 如果有 rowKey
deleted = [];
const selectedMap = getKeysMap(selection, rowKey);
const dataMap = getKeysMap(data, rowKey);
for (let key in selectedMap) {
// 如果選擇的項(xiàng)不再存在于數(shù)據(jù)中
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);
});
// 如果有需要被刪的項(xiàng),觸發(fā) selection-change 事件
if (deleted.length) {
this.table.$emit('selection-change', selection);
}
};
/**
* 更新是否全部被選中
*/
TableStore.prototype.updateAllSelected = function() {
const states = this.states;
const { selection, rowKey, selectable, data } = states;
// 如果不存在數(shù)據(jù),或者數(shù)據(jù)長(zhǎng)度為 0,肯定不可能有被選中的,因此直接設(shè)置 false
if (!data || data.length === 0) {
states.isAllSelected = false;
return;
}
let selectedMap;
// 如果存在 rowKey,則獲取對(duì)應(yīng)的映射
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)) { // 如果當(dāng)前行沒(méi)被選中
isAllSelected = false;
break;
} else { // 當(dāng)前行被選中
selectedCount++;
}
}
} else { // 如果不可選
if (!isSelected(item)) { // 如果當(dāng)前行沒(méi)被選中了
isAllSelected = false;
break;
} else { // 當(dāng)前行被選中
selectedCount++;
}
}
}
// 如果被選擇的數(shù)量仍是 0
if (selectedCount === 0) isAllSelected = false;
// 更改狀態(tài)信息
states.isAllSelected = isAllSelected;
};
/**
* 重新規(guī)劃布局
*/
TableStore.prototype.scheduleLayout = function() {
this.table.debouncedLayout();
};
/**
* 設(shè)置當(dāng)前的行 rowKey
* @param key
*/
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;
}
};
/**
* 更新當(dāng)前行的信息
*/
TableStore.prototype.updateCurrentRow = function() {
const states = this.states;
const table = this.table;
const data = states.data || [];
const oldCurrentRow = states.currentRow;
// 如果舊的當(dāng)前行不存在了
if (data.indexOf(oldCurrentRow) === -1) {
states.currentRow = null;
// 如果當(dāng)前行發(fā)生了改變
if (states.currentRow !== oldCurrentRow) {
table.$emit('current-change', null, oldCurrentRow);
}
}
};
/**
* commit 觸發(fā)相應(yīng)的 mutation
*/
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;