An open API service indexing awesome lists of open source software.

https://github.com/uwla/vue-data-table

Vue plugin that adds advanced features to an HTML table.
https://github.com/uwla/vue-data-table

data-table vue vue2 vue3

Last synced: 10 months ago
JSON representation

Vue plugin that adds advanced features to an HTML table.

Awesome Lists containing this project

README

          

# VUE DATA TABLE

`VueDataTable` is a Vue plugin to easily create fully-featured data tables.

* [FEATURES](#features)
* [DEMO](#demo)
* [GETTING STARTED](#getting-started)
* [Installation](#installation)
* [Set up](#set-up)
* [Usage](#usage)
* [Nuxt integration](#nuxt-integration)
* [Laravel integration](#laravel-integration)
* [CONFIGURATION](#configuration)
* [Columns](#columns)
* [Custom Cell Component](#custom-cell-component)
* [Action Buttons](#action-buttons)
* [Editable cells](#editable-cells)
* [Selectable rows](#selectable-rows)
* [Text](#text)
* [Adding Language](#adding-language)
* [Async data](#async-data)
* [Layout](#layout)
* [Custom Components](#custom-components)
* [Footer](#footer)
* [Sort Icon](#sort-icon)
* [Sorting Index Icon](#sorting-index-icon)
* [ROADMAP](#roadmap)
* [LICENSE](#license)
* [CONTRIBUTING](#contributing)

Check out my other plugin, [vue-form-builder](https://github.com/uwla/vue-form-builder),
that automatically generates beautiful forms from declarative rules.

## FEATURES

- Pagination
- Search filter
- Single column sorting
- Multiple column sorting
- Customize every visible text
- Support for multiple languages
- Export data (JSON, CVS, TXT or XLS)
- Action buttons (view, edit, delete)
- Editable cells (edit cell values)
- Custom Vue components to render cells
- Custom Footer to display data summary
- Support for Vue3 and Vue2
- Nuxt integration
- Laravel integration

## DEMO

The best way to see if a package suits your needs is by viewing and testing its
functionalities via a [demo app](https://uwla.github.io/vue-data-table/demo).

There is also this [CodeSandbox Playground](https://codesandbox.io/s/vue3-data-table-demo-c7kfj5)
in which you can edit the source code with live preview.

![Vue Data Table Demo 1](./assets/vdt-1.png)
![Vue Data Table Demo 2](./assets/vdt-2.png)
![Vue Data Table Demo 3](./assets/vdt-3.png)

## GETTING STARTED

### Installation

```shell
npm install @uwlajs/vue-data-table
```

Make sure to install version `2.0.0` or above for Vue3.

Versions prior to `2.0.0` are for Vue2. Checkout the `vue2` branch for its documentation.

### Set up

```javascript
import VueDataTable from "@uwlajs/vue-data-table";
Vue.component("vue-data-table", VueDataTable);
```

Don"t forget to add the style sheets

```javascript
import "@uwlajs/vue-data-table/dist/VueDataTable.css";
```

### Usage

```vue



export default {
computed: {
bindings() {
return {
columns: [/*the columns*/]
data: [/*the data*/]
/* other props...*/
}
}
},
}

```

**Note** Notice that v-bind will take all key-value pairs in the object (in this
case, the `bindings`), and pass them as props to the `VueDataTable.` So, this is
a shortcut to pass multiple props at once.

### Nuxt integration

Create a file `@/plugins/vue-data-table.js`, or whatever name you wish, with the following content:

```javascript
import VueDataTable from '@uwlajs/vue-data-table'
import '@uwlajs/vue-data-table/dist/style.css'

export default defineNuxtPlugin(nuxtApp => {
nuxtApp.vueApp.use(VueDataTable)
})
```

Nuxt automatically loads the files in the `plugins/` directory by default.

### Laravel integration

This plugin integrates with Laravel's pagination API, so it fetches data
asynchronously from the provided URL. Follow the instrunctions in the
[async data section](#async-data) for a detailed setup.

## CONFIGURATION

Only `columns` are required. Other props are optional.

If `data` is not passed, then `fetchUrl` and `fetchCallback` *must* be passed.

`vKey` is not required but is **highly** recommend to set it if you plan to
add or delete rows in the table!

| prop | type | default | description |
| --------------------- | ------------------ | --------------------------------- | ------------------------------------------------------------------------------------------ |
| allowedExports | `Array` | `["csv", "json", "txt"]` | Formats the user can export the data to. Allowed values: `csv`, `json`, `txt`, `xlsx` |
| data | `Array` | - | Array of objects with the data to be displayed on the table |
| columns | `Array` | - | Array of objects to specify how to render each column. Optional if `columnKeys` is set |
| columnKeys | `Array` | - | Array of strings matching the object keys in `data`. Discarded if `columns` is set |
| lang | `String` | `en` | The default language |
| perPageSizes | `Array` | [10, 25, 50, 100, '*'] | The options for the number of rows being displayed per page. The string '*' shows all. |
| defaultPerPage | `Number` | 10 | The default number of entries. If unset, then it will be the first value of `perPageSizes` |
| fetchUrl | `String` | - | The URL to fetch data from if `data` is null |
| fetchCallback | `String` | - | Async function which takes an URL and returns `data` matching Laravel's pagination API |
| isLoading | `Bool` | `false` | Whether table data is loading. Table rows are shown only if this value is set to `false` |
| loadingComponent | `String`, `Object` | - | VueJS component to be shown if `isLoading` is set to `true` |
| showPerPage | `Bool` | `true` | Whether to show the `PerPage` component |
| showEntriesInfo | `Bool` | `true` | Whether to show the `EntriesInfo` component |
| showSearchFilter | `Bool` | `true` | Whether to show the `SearchFilter` component |
| showPagination | `Bool` | `true` | Whether to show the `Pagination` component |
| showDownloadButton | `Bool` | `true` | Whether to show the button to download the table's data |
| tableClass | `String` | `table table-striped table-hover` | The table's HTML `class` attribute |
| sortingMode | `String` | `multiple` | Whether to sort a single column or multiple columns at once |
| sortingIndexComponent | `String`, `Object` | `VdtSortingIndex` | VueJS component for the sorting index on sortable columns |
| sortingIconComponent | `String`, `Object` | `VdtSortingIcon` | VueJS component for the sorting icon on sortable columns |
| footerComponent | `String`, `Object` | `null` | VueJS component for custom table footer |
| vKey | `String` | - | The `v-key`, the key in `data` used by Vue to track and distinguish array elements. |

### Columns

| key | type | default | description |
| --------------- | ------------------ | ---------------- | --------------------------------------------------------- |
| key | `String` | - | The object field to be displayed in a table cell |
| title | `String` | `titleCase(key)` | The title displayed in the header |
| searchable | `Bool` | `true` | Whether to allow searching rows by this column field |
| sortable | `Bool` | `true` | Whether to allow sorting the data by this column field |
| editable | `Bool` | `true` | Whether the column is editable by the user |
| collapsible | `Bool` | `false` | Whether the column is collapsible (expand and collapse) |
| type | `String` | `string` | Data type of `key`. Allowed values: `string`, `number` |
| compareFunction | `Function` | - | Custom function provided by the user to sort the column |
| searchFunction | `Function` | - | Custom function provided by the user to search the column |
| index | `Number` | 1000 | Lower values shift the column to the left of the table |
| component | `String`, `Object` | - | Custom Vue component to render inside table cell |
| componentProps | `Object` | - | Props to pass to the custom component |

If `columns` is not defined, then `columnKeys` must be defined and it will be
mapped to a `columns` array with the default parameters. Example:

```javascript
// we can define the columns
config = {
data: users,
columns: [
{
key: "name",
},
{
key: "email",
title: "Email Address",
sortable: false,
},
{
key: "phone",
sortable: false,
searchable: false,
index: 1, // smaller indexes means the column is shift to the left
},
{
key: "permissions",

/* custom function sort users by which user has more permissions */
compareFunction: function(a, b) {
// permissions is an array
return a.permissions.length - b.permissions.length;
},

/* custom function to allow searching the permission array */
searchFunction: function(search, data) {
return data.permissions.some(permission => permission.includes(search))
},

searchable: true,

/* custom component to display the permissions */
component: UserPermissionList,
}
]
}

// or use columnKeys shortcut
config = {
data: user,
columnKeys: ["name", "email", "registered_at", "last_access_at"]
},

// which will take the default column and map the array into this
[
{
key: "name",
title: "Name",
sortable: true,
searchable: true,
index: 1000
},
{
key: "email",
title: "Email",
sortable: true,
searchable: true,
index: 1000
},
{
key: "registered_at",
title: "Registered At",
sortable: true,
searchable: true,
index: 1000
},
{
key: "last_access_at",
title: "Last Access At",
sortable: true,
searchable: true,
index: 1000
},
]
```

#### Custom Cell Component

Custom cell components must have a `data` property to receive the data of the current
row for the component to display it.

In the previous code snippet, we used our custom component `UserPermissionList`.
Below is a sample of that custom component.

```vue


List of permissions for the user {{ data.name }} :


  • {{ permission }}


export default {
name: "UserPermissionList",
props: {
data: {
type: Object,
required: true
}
}
}

```

To handle events triggered by a custom component (such as clicking a button in a
component), the component should emit an event called `userEvent` and pass an
arbitrary payload to it. The event will be propagated upwards by `VueDataTable`,
which will also emit an event called `userEvent` whose payload is the same as
the one emitted by the custom component. For example:

```vue

export {
name: 'CheckboxCell',
data() {
return {
value: false,
}
},
methods: {
toggleChecked() {
const payload = {
id: this.data.id,
checked: this.value,
}
this.$emit('userEvent', payload)
}
},
props: {
data: Object,
}
}

```

When the users clicks the checkbox, it will emit an `userEvent` event, which can
be accessed from the `VueDataTable`. Here is an continuation of the previous
example.

```vue


DASHBOARD



DELETE SELECTED ROWS


export default {
data() {
return {
data: [/**/],
columns: [/**/],
selectedRows: [],
}
},
methods: {
handleEvent(payload) {
const { checked, id } = payload
if (checked === true) {
if (! this.selectedRows.includes(id))
this.selectedRows.push(id)
} else {
this.selectedRows = this.selectedRows.filter(rowId => rowId !== id)
}
},
deleteRows() {
this.data = this.data.filter(row => ! this.selectedRows.includes(row.id))
this.selectedRows = []
}
}
}

```

In the code snippet above, when the user checks the checkbox rendered by the
custom component `CheckboxCell`, it will emit an event that is handled by the
method `handleEvent`. This method will add/remove the `id` of the row to/from
the `selectedRows` array. When the user clicks the "dangerous delete button", it
will deleted the selected rows from the table (on the client side only).

#### Action Buttons

`VueDataTable` provides a component called `VdtActionButtons`, which can be used
to display buttons for common CRUD action such as viewing, editing, deleting.

Here is an example with all buttons (view, edit, delete) in one column:

```vue


DASHBOARD



import { VdtActionButtons } from '@uwlajs/vue-data-table'
export default {
data() {
return {
params: {
data: users,
columns: [
{ key: 'name' },
{ key: 'job' },
{ component: VdtActionButtons, title: "actions" },
],
},
}
},
methods: {
handleUserEvent(payload) {
console.log(payload.action, payload.data.name)
}
}
}

```

Another example, this time one button per column:

```vue


DASHBOARD



import { VdtActionButtons } from '@uwlajs/vue-data-table'
export default {
data() {
return {
params: {
data: users,
columns: [
{ key: 'name' },
{ key: 'job' },
{
title: "view"
component: VdtActionButtons,
componentProps: { actions: ["view"] },
},
{
title: "edit"
component: VdtActionButtons,
componentProps: { actions: ["edit"] },
},
{
title: "delete"
component: VdtActionButtons,
componentProps: { actions: ["delete"] },
},
],
},
}
},
methods: {
handleUserEvent(payload) {
console.log(payload.action, payload.data.name)
}
}
}

```

When an user click an action button, `VueDataTable` will emit an event whose
payload is an object with two fields: `action` and `data`. The `action` is the
name of the action (view, edit, delete) and `data` is the data of the row.

Check out the demo to see a real working example of using action buttons.

#### Editable cells

It is possible to make a column editable by settings `editable` to true in the
column definition.

```javascript
columns: {
[ 'key': name, editable: true],
[ 'key': email, editable: true],
[ 'key': phone, editable: true],
// ...
}
```

This will make `VueDataTable` display an `edit` button on the right side of the
cell's text. When the user clicks the button, it will show an input, so the user
can enter a new value for the cell. The user can cancel the editing or confirm.
If the user confirms editing, `VueDataTable` will emit a `userEvent` whose
payload looks like the following:

```javascript
{
action: 'updateCell',
key: '',
data: '',
value: '',
}
```

Where `key` is the key of the column (if user edits the `name` column, the `key`
will be `name`), the `data` is the object of the row which was edit (an example:
`{ id: 100, name: 'joe', email: 'joe@email.test' }`), and `value` is the value
inserted by the user (such as `Joe Doe`).

It is up to the developer to handle the event to update the row by, for example,
sending an AJAX request to the API, then updating the `data` array on the client
side. Here is an example of how to update the data array on the client side:

```vue

export default {
/* ... */

methods: {
handleUserEvent(payload) {
if (payload.action === 'updateCell')
{
// send some ajax request
// ...

// then update the cell
this.updateDataCell(payload.data, payload.key, payload.value)

} else {
// some other event
}
},
updateDataCell(row, field, value) {
let ind = this.data.findIndex(r => r.id === row.id)
if (ind < 0) return
let newRow = {... this.data[ind]}
newRow[field] = value
this.data.splice(ind, 1, newRow)
},
}
}

```

#### Selectable rows

`VueDataTable` provides the built-in `vdt-cell-selectable` component to select
table rows.

```javascript
const props = {
columns = [
{
title: "",
component: "vdt-cell-selectable" // <-- ADD THIS
},
{ key: "name" },
{ key: "email" },
/* ... */
],
vKey = "id",
};
const data = [
{ id: 1, name: "joe", email: "joe@example.com" },
/* ... */
]
```

When the user toggles the checkbox, `VueDataTable` emits an event called
`userEvent` with the following payload:

```javascript
{
action: "select",
selected: true || false, // this is the current value of the checkbox
data: {}, // this is the current row (example, a user from an users array)
}
```

You can have a reactive variable to keep track of selected items:

```javascript
const selected = ref([]);

const handleSelect(payload) {
const item = payload.data;
if (payload.selected) {
selected.value.push(item);
} else {
selected.value = selected.value.filter((x) => x.id !== item.id);
}
}
```

You can use this variable to perform bulk operations, such as mass deletion or
mass edition.

### Text

Currently, `VueDataTable` has support for the following languages: English (en),
Brazilian Portuguese (pt-br), and Spanish(es). The `lang` prop specifies in
which language to display the text in our table.

If we want to add a custom text (maybe because there is no language support or
because we want something else), we have to set it in the `text` prop.

The following table shows the texts we can customize and their default values
for the English language.

| key | default |
| --- | --- |
| perPageText | "Show :entries entries" |
| perPageAllText | "ALL" |
| infoText | "Showing :first to :last of :total entries" |
| infoAllText | "Showing all entries" |
| infoFilteredText | "Showing :first to :last of :filtered (filtered from :total entries)" |
| nextButtonText | "Next" |
| previousButtonText | "Previous" |
| paginationSearchText | "Go to page" |
| paginationSearchButtonText | "GO" |
| searchText | "search:" |
| downloadText | "export as:" |
| downloadButtonText | "DOWNLOAD" |
| emptyTableText | "No matching records found" |

**Note**: Notice that the placeholders `:first`, `:last`, `:total`, and
`:filtered` will be automatically replaced with the proper numbers.

Example code:

```javascript
parameters() {
return {
data: [/**/],
columns: [/**/],
text: {
PerPageText: "Number of users per page :entries",
infoText: "Displaying :first to :last of :total users",
emptyTableText: "No users found :(",
}
}
}
```

#### Adding Language

If your language is not yet supported, you can add a new language and use it in
any `VueDataTable` instance as follow:

```javascript
import { languageServiceProvider } from "@uwlajs/vue-data-table";

const loremIpsumLanguage = {
perPageText: "lorem ipsum",
nextButtonText: "labore ipsum",
/* more ... */
};

languageServiceProvider.setLang("lorem", loremIpsumLanguage)
```

You can also change any default text for an existing language and that will
reflect the changes globally. For example:

```javascript
// the default text for the download button in the export component is "export as"
// we may want change that to "download as"
languageServiceProvider.setLangText("en", "downloadText", "download as:")
```

### Async data

If you do not want to fetch all data at once and pass it to `VueDataTable` via
the `data` prop, you can do so by defining:

- `fetchUrl`: initial endpoint for the first ajax request to fetch data
- `fetchCallback`: async function which takes an URL and returns a response
following Laravel's pagination API.

Here is a sample `fetchCallback`:

```vue

Users


const vdtProps = {
columns: [
{ key: 'name' },
{ key: "email", title: "Email address" },
],
fetchUrl: "http://app.test/api/users",
fetchCallback: async (url) => fetch(url).then(response => response.json())
}

```

The example above uses the browser's built-in `fetch`, but you can also use
`axios` or Nuxt's `$fetch` under the hood. Just make sure the response returned
by the callback matches the following.

The response from the `fetchCallback` should look like this:

```jsonc
{
"data": [
{ "id": 1, "name": "Miss Juliet Heidenreich", "email": "alvera13@example.org"},
{ "id": 2, "name": "Heloise Boehm", "email": "joany.feil@example.net"},
{ "id": 3, "name": "Antwon Collins", "email": "xhills@example.com},
/* ... */
],
"current_page": 1,
"first_page_url": "http://app.test/api/users?page=1",
"from": 1,
"last_page": 23,
"last_page_url": "http://app.test/api/users?page=23",
"links": [
{ "url": null, "label": "« Previous", "active": false },
{ "url": "http://app.test/api/users?page=1", "label": "1", "active": true },
{ "url": "http://app.test/api/users?page=2", "label": "2", "active": false },
{ "url": "http://app.test/api/users?page=3", "label": "3", "active": false },
/* ... */
{ "url": "http://app.test/api/users?page=23", "label": "23", "active": false },
{ "url": "http://app.test/api/users?page=2", "label": "Next »", "active": false }
],
"next_page_url": "http://app.test/api/users?page=2",
"path": "http://app.test/api/users",
"per_page": 15,
"prev_page_url": null,
"to": 15,
"total": 340
}
```

Here is how you do so in Laravel:

```php
allowedSorts(['name', 'email'])
->allowedFilters(['name', 'email'])
->paginate();
});
```

The endpoints look like this:

- `http://app.test/api/users?page=1&filter[name]=foo`
- `http://app.test/api/users?page31&sort=job,-email`
- `http://app.test/api/users?page=1&sort=email&filter[email]=joe&filter=[name]=joe`

You do **not** need to worry about the URLs if you are using Spatie's Laravel Query Bulder,
because `VueDataTable` follows their endpoint standard and automatically generates the urls.

If you do not use their package, then you should parse the `url` variable inside
the `fetchCallback`, and modify the url. For example, your javascript code should modify:

`http://app.test/api/users?page=4&filter[name]=foo --> http://app.test/api/users?page=4&search=foo`.

Keep in mind that, by default, Spatie's Query Builder apply AND logic for all
filters. That means if you have `&filter[name]=Ana&filter[email]=Ana`, then
you will only get results that both `name` and `email` fields match Ana. If
`name` matches Ana but not the `email` column, then this row would not appear.

Here is how you can implement `OR` logic using their package:

```php
allowedSorts(['name', 'email'])
->allowedFilters([
AllowedFilter::custom('name', new FilterOrWhere),
AllowedFilter::custom('email', new FilterOrWhere)
])
->paginate();
});

// app/Http/Filters/FilterOrWhere.php
namespace App\Http\Filters;

use Spatie\QueryBuilder\Filters\Filter;
use Illuminate\Database\Eloquent\Builder;

class FilterOrWhere implements Filter
{
public function __invoke(Builder $query, $value, string $property)
{
$query->orWhere($property, 'LIKE', '%' . $value . '%');
}
}
```

### Layout

`VueDataTable` uses CSS's grid display to specify the position of its components
(search filter, pagination, entries info, per page options, download button).

**We can specify the position of the components by including our custom CSS/SCSS
and overriding the defaults.**

By default, this is how `VueDataTable` displays the components:

```scss
.data-table {
display: grid;
width: 100%;
grid-template-columns: 25% 25% 25% 25%;
&> div {
margin-top: 1rem;
max-width: 100%;
}
& > .data-table-search-filter, .data-table-pagination, .data-table-export-data {
margin-left: auto
}
@media (min-width: 1401px) {
grid-template-areas:
"perPage search search search"
"table table table table"
"info pagination pagination download";
}
@media (min-width: 1051px) AND (max-width: 1400px) {
grid-template-areas:
"perPage search search search"
"table table table table"
"info pagination pagination pagination"
". . download download";
}
@media (min-width: 851px) AND (max-width: 1050px) {
grid-template-areas:
"perPage search search search"
"table table table table"
"pagination pagination pagination pagination"
"info info download download";
}
@media (max-width: 800px) {
& > .data-table-pagination {
flex-wrap: wrap;
}
}
@media (min-width: 651px) AND (max-width: 850px) {
grid-template-areas:
"perPage search search search"
"table table table table"
"pagination pagination pagination pagination"
"info info info info"
"download download download download";
}
@media (max-width: 650px) {
grid-template-areas:
"search search search search"
"perPage perPage perPage perPage "
"table table table table"
"pagination pagination pagination pagination"
"info info info info"
"download download download download";
& > .data-table-per-page {
margin-left: auto
}
}
}
```

Feel free to copy the styles above, modify it, and then set the position of the
components as you want.

### Custom Components

Besides a custom component for each column, you provide custom components for
the table's footer, the column's `sorting icon` (the icon displayed if the
columns is sorted), and the column's `sorting index` (the index of the current
column if it is being sorted and multi column sorting is enabled).

#### Footer

The property `footerComponent` sets the component to render the table's footer.
The component can be either the component `Object`, or a `String` equals to the
name of the registered component.

The `footerComponent` must be a `` HTML element and it must have the
properties `data`, `dataDisplayed`, `dataFiltered`. If the component does not
specify those properties in `props`, Vue will probably think they are some
custom HTML attribute and their values will be show as HTML attributes, which is
really messy.

The property `data` correspond to all data passed to `VueDataTable`. The
`dataDisplayed` corresponds to all data that is currently visible on the table.
The `dataFiltered` corresponds to all data that was filtered by a search query.
These properties can be used to perform common operations such as calculating
the sum of the values of the total rows of a certain column.

Suppose we have a table that of fruits. The `data` is an array of objects whose
properties are `name`, `price`, and `amount`. We can provide a custom footer to
show the total amount of fruits bought and the total price.

The footer component would be something like:

```vue


Total

{{ totalAmount }}
{{ totalPrice }}

export default {
name: "TableFooter",
computed: {
totalPrice() {
let s = 0;
for (let f of this.dataDisplayed)
s += f.price * f.amount;
return s;
},
totalAmount() {
let s = 0;
for (let f of this.dataDisplayed)
s += f.amount;
return s;
}
},
props: {
data: Array,
dataDisplayed: Array,
dataFiltered: Array,
}
}

```

And we pass this component as follow:

```vue

import TableFooter from './TableFooter.vue'

export default {
/* ... some code */
data() {
return {
tableProps: {
columns: [ /* ... code */ ],
data: [ /* ... more code */ ],
footerComponent: TableFooter,
}
}
}
}

```

Alternately, you can register the component and pass a string:

```javascript
/* early on */
import TableFooter from './TableFooter.vue'
Vue.component("table-footer", TableFooter)

/* later on */
footerComponent: "table-footer"
```

#### Sort Icon

By default, `VueDataTable` will display arrows to indicate the sorting direction
when sorting a column. The `SortingIcon` component is wrapped in a `th` element.
The `th` element has a `data-sorting` attribute that may be `asc` or `desc`
only. Based on this value, we display an `arrow_up` or an `arrow_down` icon
using `CSS` rules.

```vue

 

.data-table-sorting-icon {
&::after {
content: "\2193";
}
&::before {
content: "\2191";
}
&::after, &::before {
opacity: 0.5;
}
[data-sorting="asc"] &::before, [data-sorting="desc"] &::after {
opacity: 1;
}
}

```

**Note**: Some code was omitted to keep it clean.

If we want to add our custom icons for this, then we can register our component,
like so:

```javascript
import SortingIcon from "./path/to/SortIcon.vue";

export default {
computed: {
bindings() {
return {
SortingIconComponent: SortingIcon,
data: [],
/**/
};
}
}
}
```

#### Sorting Index Icon

When sorting multiple columns, `VueDataTable` will display an icon with a index
indicating which column has the priority in the sorting process.

```vue


{{ index }}

```

If we want to add our own component for this, we can register it just like we
did before.

```javascript
import SortingIndex from "./path/to/SortingIndex.vue";

export default {
computed: {
bindings() {
return {
SortingIndexComponent: SortingIndex,
data: [],
/**/
};
}
}
};
```

In our `SortingIndex` component, we must have a `index` property, which
correspondent to the index of the column in the sorting process.

```javascript
export default {
name: "SortingIndex",
props: {
index: {
type: Number,
required: true
}
}
};
```

## ROADMAP

- [x] Support for Vue3
- [x] Laravel integration
- [ ] Support for SSR
- [ ] String notation for defining columns

## LICENSE

MIT

## CONTRIBUTING

Pull requests are very welcome.