The last article looked at the Store part of the Table component, but today we’ll look at what makes up the body

Review the structure of table.vue:

/ / hide the column<div class="hidden-columns" ref="hiddenColumns"><slot></slot></div>// Table header<div class="el-table__header-wrapper"><table-header></table-header></div>// The body part<div class="el-table__body-wrapper"><table-body></table-body></div>// placeholder block, render when there is no data<div class="el-table__empty-block"></div>// Insert content after the last row of the table<div class="el-table__append-wrapper"><slot="append"></slot></div>// End of table<div class="el-table__footer-wrapper"><table-foot></table-foot></div>// Set the left part of the column<div class="el-table__fixed"></div>// Set the right side of the column<div class="el-table__fixed-right"></div>// Fixed part of the right column patch (scroll bar)<div class="el-table__fixed-right-patch"></div>// Proxy for column width adjustment<div class="el-table__column-resize-proxy"></div>
Copy the code

table-header

Table-header is a complex and core part of a table: Take a look at the template, which uses JSX syntax and provides a render function:

render(h) {
    const originColumns = this.store.states.originColumns;
    const columnRows = convertToRows(originColumns, this.columns);
    // Whether to have a multilevel header
    const isGroup = columnRows.length > 1;
    if (isGroup) this.$parent.isGroup = true;
    return(...).Copy the code

The original columns are stored in the store. Find store source code is not difficult to find:

Originedcolumns = fixedColumns + notFixedColumns + rightFixedColumns originedColumns = notFixedColumns + rightFixedColumns ConvertToRows needs to be called to convert all column information to row information of the table header, including hierarchy, colSPAN and other details, and determine whether there are multiple table headers based on the converted columnRows.

Here is the rendering, simplified as follows:

 <table>
     <colgroup></colgroup>
     <thead>
        <tr>
            <th>
                <div>.</div>
            </th>
        <tr>
     </thead>
 </table>
Copy the code

As you can see, table-head is actually a separate table

<colgroup>
    {
        this.columns.map(column => <col name={ column.id } key={column.id} />)
    }
    {
        this.hasGutter ? <col name="gutter" />: "'}</colgroup>
Copy the code

Colgroup returns a list of col columns from the column traversal. In the hasGutter, if there is no fixed column, add one more column to adjust the width. Otherwise, it will cause the head and body of the table to slip

hasGutter() {
    return !this.fixed && this.tableLayout.gutterWidth;
}
Copy the code

Moving on to thead, it’s a bit more informative:

 <thead class={[{'is-group': isGroup, 'has-gutter': this.hasGutter }] }>
    {
        this._l(columnRows, (columns, rowIndex) =>
            <tr
                style={ this.getHeaderRowStyle(rowIndex)}class={ this.getHeaderRowClass(rowIndex)} >
            {
                columns.map((column, cellIndex) = (<th>.</th>)
            }
            {
                this.hasGutter ? <th class="gutter"></th>: "'}</tr>)}</thead>
Copy the code

IsGroup says above whether there are multiple headers. RenderList (renderList) {renderList (renderList) {renderList (renderList) {renderList (renderList) {renderList (renderList) {renderList (renderList) {renderList (renderList)}} GetHeaderRowStyle and getHeaderRowClass are two exposed props that allow users to customize styles and classes.

Let’s take a look at the render part of column, there are more, directly look at the source code

columns.map((column, cellIndex) => (<th// Calculated in advancecolSpanandrowSpan
    colspan={ column.colSpan }
    rowspan={ column.rowSpan} // Handle mouse eventson-mousemove={ ($event) = > this.handleMouseMove($event, column) }
    on-mouseout={ this.handleMouseOut }
    on-mousedown={ ($event) => this.handleMouseDown($event, column) }
    on-click={ ($event) => this.handleHeaderClick($event, column) }
    on-contextmenu={ ($event) => this.handleHeaderContextMenu($event, column) }
    // 处理th的样式
    style={ this.getHeaderCellStyle(rowIndex, cellIndex, columns, column) }
    class={ this.getHeaderCellClass(rowIndex, cellIndex, columns, column) }
    key={ column.id }>
        <div class={ ['cell', column.filteredValue && column.filteredValue.length >0? 'highlight' : '', column.labelClassName] }> { column.renderHeader ? column.renderHeader.call(this._renderProxy, h, { column, $index: cellIndex, store: this.store, _self: This.$parent.$vnode.context}) : column.label} {// Whether to allow sorting, if allowed sorting icon and binding event column.sortable? (<span
                    class="caret-wrapper"
                    on-click={ ($event) = > this.handleSortClick($event, column) }>
                    <i class="sort-caret ascending"
                        on-click={ ($event) = > this.handleSortClick($event, column, 'ascending') }>
                    </i>
                    <i class="sort-caret descending"
                        on-click={ ($event) = > this.handleSortClick($event, column, 'descending') }>
                    </i>
                </span>) : "} {// Whether filtering is allowed, if filtering ICONS and binding events are allowed, 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>)}Copy the code

Note here:

column.renderHeader ? column.renderHeader.call(this._renderProxy, h, { column, $index: cellIndex, store: this.store, _self: this.$parent.$vnode.context }): column.label
Copy the code

The renderHeader is actually a rendering function, passed in from the outside, that performs custom header rendering. If not, display the label directly

To finish the template analysis here, take a look at the other attributes:

 computed: {
    // Return the el-table instance
    table() {
      return this.$parent;
    },
    // If there is a scrollbar, the width patch is needed
    hasGutter() {
      return !this.fixed && this.tableLayout.gutterWidth;
    },
    // Some common global variables. mapStates({columns: 'columns'.isAllSelected: 'isAllSelected'.leftFixedLeafCount: 'fixedLeafColumnsLength'.rightFixedLeafCount: 'rightFixedLeafColumnsLength'.columnsCount: states= > states.columns.length,
      leftFixedCount: states= > states.fixedColumns.length,
      rightFixedCount: states= > states.rightFixedColumns.length
    })
  },
Copy the code
methods: {
    // Determine whether the current cell needs to be hidden based on colSPAN and the fixed number of columns
    isCellHidden(index, columns) {}
    // Call an external method with the same name
    getHeaderRowStyle(rowIndex) {}
    // Call an external method with the same name
    getHeaderRowClass(rowIndex) {}
    // Call an external method with the same name
    getHeaderCellStyle(rowIndex, columnIndex, row, column) {}
    // Compute a class list based on whether to hide, sort, etc
    getHeaderCellClass(rowIndex, columnIndex, row, column) {}
    // Switch to select all
    toggleAllSelection(event) {}
    // Process the filter click event, the filter panel appears
    handleFilterClick(event, column) {}
    // Handle the header click event, check whether it is sort or filter
    handleHeaderClick(event, column) {}
    // Handles contextMenu events, which are thrown directly to the user
    handleHeaderContextMenu(event, column) {}
    // The event of the mouse-down is mainly handled by the drag-width related logic
    handleMouseDown(event, column) {}
    // Mousemove handles drag logic
    handleMouseMove(event, column) {}
    // The mouseout event is mainly out of focus
    handleMouseOut() {}
}
Copy the code

HandleMouseDown implements header dragging.

handleMouseDown(event, column) {
    // Server render returns directly
    if (this.$isServer) return;
    // If the header is nested, it cannot be dragged (only children can be dragged)
    if (column.children && column.children.length > 0) return;
    /* istanbul ignore if */
    // If drag width is allowed and there is a border
    if (this.draggingColumn && this.border) {
        // Drag the flag bit
        this.dragging = true;
        // It is used to control whether resizeProxy is displayed on the table
        this.$parent.resizeProxyVisible = true;
        
        
        // Calculate table Vue instance, DOM instance, left offset
        const table = this.$parent;
        const tableEl = table.$el;
        const tableLeft = tableEl.getBoundingClientRect().left;
        // Find the element being dragged
        const columnEl = this.$el.querySelector(`th.${column.id}`);
        const columnRect = columnEl.getBoundingClientRect();
        // To calculate the minimum remaining width, reserve at least 30px
        const minLeft = columnRect.left - tableLeft + 30;
        
        addClass(columnEl, 'noclick');
        
        // Define a temporary state
        this.dragState = {
            // Start x
            startMouseLeft: event.clientX,
            // The amount left to the right of the starting element
            startLeft: columnRect.right - tableLeft,
            // The amount left of the starting element
            startColumnLeft: columnRect.left - tableLeft,
            tableLeft
        };
        
        ResizeProxy is a display of a dragged element that dynamically changes its position
        const resizeProxy = table.$refs.resizeProxy;
        resizeProxy.style.left = this.dragState.startLeft + 'px';
        
        document.onselectstart = function() { return false; };
        document.ondragstart = function() { return false; };
    
        const handleMouseMove = (event) = > {
            // deltaLeft drags the offset from the current mouse position to the start mouse position
            const deltaLeft = event.clientX - this.dragState.startMouseLeft;
            // The distance to move
            const proxyLeft = this.dragState.startLeft + deltaLeft;
            // Change the resizeProxy location
            resizeProxy.style.left = Math.max(minLeft, proxyLeft) + 'px';
        };
    
        const handleMouseUp = (a)= > {
            if (this.dragging) {
                // Get the initial state
                const {
                    startColumnLeft,
                    startLeft
                } = this.dragState;
                // Calculate the final mouse position and convert it to an integer
                const finalLeft = parseInt(resizeProxy.style.left, 10);
                // The width of the final drag
                const columnWidth = finalLeft - startColumnLeft;
                // Change the width of column
                column.width = column.realWidth = columnWidth;
                table.$emit('header-dragend', column.width, startLeft - startColumnLeft, column, event);
                // *** triggers a page relayout to update the DOM ***, which completes the table body relayout
                this.store.scheduleLayout();

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

                table.resizeProxyVisible = false;
            }
            // Remove the event binding
            document.removeEventListener('mousemove', handleMouseMove);
            document.removeEventListener('mouseup', handleMouseUp);
            document.onselectstart = null;
            document.ondragstart = null;

          setTimeout(function() {
            removeClass(columnEl, 'noclick');
          }, 0);
        };
        
        // Bind events
        document.addEventListener('mousemove', handleMouseMove);
        document.addEventListener('mouseup', handleMouseUp); }},Copy the code