Element分析(工具篇)——TableStore

說(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;

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,353評(píng)論 25 708
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫(kù)、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,245評(píng)論 4 61
  • 基本概念 客戶端(Client):移動(dòng)應(yīng)用(iOS、android等應(yīng)用) 服務(wù)器(Server):為客戶端提供服...
    Ths閱讀 670評(píng)論 0 0
  • 總是想抓住一些東西,可是好像在輕易之間,所有覺(jué)得越來(lái)越好的東西變得越來(lái)越差。同時(shí)我也傾向于另一方。多想兩兩方都好。
    枝蘭小記閱讀 266評(píng)論 0 0
  • 我是喝著羊奶長(zhǎng)大的,從小就跟羊這種可愛(ài)的動(dòng)物有著深厚的情感。 我們那兒是平原地帶,沒(méi)有草地和山坡,...
    路重坡閱讀 271評(píng)論 0 0