epicmaxco / vuestic-ui

Free and Open Source UI Library for Vue 3 🤘
https://vuestic.dev
MIT License
3.34k stars 324 forks source link

The Data Table component #952

Closed sergey-kravcov closed 2 years ago

sergey-kravcov commented 2 years ago

Description

Features

Docs

Testing

Props

Slots

Emits

Снимок экрана 2021-07-13 в 10 53 35

Refs

smellyshovel commented 2 years ago

<va-data-table> Current state updates

  1. Data binding

Optionally accepts the items prop.

items: ITableItem[]

ITableItem: Record<string, any>

In other words, the items prop must always be an array (if provided) of zero-or-more length with each item being an object with string keys and values of any type.

The va-data-table automatically calculates the amount and the titles of columns (if not specified otherwise with the columns prop) based on these object's keys. So e.g. if your items looks like:

[{a: 1}, {a: 2, b: 3}, {c: 4, a: 5}]

then the resulting table will have a total of 3 columns called A, B and C and will look like this:

Screenshot_20210901_113548

When calculating the columns' names based on the item's objects' keys va-data-table uses Lodash's startCase internally, so that myProp becomes My Prop, userFullName becomes User Full Name and so on.

The items' actual values are stringified before being displayed. Falthy values are asserted to the empty string.


Automatically built columns are not always the desirable behavior, so va-data-table also optionally accepts the columns prop.

columns: (string | ITableColumn)[]

ITableColumn {
  key: string; // name of an item's property
  label?: string; // what to display in the respective heading
  headerTitle?: string; // <th>'s `title` attribute's value
  sortable?: boolean, // whether the table can be sorted by that column
  sortingFn?: (a: any, b: any) => number; // a custom sorting function. `a` and `b` are currently compared cells' original values (sources). Must return a number (see the standard JS's Array.prototype.sort)
  alignHead?: TAlignOptions; // horizontal alignment of the column's heading
  verticalAlignHead?: TVerticalAlignOptions; // vertical alignment of the column's heading
  align?: TAlignOptions; // horizontal <td>'s alignment
  verticalAlign?: TVerticalAlignOptions; // vertical <td>'s alignment
}

In other words, the columns prop, if provided, must be an array of either strings or objects implementing the ITableColumn interface.

The component internally builds instances similar to (but not exactly to) the ITableColumn interface. When a string is provided, its value is used to simultaneously build the columns title (using the same startCase method) and as an indication of which field should be used to render the value of a given row.

E.g. if provided columns prop looks like this:

["columnOne", "columnTwo"]

And the items:

[{columnOne: 1, columnThree: 3}, {columnTwo: 2, columnOne: 1}]

Then the resulting table would look like follows:

Screenshot_20210901_114955

Note how the columnTrhree is ignored. If it wasn't for the columns props (if the columns were generated automatically based on the items' value, then it would exist).

Instead of strings, you can also provide objects of the ITableColumn interface. Sometimes it's the only option since only that form allows to enable certain column features (such as sorting).

The only mandatory field for the ITableColumns is the key. It acts the same way as the string-value of the columns array does.

smellyshovel commented 2 years ago
  1. Filtering

va-data-tables allows to apply a single table-wise filter with the filter prop.

filter: string

When the filter is provided, only the rows where at least one cell contains the specified value will be rendered. To runtime-disable filtering (clear the filter), provide an empty string.

basic filtering

To use a custom filtering function, provide the va-data-table with the filtering-fn prop.

filtering-fn: TFilteringFn

TFilteringFn: (source: any) => boolean

The function takes the initial value of a currently being checked cell (the source formal parameter) and must return a boolean, indicating, whether to include the row containing that cell or not. The following example shows the use of a custom filtering function (which looks for the exact match rather than the standard one which checks for substring-inclusion) when the respective checkbox is checked:

custom filtering fn

va-data-table emits the filter event each time filtering is applied (and when the filter is cleared), passing the number of filtered items as the first (and the only) argument.

smellyshovel commented 2 years ago
  1. Sorting

You can specify which columns should be sortable by providing a column definition object (see the columns prop above) with the sortable: true field.

Making a column sortable means allowing to click the column's header to toggle the sorting by that column's values:

basic sorting

You can also provide a custom sorting function for a given column using the sortingFn field on the column definition object.

The following GIF show runtime-toggling the custom sorting function which sorts the ID column's values as number instead of as strings:

custom sorting fn

sortingFn: (a: any, b: any) => number;

The function takes two cells' initial values (a, b) (note: initial! (i.e. in the form the user provided them, rather than stringified)) and must return a number (-1, 0, +1) indicating whether the two rows shuld be swapped the places or not. See the standard JS's Array.prototype.sort for details.

If you want to runtime-disable the custom function and start making use of the built-in one, pass the undefined to the sortingFn.

Each time the table's sorting changes, the sort event is thrown, with the following param:

{sortBy: string, sortingOrder: TSortingOrder}

TSortingOrder: "asc" | "desc" | null

...indicating the column's key the table is currently sorted by and the sorting order: ascending, descending and null for un-sorted table.


va-data-table also optionally accepts the sortBy and the sortingOrder modeled props, which allow users to change sorting settings from-outside and to model the changes introduced to the table's sorting by interacting with the table itself. They also allow to provide initial sorting values. Don't use them without v-models.

sorting model

smellyshovel commented 2 years ago
  1. Selection

Selection fully respects filtering and sorting.

Use the selectable prop to indicate whether the va-data-table should have selectable rows or not.

selectable: boolean

We provide 2 selection modes: multiple (default) and single. Single selection, as its name implies, allows for only a single row to be selected at a time, while multiple mode allows to select multiple rows by clicking on checkboxes or using the ctrl/shift keys when clicking rows.

select-mode: TSelectMode

TSelectMode: "single" | "multiple"

basic selection

When select-mode is set to multiple, an additional header-checkbox is also rendered, allowing to select/un-select all the rows simultaneously.


The highlighting color of the selected row might be changed with the selected-color prop, taking a string with one of the possible color options: "primary", "danger" and so on:

selection colors


The selection may optionally be attached to a model. The standard v-model (using the modelValue prop and the @update:modelValue event). This also allows to set the initial selection on the va-data-table:

selection model

Don't use the :modelValue prop without v-model.


Except for the update:modelValue event, the selection-change event is also thrown each time the selection changes.

It provides the following object as its only argument:

{
  currentlySelectedItems: ITableItem[];
  previouslySelectedItems: ITableItem[];
}

...with the currently and previously selected items respectively.

smellyshovel commented 2 years ago
  1. Pagination

Use the perPage and currentPage props to enable pagination.

perPage: number
currentPage: number

Those 2 indicate the amount of items which should be shown on a page and the number of the page respectively.

basic pagination

The va-data-table component is paginator-agnostic. I.e. it can work with any pagination component you'd like. In the example above it uses standard (native) inputs.

The component also provide no protection whatsoever for the cases when the currentPage is set higher the the actual number of pages, so make sure to check it yourself.

smellyshovel commented 2 years ago
  1. Other props

loading (boolean) and loadingColor props allow to set the loading state for the table (by displaying the spinning wheel loading-indicator).

loading


noDataHtml and noDataFilteringHtml respectively set the html-content for the cases when:

  1. No items provided with the items prop at all
  2. No items are found using when the table is filtered using the filter prop.

no items filtering


hideDefaultHeader (boolean) indicates whether to show the default headers for columns.

hide header


footClone (boolean) - whether to clone the headers into the footer.

clone foot

Has no effect if the default header is hidden with the hideDefaultHeader prop.


allowFootSorting - is there's a footer, allow clicking its column headers to sort (and to display the sorting status) the rows.

allow footer sorting


striped (boolean) - to apply the striped styling to the rows (highlights each 2nd row).

striped

smellyshovel commented 2 years ago

Slots

  1. colgroup The contents of this slot is wrapped (if provided) inside the <colgroup> tag, allowing to specify certain column options. Bound to columns (not the prop, but their internal representation: TableColumn[]).

  2. head.prepend Allows to insert custom rows in table's header. Isn't bound to anything.

  3. head Targets all the table's headings. Is bound to column.

  4. head(key) Targets a specific table header by the column's key. Is bound to column

  5. head.append Append custom rows inside table's header.

  6. body.prepend Prepend <tbody> with custom rows.

  7. cell Targets all the cells. Is bound to the current cell (TableCell).

  8. cell(key) Allows to target only cells of a specified by the given key column. Is bound to the current cell.

  9. body.append Appends rows to the table's body.

The following footer-related slots (except for foot.prepend and foot.append) will only work if there's a footClone prop set to some truthy value.

  1. foot.prepend Prepend rows to footer.

  2. foot Target all the headers inside <tfoot>

  3. foot(key) A speecific header in footer. Is bound to the column.

  4. foot.append Append rows to the footer.

When binding a bindable head/foot/cell to a column/cell, here's the structure of exposed objects:

TableColumn:

readonly source: string | ITableColumn;
readonly initialIndex: number;
readonly key;
readonly label;
readonly headerTitle;
readonly sortable;
readonly sortingFn;
readonly alignHead: TAlignOptions;
readonly verticalAlignHead: TVerticalAlignOptions;
readonly align: TAlignOptions;
readonly verticalAlign: TVerticalAlignOptions;

Basically, almost the same as the column definition object you pass to the columns prop.

TableCell:

readonly source: any; // the initial column's value (not stringified)
readonly row: TableRow; // the TableRow instance the cell belongs to
readonly column: TableColumn; // the TableColumn instance the cell belongs to
readonly value: string; // stringified value

And here's the TableRow:

  readonly source: ITableItem; // the initial ITableItem definition (string if the column was defined using the string-syntax)
  readonly initialIndex: number; // initial row position
  readonly cells: TableCell[]; // the array of the row's cells
smellyshovel commented 2 years ago

Alignment

Should be specified on columns (see the columns prop above).

Allows to provide different values for headers and for columns' cells. Supported values for horiz. alignment are: left, center, right, for vert. - top, middle, bottom. Pretty much the same as for text-align and vertical-align css properties.

alignment