HOOKS

Public Hooks encapsulated table selection operation useTableRowSelection

Table Selection Operation Hook: useTableRowSelection

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 ‘useTableRowSelection.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 Considerations: Why encapsulate useTableRowSelection.js?

First of all, based on the encapsulation of Hooks(‘useTableData.js’,’ useQueryParams.js’), it is related to batch deletion and batch editing of common operations of managing background tables. packaging’ useTableRowSelection.js’ can be encapsulated and used, and can be processed only in the page table component ‘:row-selection = “rowSelection”’, instead of writing duplicate code of table selection in each single page file. Additionally, in the official component documentation for ‘Ant Design Vue’ or ‘Ant Design’, you can find a selection example for the Table component: after pagination is performed, the previously selected data is cleared. Well, now the official team can finally provide this feature—tears~ PS: At least August 2022 definitely does not have this ability, because my Hooks was encapsulated at that time, and only recently did I start writing articles again ~

Encapsulation and Decomposition: Table Multi-Selection

Since the official documentation did not provide support for cross-page multiple selection when these Hooks were initially encapsulated, and since this capability is now available, the relevant code will only illustrate the encapsulation approach. When multiple selections are made, the table that requires cross-page selection caches the selected data. The checkbox needs to account for both individual selection and the “Select All” functionality (applied to the header of each paginated table). The selected ‘selectKeys’ and ‘selectItems’ are processed and exposed, and then provided to the page that needs to be selected for table selection for data processing, including but not limited to the parameter transmission or echo required by the request service interface.

//  选择
onSelect: (record, selected) => {
  if (selected) {
    selectKeys.value.push(record[rowId]);
    if ($cacheItem) selectItems.value = [...selectItems.value, ...[record]];
  } else {
    const index = selectKeys.value.findIndex(key => key === record[rowId]);
    if (index >= 0) {
      selectKeys.value.splice(index, 1);
      if ($cacheItem) {
        const $cacheSelectItems = selectItems.value;
        $cacheSelectItems.splice(index, 1);
        selectItems.value = [...$cacheSelectItems];
      }
    }
  }
},
  
// 全选
onSelectAll: selected => {
  if (selected) {
    tableData.value.forEach(item => {
      const index = selectKeys.value.findIndex(id => item[rowId] === id);
      if (index < 0) {
        selectKeys.value.push(item[rowId]);
        if ($cacheItem) selectItems.value = [...selectItems.value, ...[item]];
      }
    });
  } else {
    tableData.value.forEach(item => {
      const index = selectKeys.value.findIndex(key => key === item[rowId]);
      if (index >= 0) {
        selectKeys.value.splice(index, 1);
        if ($cacheItem) {
          const $cacheSelectItems = selectItems.value;
          $cacheSelectItems.splice(index, 1);
          selectItems.value = [...$cacheSelectItems];
        }
      }
    });
  }
},

Encapsulated Thinking: Complete Table Selection Hooks

Initially, this was done to align with the usage of table-related hooks such as ‘useTableData’ and ‘useQueryParams’, with the aim of reducing duplicate code within the project. A complete ‘useTableRowSelection’ implementation should account for both multiple and single row selection in the table. although multi-selection can realize the function of single selection , the UI presentation mode of multi-selection and single selection in the page is still different ~, so Hooks receives table source data’ tableData’, filter type’ selectType’, unique key’ rowId’ and whether caching is enabled (I .e. cross-page filtering, after all, not all tables need cross-page filtering) ‘cacheItem = false’. Here, the encapsulated Hooks are slightly different from the capabilities provided by the existing authorities. The screening of tables in official documents is cross-page selection, considering actual business (of course, only individual companies are involved in business scenarios, which may not be universal), and only when it is necessary to select personnel to participate in an actual business project or adjust the relevant visible range.

Encapsulation Decomposition: Expose clearKeys and isEmptyKeys

Expose the clearKeys method to clear table filters, and expose the isEmptyKeys method to determine whether the table filters are empty.

Complete code for useTableRowSelection.js


export function useTableRowSelection(tableData, selectType, rowId, cacheItem = false) {
  if (!isRef(tableData)) throw new Error('参数 tableData 必须为 Ref 类型');

  const selectKeys = ref([]);

  const selectItems = shallowRef([]);

  const rowSelection = computed(() => {
    const $selectType = isRef(selectType) ? selectType.value : selectType;
    const $cacheItem = isRef(cacheItem) ? selectType.value : cacheItem;
    if ($selectType === ROW_SELECT_TYPE.CHECKBOX) {
      return {
        onSelect: (record, selected) => {
          if (selected) {
            selectKeys.value.push(record[rowId]);
            if ($cacheItem) selectItems.value = [...selectItems.value, ...[record]];
          } else {
            const index = selectKeys.value.findIndex(key => key === record[rowId]);
            if (index >= 0) {
              selectKeys.value.splice(index, 1);
              if ($cacheItem) {
                const $cacheSelectItems = selectItems.value;
                $cacheSelectItems.splice(index, 1);
                selectItems.value = [...$cacheSelectItems];
              }
            }
          }
        },
        onSelectAll: selected => {
          if (selected) {
            tableData.value.forEach(item => {
              const index = selectKeys.value.findIndex(id => item[rowId] === id);
              if (index < 0) {
                selectKeys.value.push(item[rowId]);
                if ($cacheItem) selectItems.value = [...selectItems.value, ...[item]];
              }
            });
          } else {
            tableData.value.forEach(item => {
              const index = selectKeys.value.findIndex(key => key === item[rowId]);
              if (index >= 0) {
                selectKeys.value.splice(index, 1);
                if ($cacheItem) {
                  const $cacheSelectItems = selectItems.value;
                  $cacheSelectItems.splice(index, 1);
                  selectItems.value = [...$cacheSelectItems];
                }
              }
            });
          }
        },
        getCheckboxProps: record => {
          return {
            disabled: record.disabled,
          };
        },
        selectedRowKeys: selectKeys.value,
        type: ROW_SELECT_TYPE.CHECKBOX,
      };
    } else {
      return {
        onSelect: record => {
          selectKeys.value = [record[rowId]];
          if ($cacheItem) selectItems.value = [record];
        },
        selectedRowKeys: selectKeys.value,
        getCheckboxProps: record => {
          return {
            disabled: record.disabled,
          };
        },
        type: ROW_SELECT_TYPE.RADIO,
      };
    }
  });

  const clearKeys = () => {
    selectKeys.value.length = 0;
    selectItems.value = [];
  };

  const isEmptyKeys = () => {
    return selectKeys.value.length === 0;
  };

  return {
    selectItems,
    selectKeys,
    rowSelection,
    clearKeys,
    isEmptyKeys,
  };
}

Actual Use: Reference Example

-Page component ‘Page.vue’

<a-table
  size="small"
  class="mt-2"
  row-key="id"
  :data-source="tableData"
  :loading="loading"
  :columns="columns"
  :row-selection="rowSelection"
  @change="onTableChange"
/>

-Specific usage

  const { rowSelection, selectKeys, clearKeys, isEmptyKeys } = useTableRowSelection(
    tableData,
    ROW_SELECT_TYPE.CHECKBOX,
    'id',
  );

  const confirmSelect = () => {
    if (isEmptyKeys()) { // 确认按钮,业务场景,表格筛选项非空校验
      proxy.$message.warning('请选择****');
      return;
    }
    const selectItem = tableData.value.find(item => item.id === selectKeys.value[0]);
    emits('on-select', {
      id: selectItem.id,
    });
    closeModal();
  };
  
  const closeModal = () => {
    clearKeys();
    resetParams();  // 还记得这个吗~`useQueryParams.js`Hooks内提供的方法~
  };

Related posts

View all →