HOOKS

Public Hooks package custom table data column rendering useTableColumns

Custom Table Column Rendering Hook: useTableColumns

Foreword

For front-end developers who frequently build enterprise management backends, using tables to handle data is essential. When optimizing the code of existing projects, it’s a good practice to encapsulate some reusable hooks. This article is for ‘useTableColumns.js’.

Hooks encapsulated based on a personal project environment; this article only shares insights and best practices for hook encapsulation, so the associated code may not be suitable for others.

Project Environment

Vue 3.x + Ant Design Vue 3.x + Vite 3.x

Encapsulation thinking: what is custom table data column rendering and what kind of scene it serves

Based on the actual business scenario, to mitigate legal risks, certain content in the screenshots has been anonymized.

As shown in the following figure, when there are many columns in the table content, under normal circumstances, we usually adopt the method of columns on the left and right sides, that is, the key column on the left and the operation column on the right are fixed, and the content area in the middle column is scrolled. However, when there are a lot of data columns and each column of data has its true meaning, we should consider adopting a better way. For different system users, everyone usually has different concerns. Therefore, everyone can take the initiative to change the table column items to show the data columns of concern.

! tableColumnsDemo

Combined with the above scenario and optimization considerations, the following personal solution is obtained, the encapsulation of ‘useTableColumns’

Encapsulation Decomposition: Main Ideas Columns

Combined with the actual UI framework’ Ant Design Vue’, the Column column describes the data object and is one of the Columns. Column uses the same API. Common data structures include the following:

  columns: [
    {
      title: '姓名',
      dataIndex: 'name',
      key: 'name',
    },
    {
      title: '年龄',
      dataIndex: 'age',
      key: 'age',
    },
    {
      title: '住址',
      dataIndex: 'address',
      key: 'address',
    },
  ],

In the above data structure, the table header, data index and other relevant information corresponding to each column of data in the table are pointed out. In addition, there are a series of API provided in the official document. Combined with the screenshots already provided above, the idea is actually obvious, just dynamically changing this JSON data. In actual use, add the custom attribute ‘addFilter’ to the data column that needs to participate in the change ’.

Package decomposition: drop-down menu-filter display data column component

<template #headerCell="{ column }">
  <template v-if="column.key === 'action'">
    <div class="flex justify-between items-center">
      <span>操作</span>
      <table-field-filter :columns="columns" @change="onFilterColumnChange" />
    </div>
  </template>
</template>

the above code is based on the actual project UI FrameWork – ‘ant design vue’. similarly, you can also look at other frameworks, such as the relevant API or writing method provided by the’ table’ component of’ element ui.

In the project, I chose to place’ onFilterColumnChange’ on the header of the table operation column. in actual use, it can also be placed in other areas, such as screening modules, titles, etc. it depends on how to design it, without damaging the existing layout or requiring UI designers to design it a little ~

Package decomposition: Columns dynamic change

let includeColumns = []; // 参与过滤的列表字段集合

let excludeColumns = []; // 不参与过滤的列表字段集合

const columns = shallowRef([]); // 需要展示的列表字段集合

if (options?.initFilterKeys.length > 0) {
  for (let i = 0, len = options.initFilterKeys.length; i < len; i++) {
    const key = options.initFilterKeys[i];
    const index = $columns.findIndex(item => item.key === key);
    $columns.splice(index, 1);
  }
}

The current implementation is to put the table columns that participate in the filtering into one array, and then put the items that do not participate in the filtering into another array, and then merge them. In combination with the actual business, there may be no corresponding data for some special scenarios and they do not participate in display control. For example, in the enterprise background-enterprise WeChat/or other similar ecological account login systems, account control is actually unnecessary (or due to policy restrictions and other reasons, consider blocking the account display in the form). Of course, this is in combination with the actual business and will not be repeated here ~

Package decomposition: utils – getArrayDiff

/**
 * @description 筛选两个数组不同的元素
 */
export function getArrayDiff(arr1, arr2) {
  return arr1.concat(arr2).filter((item, index, arr) => arr.indexOf(item) === arr.lastIndexOf(item));
}

Complete code for useTableColumns.js


export function useTableColumns(defaultColumns, options) {
  if (!defaultColumns || !Array.isArray(defaultColumns)) return;

  let includeColumns = []; // 参与过滤的列表字段集合

  let excludeColumns = []; // 不参与过滤的列表字段集合

  const columns = shallowRef([]); // 需要展示的列表字段集合

  const onFilterColumnChange = keys => {
    const orKeys = includeColumns.map(item => item.key);
    const delKeys = getArrayDiff(orKeys, keys);
    const $columns = cloneDeep(includeColumns);
    for (let i = 0, len = delKeys.length; i < len; i++) {
      const key = delKeys[i];
      const index = $columns.findIndex(item => item.key === key);
      $columns.splice(index, 1);
    }
    columns.value = [...$columns, ...excludeColumns];
  };

  // 初始化字段处理
  const initColumns = () => {
    const $columns = cloneDeep(defaultColumns);
    if (options?.initFilterKeys.length > 0) {
      for (let i = 0, len = options.initFilterKeys.length; i < len; i++) {
        const key = options.initFilterKeys[i];
        const index = $columns.findIndex(item => item.key === key);
        $columns.splice(index, 1);
      }
    }
    for (let i = 0, len = $columns.length; i < len; i++) {
      const item = $columns[i];
      if (item.addFilter) {
        includeColumns.push(item);
      } else {
        excludeColumns.push(item);
      }
    }
    columns.value = $columns;
  };

  initColumns();

  return {
    columns,
    onFilterColumnChange,
  };
}

Follow-up thinking: Is there a problem with the current package? How to optimize it?

For Columns processing in Hooks, two arrays are used to temporarily store table columns that need to be filtered and table columns that do not need to be filtered respectively. Since only one page “As shown in the screenshot” in the actual project needs to process the above operations, for table column data, the ‘addFilter’ custom attribute is added except for the operation column. However, in actual business requirements, the following situations may occur in table fields:
Only a brief explanation

[字段A, 字段B, 字段C, 字段D, 字段E, 字段F, 字段G, 操作列 ]

If you add the’ addFilter’ custom attribute to all except the operation columns, it is the same as the method I am currently using, and there is no problem.
If the above fields (field a, field c, field g) do not participate in the filtering, and the rest of the fields participate in the filtering, the currently encapsulated Hooks do not meet the requirements, because according to the existing writing method, the columns array will become
after going through the’ onFilterChange’

[字段A, 字段C, 字段G, 字段B, 字段E, 字段F, 操作列 ]

In this case, for the original table, the original index order is broken, which only satisfies the filtering, but it is not the original location “delete/hide” filtering. For actual business, it may not be satisfied ~
How to solve it? Currently, due to the demand iteration, there is no time to deal with it ~ To be optimized later ~
Of course, I also expect readers to provide solutions or optimize the code together ~

Related posts

View all →