List ​

WList renders a paginated collection as a table on desktop and as cards on mobile. It owns pagination, selection, column settings (show, hide, reorder, resize) and ordering. You give it:

  • useQueryFn and queryParams — a paginated query.
  • fields — one component per column. Each has a meta export with its title, width, ordering field and CSS class, and renders one cell from item.
  • cardColumns and cardAreas — the card layout as a CSS grid, with field labels as area names. Every field must appear in cardAreas, even ones hidden by default; a field left out renders in an extra grid column.
  • Optionally an expansion component for a detail row and a menu component for row actions.

The List with fields recipe builds this step by step; the list below is its result.

Growth
Caretaker

Layout variables ​

The sticky header, the checkbox column and full-width rows position themselves from the app's layout variables — --header-height, --nav-bar-width, --actions-bar-width and --inner-margin — so the app has to set them (see App shell). Inside a modal, the modal sets its own.

Bulk actions ​

Above the table is the selection bar, WButtonSelection. While nothing is selected it shows the action components and the list settings; while items are selected, the bulk components and a "Selected N items" counter with a clear button. Actions that don't fit move into a More menu.

Each action is a component that renders a WButtonSelectionAction — an icon with a title, hidden on phones. A bulk action gets BulkProps: selectionCount, queryParamsGetter() for the query params narrowed to the selection, readonly, disableMessage, which disables the button with that text as its tooltip while nothing is selected, and clearSelection() to reset the selection when it is done.

Call clearSelection rather than emitting clear:selected, which still works but is deprecated. An action that doesn't fit the bar renders in the More menu, and a confirm opened from there takes the menu's place, unmounting the action — an emit after the confirm is accepted never arrives.

vue
<template>
  <WButtonSelectionAction
    title="Archive"
    :icon="markRaw(IconArchiveBook)"
    :disable-message="disableMessage"
    :disabled="readonly"
    @click="archive"
  />
</template>

<script lang="ts" setup>
import {markRaw} from 'vue'

import type {BulkProps} from 'eco-vue-js/dist/components/List/types'

import WButtonSelectionAction from 'eco-vue-js/dist/components/Button/WButtonSelectionAction.vue'

import IconArchiveBook from 'eco-vue-js/dist/assets/icons/IconArchiveBook'

const props = defineProps<BulkProps<QueryParamsPlants>>()

const archive = () => plantApi.archive(props.queryParamsGetter()).then(() => props.clearSelection())
</script>

WButtonUnselect is the small round clear button of the select chips.

Filters ​

WListFilter renders filter components for query params held in a Uniform form's scope — usually a form over the route query. Each filter is a component with a meta export (its title, icon and the fields it sets) that gets scope, global and readonly. search adds a text search for the search param, or filterSearch for your own.

In place, the filters are chips in a row: the set ones are shown, and a button adds another. With global they go into the app shell instead — the filters into the actions bar's panel and the search into the header bar — with a button that resets them. disabledFilterFields leaves out filters for params the page fixes. pinned filters are always shown, first, and their remove button only clears them. The List with fields recipe builds a set of filters for its list.

WListHeader and WListHeaderItem are the table header row of the list, with the select-all checkbox and sortable, resizable column titles. WList renders them from fields.

Filters in the selection bar ​

Put the WListFilter in WList's filter slot to show it at the start of the selection bar, on one line with the list actions and the list settings. Chips that do not fit go into a "more" chip that opens them as a list, and the list actions, which move to the end of the bar, fold into More before any chip does. While rows are selected, the selection with its bulk actions replaces the filters, so the bar keeps its height. On phones the filters take a row of their own above the actions, and the search narrows for them. --w-list-toolbar-height (w-list-toolbar-h-*) sets the height of the bar's inputs, chips and actions, by default the inherited input height.

Each chip names its filter and, when set, the picked values. A filter's meta.summary returns them from the query params, such as the titles of the picked options; without it the chip shows how many values are picked. A chip in the "more" list can be removed there too.

Groups ​

groupBy tells whether two neighbouring items belong to the same group, and the group slot renders a heading before the first item of each group and before the first item rendered. Only neighbours are compared, so the query has to sort by the field the list groups by.

The items of all loaded pages share one layout. A group runs on across pages without a new heading, and in the grid a page that ends in the middle of a row is continued in that row by the next one. The list places the heading: it takes a row of its own in the grid, stays under the toolbar while its group scrolls past and, in list mode, stays at the left edge when the rows scroll sideways. The slot gives its content and lays it out.

Nested fields ​

A field's meta can hold fields instead of being a column itself. Its columns then show a related object or every item of an array:

  • keyEntity, or getterEntity for a computed one, gives the nested field components that object as item.
  • keyArray, or getterArray, repeats the columns once per array item, one line each. The items need an id. componentItem wraps each line, for example to draw a separator between them.
  • With neither, fields only groups the columns, such as to place them in one card area.

In the table, nested columns are list columns like any other: each has its own header and width, can be hidden in the list settings, and lines up across the rows, including each line of an array. In a card, a nested field is a flex row. cssClass styles it, cssClassArray styles the wrapper of an array's lines, and [grid-area:…] places it in one of cardAreas; pick labels without _ for that, since Tailwind reads _ in a class as a space.

In the table, the root of componentItem is a line of the table, and each of its children spans the whole line. Put a separator next to <slot />, not around it.

Rows with lines of an array are taller than one line, so the demo uses align-top. The checkbox and the row menu then sit --w-list-align-top from the top of the row. Set it with w-list-align-top-* on WList to match the row's top padding and first line: here the rows have py-2, and w-list-align-top-2.5 centers the box on the first line.

Name
Kind
Caretaker
Role
Task
Due

API ​

WList ​

ts
import WList from 'eco-vue-js/dist/components/List/WList.vue'

Props ​

PropTypeDefaultDescription
countnumber—Total number of items, when known in advance. Sizes the skeleton and the select-all. Defaults to the count the query returns.
fieldsFieldsrequiredField components, one per column, each with a meta export for its title, label, ordering field and classes. A meta with fields groups nested fields.
expansionExpansionComponent<Data, QueryParams>—Detail component shown under a row when the row is clicked. Receives the same props as a field, except config.
useQueryFnUseQueryDefault<PaginatedResponse<Data>, QueryParams>requiredPaginated query the pages are loaded with.
useQueryFnExportUseQueryDefault<PaginatedResponse<Data>, QueryParams>—Paginated query for export, when it differs from useQueryFn.
queryParamsQueryParamsrequiredParams for useQueryFn. An ordering param drives the sort controls and the sortable column headers.
queryOptionsDefaultQueryOptions<PaginatedResponse<Data>>—Options for every page query.
bulkDisableMessagestring—Tooltip on the bulk actions while nothing is selected. Defaults to "No selected items".
selectionTitlestringrequiredSingular noun in the "Selected N items" counter of the selection bar.
bulkBulkComponent<QueryParams>[]—Actions in the selection bar while items are selected. Each gets the selection count and a getter of the query params narrowed to the selection. The ones that do not fit the bar move into a More menu.
actionActionComponent<QueryParams>[]—Actions in the selection bar while nothing is selected, with the current query params.
menuMenuComponent<Data>[]—Row menu items, opened by the row's more button or a right click. A [component, props] tuple passes extra props.
readonlyGetter((item: Data) => boolean)—Makes a single row readonly.
cardClassstring—Classes for each row's content box.
cardWrapperClassstring—Classes for each row's outer box.
selectAllTextGetter(isUnselect: boolean, count: number) => stringrequiredTooltip of the select-all checkbox in the table header.
hasBorderboolean—Draws a border around each row.
configKeystringrequiredlocalStorage key the column settings and view mode are saved under.
defaultConfigMapFieldConfigMap<Fields>requiredWidth, visibility, order and stickiness of each field by label, until the user changes them.
defaultModeListModeListMode.TABLETable or grid view, until the user picks one. Small screens always show cards.
alignTopboolean—Aligns cells, the checkbox and the row menu to the top of the row instead of centering them. --w-list-align-top (w-list-align-top-*) sets how far the checkbox and the menu are from the top, 1.125rem by default, so they line up with the first line of the cells.
disableMoreboolean—Disables the row menu, both the more button and the right click.
readonlyboolean—Makes every row readonly, and passes readonly to the bulk, action and menu components. When unset, inherits the readonly state provided by a parent.
noOrderingboolean—Hides the sort control and makes the column headers not sortable.
formFieldGetter((data: Data, index: number) => string | undefined)—Wraps a row in WUniform for the returned field of uniformScope, so its fields can edit the item as part of a form. undefined leaves the row unwrapped.
uniformScopeUniformScope<Data[]>—Form scope of the items, for formFieldGetter.
groupBy((a: Data, b: Data) => boolean)—Whether two neighbouring items are in the same group. The group slot renders before each new group.
cardColumnsCardColumnsrequiredColumn widths of the card grid.
cardAreasCardAreas<Fields, CardColumns["length"]>requiredRows of the card grid, as area names: field labels, AREA_SELECT, AREA_MORE or .. Areas of hidden fields are dropped, and so are rows and columns left empty.
cardTo((item: Data) => RouteLocationRaw | undefined)—Route a row links to.
hasActionboolean—Makes a row click emit click:action.
noHeaderSettingsboolean—Hides the column settings button, and neither loads nor saves column settings.
noRefetchboolean—Hides the refetch button.
refetchIntervalnumber—Refetches the loaded pages every this many ms.
apiMethodExport((queryParams: QueryParams) => Promise<Data[]>)—Loads the items to export in one call, instead of paging through the export query.
exportFileNamestring—Name of the exported file.
disableExportboolean—Hides the export button. Rows are then selectable only with bulk or alwaysSelect.
alwaysSelectboolean—Shows the checkboxes even without bulk or export, and makes a row click toggle its selection. With cardTo, the row link moves into a View menu item.
selectionSelection<number>—Selected items, controlled from outside with update:selection. By default the selection is kept in the URL hash.
noHeaderUpdateboolean—Keeps the sticky list header out of the app header bar's padding while scrolled.
minHeightboolean—Drops the full-screen minimum height and the bottom padding, for a list inside other content.
toMarkdown((data: Data, index: number) => string)—Converts an item to Markdown. Adds a Copy as Markdown item to the row menu and a Markdown option to export.
noModeboolean—Hides the table and grid switch in the column settings.
disableSelectboolean—Hides the checkboxes and turns selection off.
selectOnlyboolean—Allows only picking rows one by one — no select-all and no Shift range selection.
contentVisibilityboolean—Skips rendering rows outside the viewport with content-visibility: auto, for long lists.

Events ​

EventPayloadDescription
update:error(ApiError<{}, ErrorResponse<{}>>)A page query failed.
click:action(CardActionParams<Data>)A row was clicked, with hasAction. Carries the item, a setter that replaces it and its form scope.
update:query-params(QueryParams)The changed params only — the new ordering, from the sort control or a column header.
update:count(number | undefined)Total count returned by the query.
update:selection(Selection<number>)The new selection, with selection.

Slots ​

SlotPropsDescription
header{ count: number | undefined; }Content above the selection bar, with the count returned by the query.
filter—Filters at the start of the selection bar, usually a WListFilter. They keep to one line, the chips that do not fit go to a menu, the list actions move to the end and fold into More first, and a selection replaces them. --w-list-toolbar-height sets the bar's height, the inherited input height by default.
selection—Replaces the buttons at the end of the selection bar — range select, refetch, sort and column settings.
group{ item: Data; previous: Data | undefined; skeleton: boolean; }Content of the heading before each group of rows, with groupBy. The heading spans the whole row, sticks under the toolbar and, in list mode, to the left edge while the rows scroll sideways. Its content is laid out by the slot. skeleton is true while the page loads.
empty—Shown instead of the rows when the query returns no items.
error{ error: Error; retry: () => Promise<void>; isFetching: boolean; }Shown instead of a page that failed to load and has nothing cached. Gets the error, retry to load the page again, and isFetching. Replaces the message and Retry button.

WListCardField ​

ts
import WListCardField from 'eco-vue-js/dist/components/List/WListCardField.vue'

Props ​

PropTypeDefaultDescription
modelValuestring | number—Value shown as truncated text. The inner and default slots replace it.
skeletonboolean—Shows a placeholder instead of the content.
allowOpenboolean—Lets clicks through to the row, so they open its expansion.

Slots ​

SlotPropsDescription
default—Replaces the whole content, including the truncating wrapper.
inner—Replaces modelValue inside the truncating wrapper.

WButtonSelection ​

ts
import WButtonSelection from 'eco-vue-js/dist/components/Button/WButtonSelection.vue'

Props ​

PropTypeDefaultDescription
titlestring"item"Singular noun in the "Selected N items" counter. An "s" is added for more than one.
disableMessagestring"No selected items"Tooltip of the actions while nothing is selected, which also disables them.
selectedCountnumber—Number of selected items. While it is above 0, the counter with a clear button replaces the settings slot.

Events ​

EventPayloadDescription
clear:selection—The clear button of the counter was clicked.

Slots ​

SlotPropsDescription
default{ disableMessage: string | undefined; cssClass: string; visibleCount: number; }WButtonSelectionAction buttons. Pass them disableMessage, and cssClass for the dividers between them. Hide the ones from visibleCount on, which do not fit — the more slot shows them instead.
more{ disableMessage: string | undefined; cssClass: string; visibleCount: number; }Actions in the More menu at the end of the row: the ones of the default slot from visibleCount on, which do not fit the row. The menu shows only when some do not.
filter—Filters at the start of the bar while nothing is selected. The actions then move to the end, beside settings, and the selection replaces the filters.
settings{ isFilterShown: boolean; }Content at the end of the bar while nothing is selected, such as list settings.

WButtonSelectionAction ​

ts
import WButtonSelectionAction from 'eco-vue-js/dist/components/Button/WButtonSelectionAction.vue'

Props ​

PropTypeDefaultDescription
titlestring—Text after the icon. Hidden on phones, except in the More menu.
labelstring—Name for screen readers. Defaults to tooltipText, then title.
iconSVGComponentrequiredIcon of the action.
disableMessagestring—Disables the action and shows this text in its tooltip, e.g. "No selected items".
disabledboolean—Disables the action.
activeboolean—Marks the action as on, e.g. a filter that is applied.
loadingboolean—Shows a spinner over the action and ignores clicks.
tooltipTextstring—Tooltip text.
toRouteLocationRaw—Router location — renders a router link. Needs vue-router installed in the app.
tagkeyof HTMLElementTagNameMap—Element rendered without to, e.g. a for a link with href. Defaults to button.
hrefstring—Link URL when tag is a.
target"_self" | "_blank" | "_parent" | "_top"—target attribute of the link.
replaceboolean—Replaces the current history entry instead of adding one, with to.

Events ​

EventPayloadDescription
click(value: MouseEvent)The action was clicked, unless it is disabled or loading.

Slots ​

SlotPropsDescription
default—Slot for absolute positioned content, like counter
tooltip—Rich content of the tooltip, replacing disableMessage or tooltipText.

WButtonSelectionState ​

ts
import WButtonSelectionState from 'eco-vue-js/dist/components/Button/WButtonSelectionState.vue'

Props ​

No props.

Events ​

EventPayloadDescription
click(value: MouseEvent)The clear button was clicked.

Slots ​

SlotPropsDescription
default—The selection count, such as "Selected 3 items".

WButtonUnselect ​

ts
import WButtonUnselect from 'eco-vue-js/dist/components/Button/WButtonUnselect.vue'

Props ​

PropTypeDefaultDescription
disabledboolean—Disables the button.
loadingboolean—Ignores clicks, e.g. while the value is saving.

Events ​

EventPayloadDescription
click(value: MouseEvent)The button was clicked, unless it is disabled or loading.
mousedown(value: MouseEvent)A mouse button was pressed on it, unless it is disabled or loading.

WListFilter ​

ts
import WListFilter from 'eco-vue-js/dist/components/List/WListFilter.vue'

Props ​

PropTypeDefaultDescription
scopeUniformScope<QueryParams>requiredScope of the Uniform form that holds the query params, usually synced with the route query.
filterFilterComponent<QueryParams>[]—Filter components, one per filter, each with a meta export. A tuple adds props for it.
pinnedFilterComponent<QueryParams>[]—Filters of filter always shown as chips, first, instead of behind "Add filter". Their remove button only clears them.
filterSearchFilterComponent<QueryParams>—Component for the search field, instead of the default text search on search.
disabledFilterFields(keyof QueryParams)[]—Query params that can't be changed, e.g. fixed by the page. Their filters are left out.
searchboolean—Adds a search field for the search query param.
globalboolean—Puts the filters in the actions bar's filter panel and the search in the header bar, instead of in a row in place.
readonlyboolean—Shows the filters without changing them.
searchVisibleboolean—Deprecated: No effect: the search field is always shown in place, and behind the header's search button with global.

WListHeader ​

ts
import WListHeader from 'eco-vue-js/dist/components/List/WListHeader.vue'

Props ​

PropTypeDefaultDescription
columnsstringrequiredTrack sizes of the columns between the checkbox and the end cap, as in grid-template-columns. The rows use the same tracks, so the cells line up with them.
allowSelectboolean—Shows the select-all checkbox.
selectOnlyboolean—Keeps the checkbox column's space without the checkbox, for lists without select-all.
hideMoreboolean—Narrows the end cap when the rows have no menu.
disabledboolean—Disables the checkbox.
countnumber—Total number of items. The checkbox is disabled while it is 0 or unknown.
selectionboolean | null—State of the select-all checkbox: true for all, null for some, false for none.
tooltipTextstringrequiredTooltip of the select-all checkbox.

Events ​

EventPayloadDescription
update:header—The header was mounted or unmounted, for the sticky header to measure it.
toggle:selection(value: boolean)The select-all checkbox was toggled.

Slots ​

SlotPropsDescription
default—WListHeaderItem cells, one per column.
settings—Content of the end cap, such as the column settings button.

WListHeaderItem ​

ts
import WListHeaderItem from 'eco-vue-js/dist/components/List/WListHeaderItem.vue'

Props ​

PropTypeDefaultDescription
titlestring—Title of the column. The default slot replaces it.
fieldFieldrequiredOrdering field of the column. Without it, the title isn't a sort button.
orderingOrderItem<Field>[]requiredCurrent ordering, to show the column's direction and position in it.
disabledboolean—Turns sorting off.
allowResizeboolean—Adds a handle to resize the column.
itemClassstring—Class of the title, replacing the default bold one-line style.
styleValueRecord<string, string | undefined> | undefinedrequiredStyle of the column, such as its sticky offsets.

Events ​

EventPayloadDescription
update:width(number)The column is being resized to this width.
save:width—Resizing ended, to save the width.
update:ordering(OrderItem<Field>[])The title was clicked: descending, then ascending, then off. Other columns stay in the ordering after it.

Slots ​

SlotPropsDescription
default—Title of the column, replacing title.