# Views

**Category:** Features/Display/Views

## Design

### Description

Collections of table states. A table state includes the filters, column sorts, and custom columns that are applied to the table.

To use Views in your Table or Grid, you need to include `views={}`.

The default is an "All Items" View and full functionality for your visitors to manage views.

Properties, documented in the API tab, allows you to manage the View more, including setting different defaults views and reacting to events.

To render a collection component with a specific view, pass the URL query parameter `viewId` with the value of the desired view ID. For example, `https://www.example.com/collection?viewId=`.

Note that preset views cannot be renamed. Any visitor-defined view can be renamed using **Rename** under **Manage View**. Visitors can also use **Save as New** to create a copy with a new name.

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

---

### Variations

### Basic View

This example uses the `` `allItemsViewProps` prop to hide the default "All Items" view from the list of views. "All Items" view will be the default view, and then visitors can apply filters, custom columns, or sorting before saving the current view.

```tsx
import { Avatar } from '@wix/design-system';
import React from 'react';
import { CollectionPage } from '@wix/patterns/page';
import {
  Table,
  Views,
  CollectionToolbarFilters,
  MultiInlineCheckboxFilter,
  useTableCollection,
  OffsetQuery,
  stringsArrayFilter,
  Filter,
} from '@wix/patterns';
import { contacts } from '@wix/crm';

type ContactsFilters = {
  level: Filter<string[]>;
};

function ViewsExample() {

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

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

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

      if (filters.level) {
        queryBuilder = queryBuilder.in('info.jobTitle', filters.level);
      }

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

  const collection = state.collection;

  return (
    <CollectionPage height="400px">
      <CollectionPage.Header title={{ text: 'Contacts' }} />
      <CollectionPage.Content>
        <Table
          state={collection}
          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',
              sortable: true,
              render: (contact) =>
                `${contact.info?.name?.first} ${contact.info?.name?.last}`,
            },
            {
              title: 'Level',
              render: (contact) => contact.info?.jobTitle,
            },
            {
              id: 'lastActivity',
              title: 'Last Activity',
              render: (contact) =>
                contact.lastActivity?.activityDate?.toLocaleString(),
              defaultHidden: true,
            },
          ]}
          filters={
            <CollectionToolbarFilters
              inline={0}
              panelTitle="Filter you contacts"
              useNewFilters
            >
              <MultiInlineCheckboxFilter
                filter={collection.filters.level}
                accordionItemProps={{ label: 'Level' }}
                data={[
                  'Beginner',
                  'Amateur',
                  'Semi-Pro',
                  'Professional',
                  'World Class',
                  'Legendary',
                  'Ultimate',
                ]}
                renderItem={(level, idx) => ({ id: `${idx}`, name: level })}
              />
            </CollectionToolbarFilters>
          }
          views={
            <Views
              allItemsViewProps={{ hidden: true }}
              presets={[
                {
                  id: 'views-example-preset',
                  name: 'Advanced',
                  isDefaultView: true,
                  sortDirections: [
                    { columnId: 'name', direction: 'Descending' },
                  ],
                  filters: {
                    level: {
                      value: ['World Class', 'Legendary', 'Ultimate'],
                    },
                  },
                },
              ]}
            />
          }
        />
      </CollectionPage.Content>
    </CollectionPage>
  );
}
```

### Basic View that can be chosen

This example uses the `` `allItemsViewProps` prop to name the default view that appears in the views dropdown.

```tsx
import { Avatar } from '@wix/design-system';
import React from 'react';
import { CollectionPage } from '@wix/patterns/page';
import {
  Filter,
  Table,
  useTableCollection,
  OffsetQuery,
  Views,
  CollectionToolbarFilters,
  MultiSelectCheckboxFilter,
  stringsArrayFilter,
  useStaticListFilterCollection,
} from '@wix/patterns';
import { contacts } from '@wix/crm';

type ContactsFilters = {
  level: Filter<string[]>;
};

function AllItemsView() {

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

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

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

      if (filters.level) {
        queryBuilder = queryBuilder.in('info.jobTitle', filters.level);
      }

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

  const collection = state.collection;
  const levelsCollection = useStaticListFilterCollection(
    collection.filters.level,
    [
      'Beginner',
      'Amateur',
      'Semi-Pro',
      'Professional',
      'World Class',
      'Legendary',
      'Ultimate',
    ],
  );

  return (
    <CollectionPage height="400px">
      <CollectionPage.Header title={{ text: 'Contacts', hideTotal: true }} />
      <CollectionPage.Content>
        <Table
          state={state}
          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',
              sortable: true,
              render: (contact) =>
                `${contact.info?.name?.first} ${contact.info?.name?.last}`,
            },
            {
              title: 'Level',
              render: (contact) => contact.info?.jobTitle,
            },
            {
              title: 'Last Activity',
              render: (contact) =>
                contact.lastActivity?.activityDate?.toLocaleString(),
            },
          ]}
          filters={
            <CollectionToolbarFilters
              inline={1}
              panelTitle="Filter your contacts"
            >
              <MultiSelectCheckboxFilter
                popoverProps={{ appendTo: 'window' }}
                filter={collection.filters.level}
                accordionItemProps={{ label: 'Level' }}
                collection={levelsCollection}
                renderItem={(level) => ({ title: level })}
              />
            </CollectionToolbarFilters>
          }
          views={
            <Views
              allItemsViewProps={{ name: 'All Items' }}
              presets={[
                {
                  id: 'beginner',
                  name: 'Beginner',
                  filters: {
                    level: {
                      value: ['Beginner'],
                    },
                  },
                },
                {
                  id: 'advanced',
                  name: 'Advanced',
                  filters: {
                    level: {
                      value: ['World Class', 'Legendary', 'Ultimate'],
                    },
                  },
                },
              ]}
            />
          }
        />
      </CollectionPage.Content>
    </CollectionPage>
  );
}
```

### View with selected columns

This example uses the `selectedColumns` property in the view object in the `` `presets` prop to define which columns should be displayed in the table in this view.

```tsx
import { Avatar } from '@wix/design-system';
import React from 'react';
import { CollectionPage } from '@wix/patterns/page';
import {
  Table,
  Views,
  CollectionToolbarFilters,
  MultiInlineCheckboxFilter,
  CustomColumns,
  Filter,
  OffsetQuery,
  stringsArrayFilter,
  useTableCollection,
} from '@wix/patterns';
import { contacts } from '@wix/crm';

type ContactsFilters = {
  level: Filter<string[]>;
};

function ViewsCustomColumnsExample() {

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

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

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

      if (filters.level) {
        queryBuilder = queryBuilder.in('info.jobTitle', filters.level);
      }

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

  const collection = state.collection;

  return (
    <CollectionPage height="400px">
      <CollectionPage.Header title={{ text: 'Contacts' }} />
      <CollectionPage.Content>
        <Table
          state={collection}
          columns={[
            {
              id: 'avatar',
              name: 'Avatar',
              title: '',
              width: '50px',
              hideable: false,
              render: (contact) => (
                <Avatar
                  name={`${contact.info?.name?.first} ${contact.info?.name?.last}`}
                  imgProps={{ src: contact.info?.picture?.image }}
                />
              ),
            },
            {
              id: 'name',
              title: 'Name',
              width: '250px',
              render: (contact) =>
                `${contact.info?.name?.first} ${contact.info?.name?.last}`,
            },
            {
              id: 'level',
              title: 'Level',
              render: (contact) => contact.info?.jobTitle,
            },
            {
              id: 'lastActivity',
              title: 'Last Activity',
              render: (contact) =>
                contact.lastActivity?.activityDate?.toLocaleString(),
              defaultHidden: true,
            },
          ]}
          customColumns={<CustomColumns />}
          filters={
            <CollectionToolbarFilters
              inline={0}
              panelTitle="Filter you contacts"
              useNewFilters
            >
              <MultiInlineCheckboxFilter
                filter={collection.filters.level}
                accordionItemProps={{ label: 'Level' }}
                data={[
                  'Beginner',
                  'Amateur',
                  'Semi-Pro',
                  'Professional',
                  'World Class',
                  'Legendary',
                  'Ultimate',
                ]}
                renderItem={(level, idx) => ({ id: `${idx}`, name: level })}
              />
            </CollectionToolbarFilters>
          }
          views={
            <Views
              allItemsViewProps={{ hidden: true }}
              presets={[
                {
                  id: 'views-custom-columns-example-preset',
                  name: 'Top',
                  isDefaultView: true,
                  selectedColumns: [
                    { id: 'avatar', isSelected: true },
                    { id: 'level', isSelected: false },
                    { id: 'name', isSelected: true },
                    { id: 'lastSeen', isSelected: false },
                  ],
                  filters: {
                    level: {
                      value: ['World Class', 'Legendary', 'Ultimate'],
                    },
                  },
                },
              ]}
            />
          }
        />
      </CollectionPage.Content>
    </CollectionPage>
  );
}
```

### Views separated by category in the dropdown

This example defines views in different sections in the `` `presets` prop.

```tsx
import { Avatar } from '@wix/design-system';
import React from 'react';
import { CollectionPage } from '@wix/patterns/page';
import {
  Filter,
  Table,
  useTableCollection,
  OffsetQuery,
  Views,
  CollectionToolbarFilters,
  MultiSelectCheckboxFilter,
  stringsArrayFilter,
  useStaticListFilterCollection,
} from '@wix/patterns';
import { contacts } from '@wix/crm';

type ContactsFilters = {
  level: Filter<string[]>;
  locale: Filter<string[]>;
};

function PredefinedCategories() {

  const state = useTableCollection<contacts.Contact, ContactsFilters>({
    queryName: 'contacts-ViewsPredefinedCategories',
    fetchData: (query: OffsetQuery<ContactsFilters>) => {
      const { limit, offset, search, sort, filters } = 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',
                );
        }
      }

      if (filters.level) {
        queryBuilder = queryBuilder.in('info.jobTitle', filters.level);
      }

      if (filters.locale) {
        queryBuilder = queryBuilder.in('info.locale', filters.locale);
      }

      return queryBuilder.find().then(({ items = [], totalCount: total }) => ({
        items,
        total,
      }));
    },
    itemName: (item) => `${item.info?.name?.first} ${item.info?.name?.last}`,
    fetchErrorMessage: () => 'Error fetching contacts',
    filters: {
      level: stringsArrayFilter(),
      locale: stringsArrayFilter(),
    },
  });
  const collection = state.collection;
  const levelsCollection = useStaticListFilterCollection(
    collection.filters.level,
    [
      'Beginner',
      'Amateur',
      'Semi-Pro',
      'Professional',
      'World Class',
      'Legendary',
      'Ultimate',
    ],
  );

  const localeCollection = useStaticListFilterCollection(
    collection.filters.locale,
    ['en-US', 'he-IL', 'fr-FR', 'es-ES', 'de-DE'],
  );

  return (
    <CollectionPage height="400px">
      <CollectionPage.Header title={{ text: 'Contacts' }} />
      <CollectionPage.Content>
        <Table
          state={state}
          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',
              sortable: true,
              render: (contact) =>
                `${contact.info?.name?.first} ${contact.info?.name?.last}`,
            },
            {
              id: 'level',
              title: 'Level',
              render: (contact) => contact.info?.jobTitle,
            },
            {
              id: 'lastActivity',
              title: 'Last Activity',
              render: (contact) =>
                contact.lastActivity?.activityDate?.toLocaleString(),
            },
            {
              id: 'locale',
              title: 'Locale',
              render: (contact) => contact.info?.locale,
            },
          ]}
          filters={
            <CollectionToolbarFilters
              useNewFilters
              panelTitle="Filter your contacts"
            >
              <MultiSelectCheckboxFilter
                popoverProps={{ appendTo: 'window' }}
                accordionItemProps={{ label: 'Level' }}
                filter={collection.filters.level}
                collection={levelsCollection}
                renderItem={(level) => ({ title: level })}
              />

              <MultiSelectCheckboxFilter
                popoverProps={{ appendTo: 'window' }}
                accordionItemProps={{ label: 'Locale' }}
                filter={collection.filters.locale}
                collection={localeCollection}
                renderItem={(locale) => ({ title: locale })}
              />
            </CollectionToolbarFilters>
          }
          views={
            <Views
              presets={[
                {
                  id: 'contacts-level',
                  name: 'Contacts level',
                  views: [
                    {
                      id: 'beginner',
                      name: 'Beginner',
                      filters: {
                        level: {
                          value: ['Beginner'],
                        },
                      },
                    },
                    {
                      id: 'advanced',
                      name: 'Advanced',
                      filters: {
                        level: {
                          value: ['World Class', 'Legendary', 'Ultimate'],
                        },
                      },
                    },
                  ],
                },
                {
                  id: 'locale',
                  name: 'Locale',
                  views: [
                    {
                      id: 'en-US',
                      name: 'en-US',
                      filters: {
                        locale: {
                          value: ['en-US'],
                        },
                      },
                    },
                    {
                      id: 'he-IL',
                      name: 'he-IL',
                      filters: {
                        locale: {
                          value: ['he-IL'],
                        },
                      },
                    },
                  ],
                },
              ]}
            />
          }
        />
      </CollectionPage.Content>
    </CollectionPage>
  );
}
```

## API

### Props

| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `onSelect` | `((view: View<F>) => void)` | No | - | Function that is called when a view is selected. |
| `showTotal` | `boolean` | No | `true` | Whether to show the total count for the selected view. |
| `presets` | `Category<F>[] \| Omit<View<F>, "createdAt">[]` | No | - | Array of view or category objects that appear as predefined views or groups of views. @override [Category]()<F>[./?path=%2Fstory%2Fcommon-types--category] \| [View](./?path=%2Fstory%2Fcommon-types--view)<F>[] |
| `learnMoreLink` | `string` | No | - | Redirect URL for the **Learn More** button in the **Save View** modal. |
| `onSaveViewChanges` | `((view: View<F>) => void \| Promise<void>)` | No | - | Function that is called when a view is saved. |
| `onSaveView` | `((view: View<F>) => void \| Promise<void>)` | No | - | Function that is called when a new view is saved. |
| `onDeleteView` | `((view: View<F>) => void \| Promise<void>)` | No | - | Function that is called when a view is deleted. |
| `onRenameView` | `((view: View<F>) => void \| Promise<void>)` | No | - | Function that is called when a view is renamed. |
| `onSetAsDefaultView` | `((view: View<F>, isDefault: boolean) => void \| Promise<void>)` | No | - | Function that is called when a view is set as the default view. |
| `allItemsViewProps` | `AllItemsViewProps<F>` | No | - | Information about how to handle the "All Items" view. <br><br> Supported properties:<br> - `name`: [string] Display name instead of "All Items".<br> - `hidden`: [bool] Whether to hide the "All Items" view from the list. Ignored if there are no preset views. Default: `false`.<br> |
| `viewNamePlaceholder` | `string` | No | - | Overrides the placeholder in the **Save View** modal. |

## Testkit

### Usage

Views API is exposed from the `` [testkit](./?path=/story/base-components-collections-table-table--table&activeTab=Testkit)

## APIs List

### `getViewsDropdown`
Returns the views dropdown testkit.
There are methods to:
- get the currently selected view name (`getSelected`)
- get the currently selected view id (`getSelectedId`)
- check if total is shown (`hasTotal`)
- open the dropdown (`open`)
- get number of views (`getViewsCount`)
- get array of all view names (`getViewsContent`)
- check if the dropdown is open (`isOpen`)
- get the placeholder test of the dropdown (`getPlaceholderText`)
- get view list item driver by view id / index (`getViewListItemById` / `getViewListItemAt`)
- get category list item driver by category id / index (`getCategoryListItemById` / `getCategoryListItemAt`)
- get number of categories (`getCategoriesCount`)
- get array of all category names (`getCategoriesContent`)
- get "all items" view list item (`getAllItemsView`)
- check if view is pending changes (`isViewPendingChanges`)

### `getSaveViewModal`
Returns the Save view modal testkit.

### `getRenameViewModal`
Returns the Rename view modal testkit.

### Manage view popover APIs

- indicates whether the Manage View popover exists (`manageViewPopoverExists`)
- indicates whether the Manage View popover opened (`isManageViewPopoverOpen`)
- opens the Manage View popover (`openManageViewPopover`)
- clicks the Save Changes action in the Manage View popover (`saveViewChangesActionClick`)
- clicks the Save as New View action in the Manage View popover (`saveNewViewActionClick`)
- clicks the Rename action in the Manage View popover (`renameViewActionClick`)
- clicks the Delete action in the Manage View popover (`deleteViewActionClick`)
- get DropdownLayoutOptionUniDriver of a Save Changes option (`getSaveViewChangesBtn`)
- get DropdownLayoutOptionUniDriver of a Save as New View option (`getSaveNewViewBtn`)
- get DropdownLayoutOptionUniDriver of a Delete View option (`getDeleteViewBtn`)
- get DropdownLayoutOptionUniDriver of a Rename option (`getRenameViewBtn`)

### Actions

- saves new view with a defined name and current filters / sort / columns (`saveNewView`)
- deletes the view with a given index (`deleteView`)
- saves changes to the current view (`saveViewChanges`)
- renames the current view with a given name (`renameView`)


## BI

### Views 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:122](https://bo.wix.com/data-tools/bi-catalog-app/event/144:122) | Sent when a user creates/saves a view in a Wix Patterns component                                                                                                  |
| [144:123](https://bo.wix.com/data-tools/bi-catalog-app/event/144:123) | Sent when a user selects a view in a Wix Patterns component                                                                                                 |
| [144:124](https://bo.wix.com/data-tools/bi-catalog-app/event/144:124) | Sent when a user deletes a view in a Wix Patterns component                                 
| [144:141](https://bo.wix.com/data-tools/bi-catalog-app/event/144:141) |Sent when a user clicks on a "try again" CTA he gets in an error notification                                                                                               |
| [144:143](https://bo.wix.com/data-tools/bi-catalog-app/event/144:143) | Sent when a user selects one of the actions in "manage view" menu                   |
| [144:145](https://bo.wix.com/data-tools/bi-catalog-app/event/144:145) | Sent when a user made an action on a view, but his changes weren't update in the server from different reasons |
| [144:151](https://bo.wix.com/data-tools/bi-catalog-app/event/144:151) | Sent when the search results are displayed, after a user searches for a view in a Wix Patterns views list
| [144:115](https://bo.wix.com/data-tools/bi-catalog-app/event/144:115) | Sent when the user clicks the "More Actions" (3-dots) button inside the view dropdown. Fields: `cta_name='more_actions'`, `location='view_dropdown'`, `item_id=`, `item_index=`
| [144:4](https://bo.wix.com/data-tools/bi-catalog-app/event/144:4) | Sent when the user clicks on the view dropdown



### 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)


