preface

This is an article that has been updated for a long time. Taking advantage of this grain rain term to overcome my laziness, I want to write my understanding of the cell combination components. On the one hand, I want to summarize them better and on the other hand, I want to share them with you. If there is any incorrect place, welcome to point out thank you!


Use the table tag to complete cell merging

Tables are not so common in business, and the native Table tag can quickly help us implement a table

<table class="raw-table">
  <tr>
    <th>Month</th>
    <th>front-end</th>
    <th>back-end</th>
    <th>Savings</th>
  </tr>
  <tr>
    <td>January</td>
    <td>Express</td>
    <td>Express</td>
    <td>The $100</td>
  </tr>
  <tr>
    <td>February</td>
    <td>Vue</td>
    <td>Java</td>
    <td>The $200</td>
  </tr>
   <tr>
    <td>March</td>
    <td>React</td>
    <td>Koa</td>
    <td>The $200</td>
  </tr>
  <tr>
    <td>April</td>
    <td>React</td>
    <td>Egg</td>
    <td>The $200</td>
  </tr>
</table>
Copy the code

Of course, the resulting table is a bit ugly, so it needs some modification. We can add class to the table

.raw-table {
    width: 100%;
    margin-bottom: 10px;
    table-layout: fixed;
    border: 1px solid #dee2e6;
    border-bottom: none;
    border-spacing: 0;
}

td.th {
    color: # 333;
    font-size: 14px;
    word-wrap: break-word;
    padding: 12px;
    vertical-align: middle;
    border-bottom: 1px solid #ccc;
    border-right: 1px solid #dee2e6;
    text-align: center;
}
Copy the code

We find that some cells with the same data appear, and if we can combine them, the data will look more intuitive so we need to use the attributes colSPAN and RowSPAN for column merge and row merge respectively

<table class="raw-table">
  <tr>
    <th>Month</th>
    <th>front-end</th>
    <th>back-end</th>
    <th>Savings</th>
  </tr>
  <tr>
    <td>January</td>
+   <td colspan="2">Express</td>
-
    <td>The $100</td>
  </tr>
  <tr>
    <td>February</td>
    <td>Vue</td>
    <td>Java</td>
+   <td rowspan="3">The $200</td>
  </tr>
   <tr>
    <td>March</td>
    <td>React</td>
    <td>Koa</td>
-
  </tr>
  <tr>
    <td>April</td>
    <td>React</td>
    <td>Egg</td>
-
  </tr>
</table>
Copy the code

The final effect is as follows

The problem is that we set the specified data and then set the line and column span. We must have used this cell merge component more than once in our business development, and it would have been silly and cumbersome to change it every time, so the idea of dynamic cell merge came up. Next, develop a dynamic cell merge component based on the above ideas.

Dynamic cell merging

Dynamic cell merging, as the name implies, is that data is dynamic, so the general idea is

  • First, the general idea is to compare rows and columns of data in pairs by walking through them
  • Define data data that is used to compare the relationship between each data and whether the necessary merge is performed
const tableData = [
    { month: 'January'.frontEnd: 'Express'.backEnd: 'Express'.savings: '$100'},
    { month: 'February'.frontEnd: 'Vue'.backEnd: 'Java'.savings: '$100'},
    { month: 'March'.frontEnd: 'React'.backEnd: 'Koa'.savings: '$100'},
    { month: 'April'.frontEnd: 'React'.backEnd: 'Egg'.savings: '$100'}]Copy the code
  • Define columns data, used to represent the header, prop as the attribute key, through which to map tableData, label as the attribute name
const columns = [{
  prop: 'month'.label: 'Month'.align: 'center'
},
{
  prop: 'frontEnd'.label: 'Front-end'.align: 'center'
},
{
  prop: 'backEnd'.label: 'Back-end'.align: 'center'
},
{
  prop: 'savings'.label: 'Savings'.align: 'center'
}]
Copy the code
  • definerowSpanInstandrowPosObject,rowSpanInstTo store the data status of each column,rowPosThe data structure at the end of a record anchor point for the current operation row, obtained by comparing the data status of two adjacent rows in each column, if the samecolPosAdd the current row data and set the current row data to 0. Otherwise, set the current row data to 1 and updaterowPosTo the current row, the last data form
rowPos = {
    month: 3.frontEnd: 3.backEnd: 3.savings: 0
}
rowSpanInst = {
    month: [1.1.1.1].frontEnd: [1.1.1.1].backEnd: [1.1.1.1].savings: [3.0.0.0]}Copy the code

Assuming the current column is in month, illustrate the idea briefly with an animation

Walk through the code for each column for the last time

// Compare the two items before and after each column
getRowSpanArr(columnName) {
  const Len = this.tableData.length;
  // Initialize the current column to store data
  if (this.rowSpanInst[columnName] == null) {
    this.rowSpanInst[columnName] = [];
  }
  // initialize the current anchor point to 0
  if (this.rowPos[columnName] == null) {
    this.rowPos[columnName] = 0;
  }
  for (let i = 0; i < Len; i++) {
    if (i === 0) {
      this.rowSpanInst[columnName].push(1);
    } else {
      const array1 = this.tableData[i - 1];
      const array2 = this.tableData[i];
      // Determine whether the current element is the same as the previous one
      if (
        array1[columnName] &&
        array2[columnName] &&
        array1[columnName] === array2[columnName]
      ) {
        this.rowSpanInst[columnName][this.rowPos[columnName]] += 1;
        this.rowSpanInst[columnName].push(0);
      } else {
        this.rowSpanInst[columnName].push(1);
        this.rowPos[columnName] = i; }}}}Copy the code
  • definecolSpanInstandcolPosObject,colSpanInstTo store each row of data,colPosThe data structure at the end of a record anchor for the current operation row, obtained by comparing the data condition of two adjacent columns in each row, if the samecolPosThe current row data is set to 0. Otherwise, set the current row data to 1 and updatecolPosTo the current column, the last data form
colPos = [
    'savings'.'savings'.'savings'.'savings'
]
colSpanInst = [
    {
        month: 1.frontEnd: 2.backEnd: 0.savings: 1
    },
    {
        month: 1.frontEnd: 1.backEnd: 1.savings: 1
    },
    {
        month: 1.frontEnd: 1.backEnd: 1.savings: 1
    },
    {
        month: 1.frontEnd: 1.backEnd: 1.savings: 1}]Copy the code

Again, just like traversing a column, set the current row and animate it briefly

The final code logic is as follows

// Compare the columns in each row
getColSpanArr() {
  const Len = this.tableData.length;
  const columnsArray = this.columns.map(item= > item.prop);
  for (let i = 0; i < Len; i++) {
    // Initializes the current row to store data
    if (this.colSpanInst[i] == null) {
      this.colSpanInst[i] = {};
    }
    // Initialize the current column anchor to the current column name
    if (this.colPos[i] == null) {
      this.colPos[i] = columnsArray[i];
    }
    // Current row data
    const currentRow = this.tableData[i];
    // Start with the second column and compare with the previous column one by one
    for (let j = 0; j < columnsArray.length; j++) {
      if (j === 0) {
        this.colSpanInst[i][columnsArray[j]] = 1;
      } else {
        const array1 = currentRow[columnsArray[j - 1]].const array2 = currentRow[columnsArray[j]];
        // Determine whether the current element is the same as the previous one
        if (array1 && array2 && array1 === array2) {
          this.colSpanInst[i][this.colPos[i]] += 1;
          this.colSpanInst[i][columnsArray[j]] = 0;
        } else {
          this.colSpanInst[i][columnsArray[j]] = 1;
          this.colPos[i] = columnsArray[j];
        }
      }
    }
  }
}
Copy the code

Finally add render function logic, the corresponding merge logic on TD basic form has come out, it is worth noting that

  • There is a case where rows and columns merge the same cell at the same time. Since row merging is judged first, it is necessary to obtain the current cell row merging when columns merge. This. RowSpanInst [columnsArray[j-1]][I] === = 1 and this. RowSpanInst [columnsArray[j]][I] === = 1 Columns remain independent cells without merging

  • Rowspan =0 or colspan=0 will still be displayed due to dynamic render, so display: None is required

  • Perhaps the outside world wants to wrap the data in additional ways, so you can set the Render method in column to call back the current row data to the outside world via an internal call

render(h) {
    return (
      <table class="raw-table">
        <thead>
          <tr>
            {this.columns.map(column => (
              <th>
                {column.label}
              </th>
            ))}
          </tr>
        </thead>
        {this.tableData.map((data, index) => {
          return (
            <tr key={data.prop}>
              {this.columns.map(column => {
                return (
                  <td
                    style={{
                      'text-align': column.align| | 'center',
                      'vertical-align': 'middle'}}rowspan={this.rowSpanInst[column.prop][index]}
                    colspan={this.colSpanInst[index][column.prop]}
+                   class={{
+                     hide:
+                       this.rowSpanInst[column.prop] [index= = =0 ||
+                       this.colSpanInst[index] [column.prop= = =0
                    }}
                  >
+                   {column.render
+                     ? column.render(h, { row: data })
+                     : data[column.prop]}
                  </td>
                );
              })}
            </tr>
          );
        })}
      </table>
    );
  }
Copy the code

conclusion

The above simply encapsulates the merged cell components, or based on the original element attributes through certain methods to achieve, if there is a better way to welcome to inform and advise. Of course, there are many more complex features to be refined and explored

  • Pass data to ColSPAN or RowSPAN, without directly comparing data, directly merging
  • Cells are automatically merged by drag and drop

The source code

merge-table