Element分析(組件篇)——TableHeader

說明

table-header是表頭組件届氢,較為復(fù)雜,直接看源碼解讀徐勃。

源碼解讀

import ElCheckbox from 'element-ui/packages/checkbox';
import ElTag from 'element-ui/packages/tag';
import Vue from 'vue';
import FilterPanel from './filter-panel.vue';

/**
 * 獲取所有的列赶袄,因?yàn)闀星短姿允褂昧诉f歸
 * @param columns 原始的所有列
 * @return 所有列
 */
const getAllColumns = (columns) => {
  const result = [];
  columns.forEach((column) => {
    if (column.children) {
      result.push(column);
      result.push.apply(result, getAllColumns(column.children));
    } else {
      result.push(column);
    }
  });
  return result;
};

/**
 * 將所有的列信息轉(zhuǎn)換成表頭的行信息
 * @param originColumns 列信息
 * @return 表頭信息
 */
const convertToRows = (originColumns) => {
  // 最大層級
  let maxLevel = 1;
  // 遍歷列來判斷表頭每個單元格需要占多少格
  const traverse = (column, parent) => {
    if (parent) {
      column.level = parent.level + 1;
      if (maxLevel < column.level) {
        maxLevel = column.level;
      }
    }
    if (column.children) {
      let colSpan = 0;
      column.children.forEach((subColumn) => {
        traverse(subColumn, column);
        colSpan += subColumn.colSpan;
      });
      column.colSpan = colSpan;
    } else {
      column.colSpan = 1;
    }
  };

  // 獲取每一列的層級
  originColumns.forEach((column) => {
    column.level = 1;
    traverse(column);
  });

  const rows = [];
  for (let i = 0; i < maxLevel; i++) {
    rows.push([]);
  }

  const allColumns = getAllColumns(originColumns);

  // 相同的層級作為同一行
  allColumns.forEach((column) => {
    if (!column.children) {
      column.rowSpan = maxLevel - column.level + 1;
    } else {
      column.rowSpan = 1;
    }
    rows[column.level - 1].push(column);
  });

  return rows;
};

export default {
  name: 'ElTableHeader',

  // 渲染函數(shù)
  render(h) {
    // 原始列信息
    const originColumns = this.store.states.originColumns;
    // 表頭信息
    const columnRows = convertToRows(originColumns, this.columns);

    return (
      // table 上的 style 是為了清除默認(rèn)的間距
      <table
        class="el-table__header"
        cellspacing="0"
        cellpadding="0"
        border="0">
        {/* colgroup 是用來存儲列信息的 */}
        <colgroup>
          {/* 列信息 */}
          {
            this._l(this.columns, column =>
              <col
                name={ column.id }
                width={ column.realWidth || column.width }
              />)
          }
          {/* 如果左側(cè)有固定還需要記錄滾動條的寬度 */}
          {
            !this.fixed && this.layout.gutterWidth
              ? <col name="gutter" width={ this.layout.scrollY ? this.layout.gutterWidth : '' }></col>
              : ''
          }
        </colgroup>
        <thead>
          {
            // 按行渲染
            this._l(columnRows, (columns, rowIndex) =>
              <tr>
              {
                // 渲染每個單元格
                this._l(columns, (column, cellIndex) =>
                  <th
                    // 列高
                    colspan={ column.colSpan }
                    // 行寬
                    rowspan={ column.rowSpan }
                    // 鼠標(biāo)移動事件
                    on-mousemove={ ($event) => this.handleMouseMove($event, column) }
                    // 鼠標(biāo)移出事件
                    on-mouseout={ this.handleMouseOut }
                    // 鼠標(biāo)按下事件
                    on-mousedown={ ($event) => this.handleMouseDown($event, column) }
                    // 鼠標(biāo)單擊事件
                    on-click={ ($event) => this.handleHeaderClick($event, column) }
                    class={
                      [
                        column.id,
                        column.order,
                        column.headerAlign,
                        column.className || '',
                        // 判斷是否隱藏,為了處理 fixed
                        rowIndex === 0 && this.isCellHidden(cellIndex, columns) ? 'is-hidden' : '',
                        // 判斷是不是最后一級
                        !column.children ? 'is-leaf' : ''
                      ]
                    }>
                    <div
                      class={
                        [
                          'cell',
                          // 如果這列有選擇篩選條件就高亮
                          column.filteredValue && column.filteredValue.length > 0 ? 'highlight' : ''
                        ]
                      }>
                    {
                      // 渲染單元格內(nèi)部的內(nèi)容
                      column.renderHeader
                        ? column.renderHeader.call(
                          this._renderProxy,
                          h,
                          {
                            column,
                            $index: cellIndex,
                            store: this.store,
                            _self: this.$parent.$vnode.context
                          })
                        : column.label
                    }
                    {
                      // 渲染排序的標(biāo)志
                      column.sortable
                        ? <span class="caret-wrapper" on-click={ ($event) => this.handleSortClick($event, column) }>
                            <i class="sort-caret ascending"></i>
                            <i class="sort-caret descending"></i>
                          </span>
                        : ''
                    }
                    {
                      // 渲染篩選器的箭頭
                      column.filterable
                         ? <span
                            class="el-table__column-filter-trigger"
                            on-click={ ($event) => this.handleFilterClick($event, column) }>
                            <i
                              class={
                                [
                                  'el-icon-arrow-down',
                                  column.filterOpened ? 'el-icon-arrow-up' : ''
                                ]
                              }>
                            </i>
                          </span>
                        : ''
                    }
                    </div>
                  </th>
                )
              }
              {
                // 彌補(bǔ)滾動條的寬度
                !this.fixed && this.layout.gutterWidth
                  ? <th class="gutter" style={{ width: this.layout.scrollY ? this.layout.gutterWidth + 'px' : '0' }}></th>
                  : ''
              }
              </tr>
            )
          }
        </thead>
      </table>
    );
  },

  props: {
    fixed: String,
    store: {
      required: true
    },
    layout: {
      required: true
    },
    border: Boolean,
    defaultSort: {
      type: Object,
      default() {
        return {
          prop: '',
          order: ''
        };
      }
    }
  },

  components: {
    ElCheckbox,
    ElTag
  },

  computed: {
    // 判斷是不是全選了
    isAllSelected() {
      return this.store.states.isAllSelected;
    },

    // 判斷總的列數(shù)
    columnsCount() {
      return this.store.states.columns.length;
    },

    // 左側(cè)固定列數(shù)
    leftFixedCount() {
      return this.store.states.fixedColumns.length;
    },

    // 右側(cè)固定列數(shù)
    rightFixedCount() {
      return this.store.states.rightFixedColumns.length;
    },

    // 所有的列
    columns() {
      return this.store.states.columns;
    }
  },

  created() {
    this.filterPanels = {};
  },

  mounted() {
    if (this.defaultSort.prop) {
      const states = this.store.states;
      // 排序的屬性
      states.sortProp = this.defaultSort.prop;
      // 升序或降序
      states.sortOrder = this.defaultSort.order || 'ascending';
      this.$nextTick(_ => {
        for (let i = 0, length = this.columns.length; i < length; i++) {
          let column = this.columns[i];
          // 如果是要排序的屬性
          if (column.property === states.sortProp) {
            column.order = states.sortOrder;
            states.sortingColumn = column;
            break;
          }
        }

        if (states.sortingColumn) {
          this.store.commit('changeSortCondition');
        }
      });
    }
  },

  beforeDestroy() {
    const panels = this.filterPanels;
    for (let prop in panels) {
      // 銷毀全部的篩選面板
      if (panels.hasOwnProperty(prop) && panels[prop]) {
        panels[prop].$destroy(true);
      }
    }
  },

  methods: {
    // 判斷單元格是否應(yīng)當(dāng)隱藏
    isCellHidden(index, columns) {
      // 左側(cè)固定的 wrapper 中鞋仍,那么除了固定的這些列都應(yīng)該隱藏掉
      if (this.fixed === true || this.fixed === 'left') {
        return index >= this.leftFixedCount;
      } else if (this.fixed === 'right') {  // 右側(cè)固定的 wrapper 中,規(guī)定列之前的也都應(yīng)當(dāng)隱藏
        let before = 0;
        for (let i = 0; i < index; i++) {
          before += columns[i].colSpan;
        }
        return before < this.columnsCount - this.rightFixedCount;
      } else {  // 剩下的就是隱藏固定的列
        return (index < this.leftFixedCount) || (index >= this.columnsCount - this.rightFixedCount);
      }
    },

    // 切換全選
    toggleAllSelection() {
      this.store.commit('toggleAllSelection');
    },

    // 處理點(diǎn)擊篩選器,應(yīng)當(dāng)顯示對應(yīng)的篩選面板
    handleFilterClick(event, column) {
      event.stopPropagation();
      const target = event.target;
      const cell = target.parentNode;
      const table = this.$parent;

      // 查找對應(yīng)的篩選面板
      let filterPanel = this.filterPanels[column.id];

      // 如果存在钠龙,并且打開了,就關(guān)閉它
      if (filterPanel && column.filterOpened) {
        filterPanel.showPopper = false;
        return;
      }

      // 如果不存在御铃,就創(chuàng)建它
      if (!filterPanel) {
        filterPanel = new Vue(FilterPanel);
        this.filterPanels[column.id] = filterPanel;

        filterPanel.table = table;
        filterPanel.cell = cell;
        filterPanel.column = column;
        !this.$isServer && filterPanel.$mount(document.createElement('div'));
      }

      // 創(chuàng)建后打開
      setTimeout(() => {
        filterPanel.showPopper = true;
      }, 16);
    },

    // 處理表頭點(diǎn)擊事件
    handleHeaderClick(event, column) {
      if (!column.filters && column.sortable) {  // 排序
        this.handleSortClick(event, column);
      } else if (column.filters && !column.sortable) {  // 篩選
        this.handleFilterClick(event, column);
      }

      this.$parent.$emit('header-click', column, event);
    },

    // 鼠標(biāo)按下
    handleMouseDown(event, column) {
      if (this.$isServer) return;
      // 如果這一列還有孩子直接返回碴里,應(yīng)該操作孩子的寬度
      if (column.children && column.children.length > 0) return;
      /* istanbul ignore if */
      // 如果有拖拽的列,并且有邊框
      if (this.draggingColumn && this.border) {
        // 表示正在拖動
        this.dragging = true;

        // 顯示 resize
        this.$parent.resizeProxyVisible = true;

        const tableEl = this.$parent.$el;
        const tableLeft = tableEl.getBoundingClientRect().left;
        // 拖動列
        const columnEl = this.$el.querySelector(`th.${column.id}`);
        const columnRect = columnEl.getBoundingClientRect();
        const minLeft = columnRect.left - tableLeft + 30;

        columnEl.classList.add('noclick');

        this.dragState = {
          startMouseLeft: event.clientX,  // 鼠標(biāo)開始位置
          startLeft: columnRect.right - tableLeft,  // 開始位置距離表格最左邊的距離
          startColumnLeft: columnRect.left - tableLeft,  // 開始時列左邊離表格最左邊的距離
          tableLeft
        };

        // 顯示拖拽位置的線
        const resizeProxy = this.$parent.$refs.resizeProxy;
        resizeProxy.style.left = this.dragState.startLeft + 'px';

        document.onselectstart = function() { return false; };
        document.ondragstart = function() { return false; };

        // 鼠標(biāo)移動的時候上真,計(jì)算移動距離并且移動輔助線
        const handleMouseMove = (event) => {
          const deltaLeft = event.clientX - this.dragState.startMouseLeft;
          const proxyLeft = this.dragState.startLeft + deltaLeft;

          resizeProxy.style.left = Math.max(minLeft, proxyLeft) + 'px';
        };

        // 鼠標(biāo)抬起
        const handleMouseUp = () => {
          if (this.dragging) {
            const finalLeft = parseInt(resizeProxy.style.left, 10);  // 最終停止的位置
            const columnWidth = finalLeft - this.dragState.startColumnLeft;  // 應(yīng)該變成的列寬
            column.width = column.realWidth = columnWidth;  // 應(yīng)用列寬改變

            this.store.scheduleLayout();  // 重新更新布局

            document.body.style.cursor = '';
            this.dragging = false;
            this.draggingColumn = null;
            this.dragState = {};

            this.$parent.resizeProxyVisible = false;
          }

          // 移除相應(yīng)的監(jiān)聽器
          document.removeEventListener('mousemove', handleMouseMove);
          document.removeEventListener('mouseup', handleMouseUp);
          document.onselectstart = null;
          document.ondragstart = null;

          setTimeout(function() {
            columnEl.classList.remove('noclick');
          }, 0);
        };

        document.addEventListener('mousemove', handleMouseMove);
        document.addEventListener('mouseup', handleMouseUp);
      }
    },

    // 鼠標(biāo)移動事件
    handleMouseMove(event, column) {
      if (column.children && column.children.length > 0) return;
      let target = event.target;
      while (target && target.tagName !== 'TH') {  // 尋找 th 標(biāo)簽
        target = target.parentNode;
      }

      // 如果沒有列咬腋,或者不能改變大小
      if (!column || !column.resizable) return;

      // 如果正在拖動并且有邊框
      if (!this.dragging && this.border) {
        let rect = target.getBoundingClientRect();

        const bodyStyle = document.body.style;
        if (rect.width > 12 && rect.right - event.pageX < 8) {
          bodyStyle.cursor = 'col-resize';
          this.draggingColumn = column;
        } else if (!this.dragging) {
          bodyStyle.cursor = '';
          this.draggingColumn = null;
        }
      }
    },

    // 鼠標(biāo)移除后
    handleMouseOut() {
      if (this.$isServer) return;
      document.body.style.cursor = '';
    },

    // 切換排序順序
    toggleOrder(order) {
      return !order ? 'ascending' : order === 'ascending' ? 'descending' : null;
    },

    // 點(diǎn)擊排序
    handleSortClick(event, column) {
      event.stopPropagation();
      // 切換排序順序
      let order = this.toggleOrder(column.order);

      // 尋找 TH
      let target = event.target;
      while (target && target.tagName !== 'TH') {
        target = target.parentNode;
      }

      // 如果這時候有 `noclick` 類,就移除
      if (target && target.tagName === 'TH') {
        if (target.classList.contains('noclick')) {
          target.classList.remove('noclick');
          return;
        }
      }

      // 如果不能排序就直接返回
      if (!column.sortable) return;

      const states = this.store.states;
      let sortProp = states.sortProp;
      let sortOrder;
      const sortingColumn = states.sortingColumn;

      // 如果排序列不是當(dāng)前列睡互,就切換成當(dāng)前列
      if (sortingColumn !== column) {
        if (sortingColumn) {
          sortingColumn.order = null;
        }
        states.sortingColumn = column;
        sortProp = column.property;
      }

      // 如果沒有順序
      if (!order) {
        sortOrder = column.order = null;
        states.sortingColumn = null;
        sortProp = null;
      } else {
        sortOrder = column.order = order;
      }

      states.sortProp = sortProp;
      states.sortOrder = sortOrder;

      this.store.commit('changeSortCondition');
    }
  },

  data() {
    return {
      draggingColumn: null,
      dragging: false,
      dragState: {}
    };
  }
};

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末根竿,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子湃缎,更是在濱河造成了極大的恐慌犀填,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,324評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件嗓违,死亡現(xiàn)場離奇詭異九巡,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)蹂季,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評論 3 392
  • 文/潘曉璐 我一進(jìn)店門冕广,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人偿洁,你說我怎么就攤上這事撒汉。” “怎么了涕滋?”我有些...
    開封第一講書人閱讀 162,328評論 0 353
  • 文/不壞的土叔 我叫張陵睬辐,是天一觀的道長。 經(jīng)常有香客問我宾肺,道長溯饵,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,147評論 1 292
  • 正文 為了忘掉前任锨用,我火速辦了婚禮丰刊,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘增拥。我一直安慰自己啄巧,他們只是感情好寻歧,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,160評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著秩仆,像睡著了一般码泛。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上澄耍,一...
    開封第一講書人閱讀 51,115評論 1 296
  • 那天弟晚,我揣著相機(jī)與錄音,去河邊找鬼逾苫。 笑死卿城,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的铅搓。 我是一名探鬼主播瑟押,決...
    沈念sama閱讀 40,025評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼星掰!你這毒婦竟也來了多望?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,867評論 0 274
  • 序言:老撾萬榮一對情侶失蹤氢烘,失蹤者是張志新(化名)和其女友劉穎怀偷,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體播玖,經(jīng)...
    沈念sama閱讀 45,307評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡椎工,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,528評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了蜀踏。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片维蒙。...
    茶點(diǎn)故事閱讀 39,688評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖果覆,靈堂內(nèi)的尸體忽然破棺而出颅痊,到底是詐尸還是另有隱情,我是刑警寧澤局待,帶...
    沈念sama閱讀 35,409評論 5 343
  • 正文 年R本政府宣布斑响,位于F島的核電站,受9級特大地震影響钳榨,放射性物質(zhì)發(fā)生泄漏舰罚。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,001評論 3 325
  • 文/蒙蒙 一重绷、第九天 我趴在偏房一處隱蔽的房頂上張望沸停。 院中可真熱鬧膜毁,春花似錦昭卓、人聲如沸愤钾。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽能颁。三九已至,卻和暖如春倒淫,著一層夾襖步出監(jiān)牢的瞬間伙菊,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評論 1 268
  • 我被黑心中介騙來泰國打工敌土, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留镜硕,地道東北人。 一個月前我還...
    沈念sama閱讀 47,685評論 2 368
  • 正文 我出身青樓返干,卻偏偏與公主長得像兴枯,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子矩欠,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,573評論 2 353

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

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,070評論 25 707
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫财剖、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,093評論 4 62
  • 前段時間組建了一個家長群癌淮,時常為大家做分享躺坟。可是后來我發(fā)現(xiàn)乳蓄,除了我在群里分享咪橙,所有的人都很少說話。我說了很多次讓大...
    艾秋閱讀 159評論 0 1
  • 石匠雕石圓虚倒, 磨房驢轉(zhuǎn)圈匣摘。 農(nóng)耕大社會, 先祖不簡單裹刮。
    草原騎手閱讀 251評論 0 0
  • 我現(xiàn)在算是一個重度患者吧音榜,也是這兩天覺出自己已經(jīng)是一個重度患者,會因此感到恐懼捧弃,但還是對自己那個渴望著什么的靈魂抱...
    小紅心安東尼閱讀 420評論 0 0