Skip to content

Commit

Permalink
feat(comp: table): support multiple sort (#917)
Browse files Browse the repository at this point in the history
fix #915
  • Loading branch information
danranVm committed May 19, 2022
1 parent 25a2aac commit a407920
Show file tree
Hide file tree
Showing 13 changed files with 261 additions and 79 deletions.
2 changes: 1 addition & 1 deletion packages/components/table/demo/Bordered.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<IxHeader title="Table Header" suffix="setting"></IxHeader>
</template>
<template #footer>
<spam>This is footer</spam>
<span>This is footer</span>
</template>
</IxTable>
</template>
Expand Down
2 changes: 1 addition & 1 deletion packages/components/table/demo/Sortable.vue
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const columns: TableColumn<Data>[] = [
dataKey: 'name',
sortable: {
orders: ['descend'],
sorter: (curr, next) => curr.name.length - next.name.length,
sorter: (curr, next) => curr.name.charCodeAt(0) - next.name.charCodeAt(0),
},
customCell: 'name',
},
Expand Down
14 changes: 14 additions & 0 deletions packages/components/table/demo/SortableMultiple.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
title:
zh: 多列排序
en: Multiple sort
order: 42
---

## zh

如果设置了 `sortable``multiple`,表格的排序将支持多列。

## en

If `multiple` is set to `sortable`, sorting of tables will support multiple columns.
132 changes: 132 additions & 0 deletions packages/components/table/demo/SortableMultiple.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
<template>
<IxTable :columns="columns" :dataSource="data" :pagination="false">
<template #name="{ value }">
<a>{{ value }}</a>
</template>
</IxTable>
</template>

<script lang="ts" setup>
import { reactive } from 'vue'
import { TableColumn, TableColumnSortable } from '@idux/components/table'
interface Data {
key: number
name: string
age: number
grade: number
address: string
}
const ageSortable = reactive<TableColumnSortable<Data>>({
orderBy: 'ascend',
sorter: (curr, next) => curr.age - next.age,
onChange: currOrderBy => {
ageSortable.orderBy = currOrderBy
gradeSortable.orderBy = undefined
},
})
const gradeSortable = reactive<TableColumnSortable<Data>>({
orders: ['ascend', 'descend', 'ascend'],
sorter: (curr, next) => curr.grade - next.grade,
onChange: currOrderBy => {
ageSortable.orderBy = undefined
gradeSortable.orderBy = currOrderBy
},
})
const columns: TableColumn<Data>[] = [
{
title: 'Name',
dataKey: 'name',
sortable: {
multiple: 1,
orders: ['descend'],
sorter: (curr, next) => curr.name.charCodeAt(0) - next.name.charCodeAt(0),
},
customCell: 'name',
},
{
title: 'Age',
dataKey: 'age',
sortable: {
multiple: 2,
sorter: (curr, next) => curr.age - next.age,
},
},
{
title: 'Grade',
dataKey: 'grade',
sortable: {
multiple: 3,
orders: ['ascend', 'descend', 'ascend'],
sorter: (curr, next) => curr.grade - next.grade,
},
},
{
title: 'Address',
dataKey: 'address',
},
]
const data: Data[] = [
{
key: 1,
name: 'John Brown',
age: 18,
grade: 1,
address: 'New York No. 1 Lake Park',
},
{
key: 2,
name: 'Jim Green',
age: 20,
grade: 3,
address: 'London No. 1 Lake Park',
},
{
key: 3,
name: 'Joe Black',
age: 19,
grade: 2,
address: 'Sidney No. 1 Lake Park',
},
{
key: 4,
name: 'Disabled User',
age: 21,
grade: 2,
address: 'Sidney No. 1 Lake Park',
},
{
key: 5,
name: 'John Brown',
age: 18,
grade: 2,
address: 'Sidney No. 1 Lake Park',
},
{
key: 6,
name: 'Jim Green',
age: 20,
grade: 1,
address: 'London No. 1 Lake Park',
},
{
key: 7,
name: 'Joe Black',
age: 19,
grade: 1,
address: 'Sidney No. 1 Lake Park',
},
{
key: 8,
name: 'Disabled User',
age: 21,
grade: 3,
address: 'Sidney No. 1 Lake Park',
},
]
</script>
4 changes: 0 additions & 4 deletions packages/components/table/demo/SortableOrderBy.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,6 @@ order: 41

如果设置了 `sortable``orderBy`,表格的排序将为受控状态。

注意:如果有很多列都设置了 `orderBy`,那么只有第一列会生效。

## en

If `orderBy` of `sortable` is set, the sorting of the table will be in a controlled state.

Note: if there are many columns with `orderBy` set, only the first column will take effect.
1 change: 1 addition & 0 deletions packages/components/table/docs/Index.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ export type TableColumn<T = any, V = any> =
| 名称 | 说明 | 类型 | 默认值 | 全局配置 | 备注 |
| --- | --- | --- | --- | --- | --- |
| `nextTooltip` | 是否显示下一次排序的 `tooltip` 提示 | `boolean` | `false` | ✅ | - |
| `multiple` | 排序优先级 | `number` | - | - | 设置后,支持多列排序 |
| `orderBy` | 当前排序规则 | `'ascend' \| 'descend'` | - | - | - |
| `orders` | 支持的排序方式 | `Array<'ascend' \| 'descend'>` | `['ascend', 'descend']` | ✅ | - |
| `sorter` | 本地模式下,排序的运行函数 | `(curr: T, next: T) => number` | - | - | 参考 [`Array.sort`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort) |
Expand Down
2 changes: 1 addition & 1 deletion packages/components/table/src/Table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export default defineComponent({
props,
mergedChildrenKey,
mergedGetKey,
sortableContext.activeSortable,
sortableContext.activeSorters,
filterableContext.activeFilters,
expandableContext.expandedRowKeys,
mergedPagination,
Expand Down
30 changes: 19 additions & 11 deletions packages/components/table/src/composables/useDataSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@ import { type VKey } from '@idux/cdk/utils'
import { type TablePagination, type TableProps } from '../types'
import { type ActiveFilter } from './useFilterable'
import { type GetRowKey } from './useGetRowKey'
import { type ActiveSortable } from './useSortable'
import { type ActiveSorter } from './useSortable'

export function useDataSource(
props: TableProps,
mergedChildrenKey: ComputedRef<string>,
mergedGetKey: ComputedRef<GetRowKey>,
activeSortable: ActiveSortable,
activeSorters: ComputedRef<ActiveSorter[]>,
activeFilters: ComputedRef<ActiveFilter[]>,
expandedRowKeys: Ref<VKey[]>,
mergedPagination: ComputedRef<TablePagination | null>,
Expand All @@ -36,7 +36,7 @@ export function useDataSource(
})

const filteredData = computed(() => filterData(mergedData.value, activeFilters.value, expandedRowKeys.value))
const sortedData = computed(() => sortData(filteredData.value, activeSortable, expandedRowKeys.value))
const sortedData = computed(() => sortData(filteredData.value, activeSorters.value, expandedRowKeys.value))
const paginatedData = computed(() => {
const pagination = mergedPagination.value
const data = sortedData.value
Expand Down Expand Up @@ -109,21 +109,29 @@ function convertDataMap(mergedData: MergedData[], map: Map<VKey, MergedData>) {
})
}

function sortData(mergedData: MergedData[], activeSortable: ActiveSortable, expandedRowKeys: VKey[]) {
const { sorter, orderBy } = activeSortable
if (!sorter || !orderBy) {
function sortData(mergedData: MergedData[], activeSorters: ActiveSorter[], expandedRowKeys: VKey[]) {
const sorters = activeSorters.filter(item => item.orderBy && item.sorter)
const sorterLength = sorters.length

if (sorterLength === 0) {
return mergedData
}

const tempData = mergedData.slice()
const orderFlag = orderBy === 'ascend' ? 1 : -1

tempData.forEach(item => {
if (expandedRowKeys.includes(item.rowKey) && item.children && item.children.length > 0) {
item.children = sortData(item.children, activeSortable, expandedRowKeys)
item.children = sortData(item.children, activeSorters, expandedRowKeys)
}
})
return tempData.sort((curr, next) => {
for (let index = 0; index < sorterLength; index++) {
const { orderBy, sorter } = sorters[index]
const sorterResult = sorter!(curr.record, next.record)
if (sorterResult !== 0) {
return orderBy === 'ascend' ? sorterResult : -sorterResult
}
}
return 0
})
return tempData.sort((curr, next) => orderFlag * sorter(curr.record, next.record))
}

function filterData(mergedData: MergedData[], activeFilters: ActiveFilter[], expandedRowKeys: VKey[]): MergedData[] {
Expand Down
118 changes: 68 additions & 50 deletions packages/components/table/src/composables/useSortable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,71 +5,89 @@
* found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE
*/

import { type ComputedRef, computed, reactive, ref, watch } from 'vue'
import { type ComputedRef, computed, reactive, watch } from 'vue'

import { type VKey, callEmit } from '@idux/cdk/utils'

import { type TableColumnSortOrder, type TableColumnSortable } from '../types'
import { type TableColumnMerged } from './useColumns'

export function useSortable(flattedColumns: ComputedRef<TableColumnMerged[]>): SortableContext {
const activeSortColumn = computed(() => flattedColumns.value.find(column => column.sortable?.orderBy))

const orderByRef = ref<'descend' | 'ascend' | undefined>(activeSortColumn.value?.sortable?.orderBy)
export interface SortableContext {
activeSorters: ComputedRef<ActiveSorter[]>
activeOrderByMap: Record<VKey, TableColumnSortOrder | undefined>
handleSort: (key: VKey, sortable: TableColumnSortable) => void
}

const tempOrderByRef = ref<'descend' | 'ascend' | undefined>()
const activeSortable = reactive<ActiveSortable>({
orderBy: orderByRef.value,
})
export interface ActiveSorter {
key: VKey
multiple: number
orderBy: TableColumnSortOrder | undefined
sorter?: (curr: unknown, next: unknown) => number
}

watch(activeSortColumn, () => {
const sortColumn = activeSortColumn.value
if (sortColumn) {
activeSortable.key = sortColumn.key
orderByRef.value = sortColumn.sortable!.orderBy!
activeSortable.sorter = sortColumn.sortable!.sorter
export function useSortable(flattedColumns: ComputedRef<TableColumnMerged[]>): SortableContext {
const sortableColumns = computed(() => flattedColumns.value.filter(column => column.sortable))
const activeOrderByMap = reactive<Record<VKey, TableColumnSortOrder | undefined>>({})
const multipleEnabled = computed(() => sortableColumns.value.some(column => column.sortable!.multiple !== undefined))

watch(
sortableColumns,
(currColumns, prevColumns) => {
currColumns.forEach(currColumn => {
const { key, sortable } = currColumn
const currOrderBy = sortable!.orderBy
if (currOrderBy || activeOrderByMap[key] === undefined) {
activeOrderByMap[key] = currOrderBy
return
}

// 受控模式
const prevColumn = prevColumns ? prevColumns.find(column => column.key === key) : undefined
const prevOrderBy = prevColumn?.sortable!.orderBy
if (prevOrderBy) {
activeOrderByMap[key] = undefined
}
})
},
{ immediate: true },
)

const activeSorters = computed(() =>
sortableColumns.value
.map(column => {
const { key, sortable } = column
const { multiple = 0, orderBy = activeOrderByMap[key], sorter } = sortable!
return { key, multiple, orderBy, sorter } as ActiveSorter
})
.filter(item => item.orderBy)
.sort((c1, c2) => c2.multiple - c1.multiple),
)

const handleSort = (activeKey: VKey, activeSortable: TableColumnSortable) => {
const { orders, onChange } = activeSortable

const currOrderBy = activeOrderByMap[activeKey]
const nextOrderBy = currOrderBy ? getNextOrderBy(orders!, currOrderBy) : getNextOrderBy(orders!)

if (multipleEnabled.value) {
activeOrderByMap[activeKey] = nextOrderBy
} else {
activeSortable.key = undefined
orderByRef.value = undefined
tempOrderByRef.value = undefined
activeSortable.sorter = undefined
}
})

watch([orderByRef, tempOrderByRef], () => {
activeSortable.orderBy = orderByRef.value ?? tempOrderByRef.value
})

const handleSort = (key: VKey, sortable: TableColumnSortable) => {
const { orders, sorter, onChange } = sortable

const isSameKey = key === activeSortable.key
const orderBy = isSameKey ? getCurrOrderBy(orders!, activeSortable.orderBy) : getCurrOrderBy(orders!)

if (!isSameKey) {
activeSortable.key = key
activeSortable.sorter = sorter
Object.keys(activeOrderByMap).forEach(key => {
if (activeKey === key) {
activeOrderByMap[key] = nextOrderBy
} else {
activeOrderByMap[key] = undefined
}
})
}

tempOrderByRef.value = orderBy
callEmit(onChange, orderBy)
callEmit(onChange, nextOrderBy, activeSorters.value)
}

return { activeSortable, handleSort }
}

export interface SortableContext {
activeSortable: ActiveSortable
handleSort: (key: VKey, sortable: TableColumnSortable) => void
}

export interface ActiveSortable {
key?: VKey
orderBy?: 'descend' | 'ascend'
sorter?: (curr: unknown, next: unknown) => number
return { activeSorters, activeOrderByMap, handleSort }
}

function getCurrOrderBy(orders: TableColumnSortOrder[], currOrderBy?: TableColumnSortOrder) {
function getNextOrderBy(orders: TableColumnSortOrder[], currOrderBy?: TableColumnSortOrder) {
if (!currOrderBy) {
return orders[0]
}
Expand Down

0 comments on commit a407920

Please sign in to comment.