美文网首页Element非官方分析
Element分析(组件篇)——TableHeader

Element分析(组件篇)——TableHeader

作者: liril | 来源:发表于2017-02-17 17:44 被阅读8295次

    说明

    table-header是表头组件,较为复杂,直接看源码解读。

    源码解读

    import ElCheckbox from 'element-ui/packages/checkbox';
    import ElTag from 'element-ui/packages/tag';
    import Vue from 'vue';
    import FilterPanel from './filter-panel.vue';
    
    /**
     * 获取所有的列,因为会有嵌套所以使用了递归
     * @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;
    };
    
    /**
     * 将所有的列信息转换成表头的行信息
     * @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',
    
      // 渲染函数
      render(h) {
        // 原始列信息
        const originColumns = this.store.states.originColumns;
        // 表头信息
        const columnRows = convertToRows(originColumns, this.columns);
    
        return (
          // table 上的 style 是为了清除默认的间距
          <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 }
                  />)
              }
              {/* 如果左侧有固定还需要记录滚动条的宽度 */}
              {
                !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 }
                        // 鼠标移动事件
                        on-mousemove={ ($event) => this.handleMouseMove($event, column) }
                        // 鼠标移出事件
                        on-mouseout={ this.handleMouseOut }
                        // 鼠标按下事件
                        on-mousedown={ ($event) => this.handleMouseDown($event, column) }
                        // 鼠标单击事件
                        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' : ''
                            ]
                          }>
                        {
                          // 渲染单元格内部的内容
                          column.renderHeader
                            ? column.renderHeader.call(
                              this._renderProxy,
                              h,
                              {
                                column,
                                $index: cellIndex,
                                store: this.store,
                                _self: this.$parent.$vnode.context
                              })
                            : column.label
                        }
                        {
                          // 渲染排序的标志
                          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>
                    )
                  }
                  {
                    // 弥补滚动条的宽度
                    !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;
        },
    
        // 判断总的列数
        columnsCount() {
          return this.store.states.columns.length;
        },
    
        // 左侧固定列数
        leftFixedCount() {
          return this.store.states.fixedColumns.length;
        },
    
        // 右侧固定列数
        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: {
        // 判断单元格是否应当隐藏
        isCellHidden(index, columns) {
          // 左侧固定的 wrapper 中,那么除了固定的这些列都应该隐藏掉
          if (this.fixed === true || this.fixed === 'left') {
            return index >= this.leftFixedCount;
          } else if (this.fixed === 'right') {  // 右侧固定的 wrapper 中,规定列之前的也都应当隐藏
            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');
        },
    
        // 处理点击筛选器,应当显示对应的筛选面板
        handleFilterClick(event, column) {
          event.stopPropagation();
          const target = event.target;
          const cell = target.parentNode;
          const table = this.$parent;
    
          // 查找对应的筛选面板
          let filterPanel = this.filterPanels[column.id];
    
          // 如果存在,并且打开了,就关闭它
          if (filterPanel && column.filterOpened) {
            filterPanel.showPopper = false;
            return;
          }
    
          // 如果不存在,就创建它
          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'));
          }
    
          // 创建后打开
          setTimeout(() => {
            filterPanel.showPopper = true;
          }, 16);
        },
    
        // 处理表头点击事件
        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);
        },
    
        // 鼠标按下
        handleMouseDown(event, column) {
          if (this.$isServer) return;
          // 如果这一列还有孩子直接返回,应该操作孩子的宽度
          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,  // 鼠标开始位置
              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; };
    
            // 鼠标移动的时候,计算移动距离并且移动辅助线
            const handleMouseMove = (event) => {
              const deltaLeft = event.clientX - this.dragState.startMouseLeft;
              const proxyLeft = this.dragState.startLeft + deltaLeft;
    
              resizeProxy.style.left = Math.max(minLeft, proxyLeft) + 'px';
            };
    
            // 鼠标抬起
            const handleMouseUp = () => {
              if (this.dragging) {
                const finalLeft = parseInt(resizeProxy.style.left, 10);  // 最终停止的位置
                const columnWidth = finalLeft - this.dragState.startColumnLeft;  // 应该变成的列宽
                column.width = column.realWidth = columnWidth;  // 应用列宽改变
    
                this.store.scheduleLayout();  // 重新更新布局
    
                document.body.style.cursor = '';
                this.dragging = false;
                this.draggingColumn = null;
                this.dragState = {};
    
                this.$parent.resizeProxyVisible = false;
              }
    
              // 移除相应的监听器
              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);
          }
        },
    
        // 鼠标移动事件
        handleMouseMove(event, column) {
          if (column.children && column.children.length > 0) return;
          let target = event.target;
          while (target && target.tagName !== 'TH') {  // 寻找 th 标签
            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;
            }
          }
        },
    
        // 鼠标移除后
        handleMouseOut() {
          if (this.$isServer) return;
          document.body.style.cursor = '';
        },
    
        // 切换排序顺序
        toggleOrder(order) {
          return !order ? 'ascending' : order === 'ascending' ? 'descending' : null;
        },
    
        // 点击排序
        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;
    
          // 如果排序列不是当前列,就切换成当前列
          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: {}
        };
      }
    };
    
    

    相关文章

      网友评论

        本文标题:Element分析(组件篇)——TableHeader

        本文链接:https://www.haomeiwen.com/subject/bpplwttx.html