# MultiLevelSorting

**Category:** Features/Sort/Sortable Columns/MultiLevelSorting

## Design

### Description

Allows visitors to apply sorting to multiple columns (in hierarchical order).

See also [Sortable Columns](./?path=/story/features-sort-sortable-columns-sortable-columns--sortable-columns).

`` must be passed to a [``](./?path=/story/base-components-collections-table-table--table) `multiLevelSorting` prop. Note that only `` and `` support sorting.

Make sure to mark each column that you want to be sortable with `sortable: true`.

```tsx
import { MultiLevelSorting } from '@wix/patterns';
```

### Basic Multi Level Sorting

Apply sorting to multiple columns using the `` `multiLevelSorting` prop.

```tsx
import { Avatar, Page } from '@wix/design-system';
import React from 'react';
import {
  Table,
  PageWrapper,
  OffsetQuery,
  useTableCollection,
  MultiLevelSorting,
  ToolbarTitle,
} from '@wix/patterns';
import { contacts } from '@wix/crm';

function Basic() {

  const state = useTableCollection<contacts.Contact>({
    queryName: 'contacts-Basic',
    fetchData: (query: OffsetQuery) => {
      const { limit, offset, search, sort } = query;

      let queryBuilder = contacts.queryContacts().limit(limit).skip(offset);

      if (search) {
        queryBuilder = queryBuilder.startsWith('info.name.first', search);
      }

      if (sort) {
        for (const sortLevel of sort) {
          queryBuilder =
            sortLevel.order === 'asc'
              ? queryBuilder.ascending(
                  sortLevel.fieldName as 'info.name.first' | 'info.jobTitle',
                )
              : queryBuilder.descending(
                  sortLevel.fieldName as 'info.name.first' | 'info.jobTitle',
                );
        }
      }

      return queryBuilder.find().then(({ items = [], totalCount: total }) => ({
        items,
        total,
      }));
    },
    itemName: (item) => `${item.info?.name?.first} ${item.info?.name?.last}`,
    fetchErrorMessage: () => 'Error fetching contacts',
    filters: {},
  });

  return (
    <PageWrapper>
      <Page height="400px">
        <Page.Content>
          <Table
            state={state}
            multiLevelSorting={<MultiLevelSorting />}
            title={<ToolbarTitle title="Contacts" showTotal />}
            columns={[
              {
                title: '',
                width: '50px',
                render: (contact) => (
                  <Avatar
                    name={`${contact.info?.name?.first} ${contact.info?.name?.last}`}
                    imgProps={{ src: contact.info?.picture?.image }}
                  />
                ),
              },
              {
                id: 'info.name.first',
                title: 'Name',
                width: '250px',
                render: (contact) =>
                  `${contact.info?.name?.first} ${contact.info?.name?.last}`,
                sortable: true,
              },
              {
                id: 'info.jobTitle',
                title: 'Level',
                render: (contact) => contact.info?.jobTitle,
                sortable: true,
              },
              {
                title: 'Last Activity',
                render: (contact) =>
                  contact.lastActivity?.activityDate?.toLocaleString(),
              },
            ]}
          />
        </Page.Content>
      </Page>
    </PageWrapper>
  );
}
```

### Multi Level Sorting with defined sort directions

Use the `defaultSortDirection` property in each columns object in the `columns` array to define the direction of the sort for that column.

```tsx
import { Avatar, Page } from '@wix/design-system';
import React from 'react';
import {
  Table,
  PageWrapper,
  OffsetQuery,
  useTableCollection,
  MultiLevelSorting,
  ToolbarTitle,
} from '@wix/patterns';
import { contacts } from '@wix/crm';

function DefaultSortOrder() {

  const state = useTableCollection<contacts.Contact>({
    queryName: 'contacts-DefaultSortOrder',
    fetchData: (query: OffsetQuery) => {
      const { limit, offset, search, sort } = query;

      let queryBuilder = contacts.queryContacts().limit(limit).skip(offset);

      if (search) {
        queryBuilder = queryBuilder.startsWith('info.name.first', search);
      }

      if (sort) {
        for (const sortLevel of sort) {
          queryBuilder =
            sortLevel.order === 'asc'
              ? queryBuilder.ascending(
                  sortLevel.fieldName as 'info.name.first' | 'info.jobTitle',
                )
              : queryBuilder.descending(
                  sortLevel.fieldName as 'info.name.first' | 'info.jobTitle',
                );
        }
      }

      return queryBuilder.find().then(({ items = [], totalCount: total }) => ({
        items,
        total,
      }));
    },
    itemName: (item) => `${item.info?.name?.first} ${item.info?.name?.last}`,
    fetchErrorMessage: () => 'Error fetching contacts',
    filters: {},
  });

  return (
    <PageWrapper>
      <Page height="400px">
        <Page.Content>
          <Table
            state={state}
            multiLevelSorting={<MultiLevelSorting />}
            title={<ToolbarTitle title="Contacts" showTotal />}
            columns={[
              {
                title: '',
                id: 'avatar',
                width: '50px',
                render: (contact) => (
                  <Avatar
                    name={`${contact.info?.name?.first} ${contact.info?.name?.last}`}
                    imgProps={{ src: contact.info?.picture?.image }}
                  />
                ),
              },
              {
                id: 'info.name.first',
                title: 'Name',
                width: '250px',
                render: (contact) =>
                  `${contact.info?.name?.first} ${contact.info?.name?.last}`,
                sortable: true,
                defaultSortOrder: 'asc',
              },
              {
                id: 'info.jobTitle',
                title: 'Level',
                render: (contact) => contact.info?.jobTitle,
                sortable: true,
                defaultSortOrder: 'desc',
              },
              {
                title: 'Last Activity',
                render: (contact) =>
                  contact.lastActivity?.activityDate?.toLocaleString(),
              },
            ]}
          />
        </Page.Content>
      </Page>
    </PageWrapper>
  );
}
```

### Multi Level Sorting with custom sort order

Use the `sortMode` property in each columns object in the `columns` array to define the order of the sort options for that column.

```tsx
import { Avatar, Page } from '@wix/design-system';
import React from 'react';
import {
  Table,
  PageWrapper,
  OffsetQuery,
  useTableCollection,
  MultiLevelSorting,
  ToolbarTitle,
} from '@wix/patterns';
import { contacts } from '@wix/crm';

function SortMode() {

  const state = useTableCollection<contacts.Contact>({
    queryName: 'contacts-SortMode',
    fetchData: (query: OffsetQuery) => {
      const { limit, offset, search, sort } = query;

      let queryBuilder = contacts.queryContacts().limit(limit).skip(offset);

      if (search) {
        queryBuilder = queryBuilder.startsWith('info.name.first', search);
      }

      if (sort) {
        for (const sortLevel of sort) {
          queryBuilder =
            sortLevel.order === 'asc'
              ? queryBuilder.ascending(
                  sortLevel.fieldName as 'info.name.first' | 'info.jobTitle',
                )
              : queryBuilder.descending(
                  sortLevel.fieldName as 'info.name.first' | 'info.jobTitle',
                );
        }
      }

      return queryBuilder.find().then(({ items = [], totalCount: total }) => ({
        items,
        total,
      }));
    },
    itemName: (item) => `${item.info?.name?.first} ${item.info?.name?.last}`,
    fetchErrorMessage: () => 'Error fetching contacts',
    filters: {},
  });

  return (
    <PageWrapper>
      <Page height="400px">
        <Page.Content>
          <Table
            state={state}
            multiLevelSorting={<MultiLevelSorting />}
            title={<ToolbarTitle title="Contacts" showTotal />}
            columns={[
              {
                title: '',
                id: 'avatar',
                width: '50px',
                render: (contact) => (
                  <Avatar
                    name={`${contact.info?.name?.first} ${contact.info?.name?.last}`}
                    imgProps={{ src: contact.info?.picture?.image }}
                  />
                ),
              },
              {
                id: 'info.name.first',
                title: 'Name',
                width: '250px',
                render: (contact) =>
                  `${contact.info?.name?.first} ${contact.info?.name?.last}`,
                sortable: true,
                defaultSortOrder: 'asc',
                sortMode: ['asc', 'desc'],
              },
              {
                id: 'info.jobTitle',
                title: 'Level',
                render: (contact) => contact.info?.jobTitle,
                sortable: true,
                sortMode: ['asc', undefined, 'desc'],
              },
              {
                id: 'lastActivity',
                title: 'Last Activity',
                render: (contact) =>
                  contact.lastActivity?.activityDate?.toLocaleString(),
                sortable: true,
                sortMode: [undefined, 'desc', 'asc'],
              },
            ]}
          />
        </Page.Content>
      </Page>
    </PageWrapper>
  );
}
```

## API

### Props

| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `id` | `string \| undefined` | Yes | - | **Required**. ID of the column to sort. |
| `sortable` | `boolean` | No | - | Whether a column can be sorted. |
| `defaultSortOrder` | `"asc" \| "desc"` | No | - | Default sort order of the column. |
| `sortMode` | `SortMode` | No | - | Determines the "next sort order" strategy. If this prop is omitted, the default cycle will run.<br> When a visitor clicks on the column header, Wix Patterns changes the sort order of this column using the following cycle: `'asc' -> 'desc' -> undefined(no sort) -> 'asc'`.<br> You can customize the order by passing an array of the sort order, for example: `[undefined, 'desc', 'asc']` |

## Testkit

### Usage

All `` testkit APIs are exposed using the `getMultiLevelSortingPanel` method of `` [testkit](./?path=/story/base-components-collections-table-table--table&activeTab=Testkit)


## BI

### Events

| Event                                                                                                     | Description                                                         |
| --------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------- |
| [144:119](https://bo.wix.com/data-tools/bi-catalog-app/event/144:119) | Sent when a feature sidebar/menu in a Wix Patterns component is opened or closed |
| [144:120](https://bo.wix.com/data-tools/bi-catalog-app/event/144:120) | Sent when a user sorts a Wix Patterns component |
| [144:138](https://bo.wix.com/data-tools/bi-catalog-app/event/144:138) |Sent when a user clicks on an item and tries to drag it, in order to sort the items manually |
| [144:139](https://bo.wix.com/data-tools/bi-catalog-app/event/144:139) | Sent when a user finish to drag an item |
| [144:140](https://bo.wix.com/data-tools/bi-catalog-app/event/144:140) |Sent when a user finish to drag items but those changes weren't update in the server from different reasons |
| [144:147](https://bo.wix.com/data-tools/bi-catalog-app/event/144:147) | Sent when a user changes the order of the columns that sort a Wix Patterns component         |




### Component load events
Event   |   Description   |
--------- | --------------- |
[144:110](https://bo.wix.com/data-tools/bi-catalog-app/event/144:110) | Sent when a Wix Patterns component starts loading
[144:111](https://bo.wix.com/data-tools/bi-catalog-app/event/144:111) | Sent when a Wix Patterns component is done loading


#### 🐪 Couldn't find what you need?
* Check our [BI Catalog](https://bo.wix.com/data-tools/bi-catalog-app?viewId=all-items-view&selectedColumns=src%2Cevid%2Cname%2Cowner%2Cproduct%2CuserType+false%2CdateUpdated%2CdateCreated+false%2CcreatedBy+false%2Cstatus&source=%5B%7B%22id%22%3A%22144%22%2C%22name%22%3A%22144+-+Cairo%22%7D%5D)
* Contact us on [#cairo-bi](https://wix.slack.com/archives/C03N53KURH9)


