
store文件夾:為table設(shè)計(jì)了一組私有的store數(shù)據(jù),類似(vuex, redux),這個(gè)一會(huì)詳細(xì)講。
config.js: 一些配置和默認(rèn)信息,包括默認(rèn)寬度之類的
dropdown.js: 提供點(diǎn)擊后產(chǎn)生dropdown的一些基礎(chǔ)方法
filter-panel.vue: 渲染過濾面板的
layout-observer.js: 布局使用的一個(gè)Observer,里面提供了一些基礎(chǔ)方法,主要包括兩點(diǎn):1.column變化時(shí),動(dòng)態(tài)更新顯示寬度,2.table在進(jìn)行滾動(dòng)時(shí),計(jì)算滾動(dòng)位置。
table-body, table-column, table-footer, table-header,這個(gè)四個(gè)顧名思義都是分別負(fù)責(zé)渲染對(duì)應(yīng)的body,column,footer,header
table-layout.js: 定義了一個(gè)TableLayout的基礎(chǔ)類,內(nèi)部建立了一個(gè)觀察者模式。
table.vue: 組合上面幾個(gè)渲染模塊,渲染整個(gè)table組件
util.js: 一些工具方法
在store/index.js中
import Watcher from './watcher';
看一下watcher.js
export default Vue.extend({
data() {
return {
states: {
// 3.0 版本后要求必須設(shè)置該屬性
rowKey: null,
// 渲染的數(shù)據(jù)來源,是對(duì) table 中的 data 過濾排序后的結(jié)果
data: [],
// 是否包含固定列
isComplex: false,
// 列
_columns: [], // 不可響應(yīng)的
originColumns: [],
columns: [],
fixedColumns: [],
rightFixedColumns: [],
leafColumns: [],
fixedLeafColumns: [],
rightFixedLeafColumns: [],
leafColumnsLength: 0,
fixedLeafColumnsLength: 0,
rightFixedLeafColumnsLength: 0,
// 選擇
isAllSelected: false,
selection: [],
reserveSelection: false,
selectOnIndeterminate: false,
selectable: null,
// 過濾
filters: {}, // 不可響應(yīng)的
filteredData: null,
// 排序
sortingColumn: null,
sortProp: null,
sortOrder: null,
hoverRow: null
}
};
},
mixins: [expand, current, tree],
methods: {
// 檢查 rowKey 是否存在
assertRowKey() {
const rowKey = this.states.rowKey;
if (!rowKey) throw new Error('[ElTable] prop row-key is required');
},
// 更新列
updateColumns() {
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;
},
// 更新 DOM
scheduleLayout(needUpdateColumns) {
if (needUpdateColumns) {
this.updateColumns();
}
this.table.debouncedUpdateLayout();
},
// 選擇
isSelected(row) {
const { selection = [] } = this.states;
return selection.indexOf(row) > -1;
},
clearSelection() {
const states = this.states;
states.isAllSelected = false;
const oldSelection = states.selection;
if (oldSelection.length) {
states.selection = [];
this.table.$emit('selection-change', []);
}
},
cleanSelection() {
const states = this.states;
const { data, rowKey, selection } = states;
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 => data.indexOf(item) === -1);
}
if (deleted.length) {
const newSelection = selection.filter(item => deleted.indexOf(item) === -1);
states.selection = newSelection;
this.table.$emit('selection-change', newSelection.slice());
}
},
toggleRowSelection(row, selected, emitChange = true) {
const changed = toggleRowStatus(this.states.selection, row, selected);
if (changed) {
const newSelection = (this.states.selection || []).slice();
// 調(diào)用 API 修改選中值,不觸發(fā) select 事件
if (emitChange) {
this.table.$emit('select', newSelection, row);
}
this.table.$emit('selection-change', newSelection);
}
},
_toggleAllSelection() {
const states = this.states;
const { data = [], selection } = states;
// when only some rows are selected (but not all), select or deselect all of them
// depending on the value of selectOnIndeterminate
const value = states.selectOnIndeterminate
? !states.isAllSelected
: !(states.isAllSelected || selection.length);
states.isAllSelected = value;
let selectionChanged = false;
data.forEach((row, index) => {
if (states.selectable) {
if (states.selectable.call(null, row, index) && toggleRowStatus(selection, row, value)) {
selectionChanged = true;
}
} else {
if (toggleRowStatus(selection, row, value)) {
selectionChanged = true;
}
}
});
if (selectionChanged) {
this.table.$emit('selection-change', selection ? selection.slice() : []);
}
this.table.$emit('select-all', selection);
},
updateSelectionByRowKey() {
const states = this.states;
const { selection, rowKey, data } = states;
const selectedMap = getKeysMap(selection, rowKey);
data.forEach(row => {
const rowId = getRowIdentity(row, rowKey);
const rowInfo = selectedMap[rowId];
if (rowInfo) {
selection[rowInfo.index] = row;
}
});
},
updateAllSelected() {
const states = this.states;
const { selection, rowKey, selectable } = states;
// data 為 null 時(shí),解構(gòu)時(shí)的默認(rèn)值會(huì)被忽略
const data = states.data || [];
if (data.length === 0) {
states.isAllSelected = false;
return;
}
let selectedMap;
if (rowKey) {
selectedMap = getKeysMap(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];
const isRowSelectable = selectable && selectable.call(null, item, i);
if (!isSelected(item)) {
if (!selectable || isRowSelectable) {
isAllSelected = false;
break;
}
} else {
selectedCount++;
}
}
if (selectedCount === 0) isAllSelected = false;
states.isAllSelected = isAllSelected;
},
// 過濾與排序
updateFilters(columns, values) {
if (!Array.isArray(columns)) {
columns = [columns];
}
const states = this.states;
const filters = {};
columns.forEach(col => {
states.filters[col.id] = values;
filters[col.columnKey || col.id] = values;
});
return filters;
},
updateSort(column, prop, order) {
if (this.states.sortingColumn && this.states.sortingColumn !== column) {
this.states.sortingColumn.order = null;
}
this.states.sortingColumn = column;
this.states.sortProp = prop;
this.states.sortOrder = order;
},
execFilter() {
const states = this.states;
const { _data, filters } = states;
let data = _data;
Object.keys(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, column));
});
}
});
states.filteredData = data;
},
execSort() {
const states = this.states;
states.data = sortData(states.filteredData, states);
},
// 根據(jù) filters 與 sort 去過濾 data
execQuery(ignore) {
if (!(ignore && ignore.filter)) {
this.execFilter();
}
this.execSort();
},
clearFilter(columnKeys) {
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;
if (typeof columnKeys === 'string') {
columnKeys = [columnKeys];
}
if (Array.isArray(columnKeys)) {
const columns = columnKeys.map(key => getColumnByKey(states, key));
keys.forEach(key => {
const column = columns.find(col => col.id === key);
if (column) {
// TODO: 優(yōu)化這里的代碼
panels[key].filteredValue = [];
}
});
this.commit('filterChange', {
column: columns,
values: [],
silent: true,
multi: true
});
} else {
keys.forEach(key => {
// TODO: 優(yōu)化這里的代碼
panels[key].filteredValue = [];
});
states.filters = {};
this.commit('filterChange', {
column: {},
values: [],
silent: true
});
}
},
clearSort() {
const states = this.states;
if (!states.sortingColumn) return;
this.updateSort(null, null, null);
this.commit('changeSortCondition', {
silent: true
});
},
// 適配層,expand-row-keys 在 Expand 與 TreeTable 中都有使用
setExpandRowKeysAdapter(val) {
// 這里會(huì)觸發(fā)額外的計(jì)算,但為了兼容性,暫時(shí)這么做
this.setExpandRowKeys(val);
this.updateTreeExpandKeys(val);
},
// 展開行與 TreeTable 都要使用
toggleRowExpansionAdapter(row, expanded) {
const hasExpandColumn = this.states.columns.some(({ type }) => type === 'expand');
if (hasExpandColumn) {
this.toggleRowExpansion(row, expanded);
} else {
this.toggleTreeExpansion(row, expanded);
}
}
}
});
Watcher實(shí)際上是利用vue構(gòu)造出的一個(gè)子類,提供了state狀態(tài)包括列數(shù)據(jù)、選擇數(shù)據(jù)、過濾數(shù)據(jù)、排序數(shù)據(jù)等,并且提供了一些列數(shù)據(jù)處理的方法(排序,過濾等)。
mixins: [expand, current, tree],
這里將這三個(gè)文件的內(nèi)容通過mixin混入
current.js
current.js主要是處理current row變化的方法,row數(shù)據(jù)變更,以及事件的拋出。
expand.js
expand文件主要提供處理擴(kuò)展行的幾個(gè)方法
tree.js
tree.js主要是提供了樹形數(shù)據(jù)的節(jié)點(diǎn)更新,序列化等方法
然后再看一下index.js
Watcher.prototype.mutations = {
// 設(shè)置data數(shù)據(jù)
setData(states, data) {},
// 插入列 插入修改列數(shù)組,并通知table重新渲染
insertColumn(states, column, index, parent) {},
// 刪除列 刪除列數(shù)組信息,并通知table重新渲染
removeColumn(states, column, parent) {},
// 對(duì)列進(jìn)行排序
sort(states, options) {},
// 修改排序條件
changeSortCondition(states, options) {},
// 修改過濾器
filterChange(states, options) {},
// 處理全選
toggleAllSelection() {},
// 處理行選中
rowSelectedChanged(states, row) {},
// 設(shè)置hover的行
setHoverRow(states, row) {},
// 設(shè)置當(dāng)前行 調(diào)用current.js里面的方法
setCurrentRow(states, row) {}
};
Watcher.prototype.commit = function(name, ...args) {}
Watcher.prototype.updateTableScrollY = function() {}
export default Watcher;
這里為watcher添加了mutations的原型方法,熟悉store設(shè)計(jì)理念的都能理解,修改store數(shù)據(jù)必須要通過mutation方法保證數(shù)據(jù)流向的清晰
helper.js
helper這里理解為提供createStore, mapStates兩個(gè)方法,
createStore方法主要是創(chuàng)建store對(duì)象,而mapState提供了把state映射到實(shí)例數(shù)據(jù)上的功能。在table.vue中使用創(chuàng)建store
import Store from './index';
export function createStore(table, initialState = {}) {}
export function mapStates(mapper) {};
table.vue是核心組件 簡(jiǎn)化后的模板是這樣
// 隱藏列
<div class="hidden-columns" ref="hiddenColumns"><slot></slot></div>
// 表頭部分
<div class="el-table__header-wrapper"><table-header></table-header></div>
// 主體部分
<div class="el-table__body-wrapper"><table-body></table-body></div>
// 占位塊,沒有數(shù)據(jù)時(shí)渲染
<div class="el-table__empty-block"></div>
// 插入至表格最后一行之后的內(nèi)容 slot插入
<div class="el-table__append-wrapper"><slot="append"></slot></div>
// 表尾部分
<div class="el-table__footer-wrapper"><table-foot></table-foot></div>
// 左側(cè)固定列部分
<div class="el-table__fixed"></div>
// 右側(cè)固定列部分
<div class="el-table__fixed-right"></div>
// 右側(cè)固定列部分補(bǔ)?。L動(dòng)條)
<div class="el-table__fixed-right-patch"></div>
// 用于列寬調(diào)整的代理
<div class="el-table__column-resize-proxy"></div>
在data中創(chuàng)建store
this.store = createStore(this, {
rowKey: this.rowKey,
defaultExpandAll: this.defaultExpandAll,
selectOnIndeterminate: this.selectOnIndeterminate,
// TreeTable 的相關(guān)配置
indent: this.indent,
lazy: this.lazy,
lazyColumnIdentifier: hasChildren,
childrenColumnName: children
});