Datatable
The resources/js/Components/DataTable
directory contains Vue components related to the AppDataTable
Vue component, which is responsible for rendering tabular data, usually sourced from a database.
AppDataTable
The AppDataTable
Vue component is generally used with the following components:
AppDataSearch
: Responsible for rendering the search bar.AppDataTableHead
: Responsible for rendering the table head.AppDataTableRow
: Responsible for rendering the table rows.AppDataTableData
: Responsible for rendering the table data.AppPaginator
: Responsible for rendering the pagination links, if applicable.
The AppDataTable
component can be used as follows:
<AppDataSearch
v-if="customers.data.length || route().params.searchTerm"
:url="route('customer.index')"
fields-to-search="name"
></AppDataSearch>
<AppDataTable v-if="customers.data.length" :headers="headers">
<template #TableBody>
<tbody>
<AppDataTableRow
v-for="customer in customers.data"
:key="customer.id"
>
<AppDataTableData>
{{ customer.id }}
</AppDataTableData>
<AppDataTableData>
{{ customer.name }}
</AppDataTableData>
<AppDataTableData>
<AppButton
class="btn btn-icon btn-primary"
@click="
$inertia.visit(
route(
'customer.edit',
customer.id
)
)
"
>
<i class="ri-edit-line"></i>
</AppButton>
</AppDataTableData>
</AppDataTableRow>
</tbody>
</template>
</AppDataTable>
<AppPaginator
:links="customers.links"
class="mt-4 justify-center"
></AppPaginator>
<AppAlert v-if="!customers.data.length" class="mt-4">
No customers found.
</AppAlert>
Your Controller in modules/Customer/Http/Controllers/CustomerController.php
will look like this:
<?php
namespace Modules\Customer\Http\Controllers;
use Modules\Support\Http\Controllers\BackendController;
use Modules\Customer\Models\Customer;
use Inertia\Response;
class CustomerController extends BackendController
{
public function index(): Response
{
$customers = Customer::orderBy('id')
->search(request('searchContext'), request('searchTerm'))
->paginate(request('rowsPerPage', 15))
->withQueryString()
->through(fn ($customer) => [
'id' => $customer->id,
'name' => $customer->name
]);
return inertia('Customer/CustomerIndex', [
'customers' => $customers
]);
}
}
The customers
prop that is passed to resources/js/Pages/Customer/CustomerIndex.vue
, which uses the AppDataTable
component, will look like this:
{
"current_page": 1,
"data": [
{ "id": 1, "name": "Customer One" },
{ "id": 2, "name": "Customer Two" }
],
"first_page_url": "/customer?page=1",
"from": 1,
"last_page": 2,
"last_page_url": "/customer?page=2",
"links": [
{ "url": null, "label": "« Previous", "active": false },
{
"url": "/customer?page=1",
"label": "1",
"active": true
},
{
"url": "/customer?page=2",
"label": "2",
"active": false
},
{
"url": "/customer?page=2",
"label": "Next »",
"active": false
}
],
"next_page_url": "/customer?page=2",
"path": "/customer",
"per_page": 2,
"prev_page_url": null,
"to": 2,
"total": 3
}
Note that the customers
prop contains the customers' data in the data
key, along with pagination-related information in other keys.
<script setup>
import { ref } from 'vue'
const props = defineProps({
customers: {
type: Object,
default: () => {}
}
})
const headers = ['ID', 'Name', 'Actions']
</script>
The rendered table will look like this (search, pagination links, and edit button are disabled in this example):
AppDataTable Props:
Name | Type | Default | Description |
---|---|---|---|
headers | Array | [] | An array containing the header names for the table. This can be used as an alternative (shorter syntax) to the more versatile TableHead slot of the AppDataTable. |
AppAlert Slots
Name | Description |
---|---|
TableHead | The Head of the AppDataTable. |
TableBody | The Body of the AppDataTable. |
AppDataSearch
The AppDataSearch
Vue component is responsible for rendering the search feature related to the Data Table. It is designed to be integrated into your AppDataTable
Vue Component and your Laravel backend with minimal code. The AppDataSearch
component can be used as follows:
<AppDataSearch
v-if="customers.data.length || route().params.searchTerm"
:url="route('customer.index')"
fields-to-search="name"
/>
<script setup>
const props = defineProps({
customers: {
type: Object,
default: () => {}
}
})
</script>
Note that the customers
prop is an instance of the Illuminate\Pagination\LengthAwarePaginator
class. So, when it's passed to the AppDataTable
component, the customers' data will be available through the customers.data
path inside the template
.
All the work of communicating with the backend is handled by the resources/js/Composables/useDataSearch.js
composable, so there's no need to worry about it. This composable assists with:
- Sending the search request to the backend.
- Debouncing the search input to avoid unnecessary requests.
- Handling the search input state and the clear search button.
- Preventing issues with back button navigation and searching.
AppDataSearch Props:
Name | Type | Default | Description |
---|---|---|---|
url | String | - | The route path that loads the resource, usually your Module's Controller index method. |
fieldsToSearch | String | - | Comma-separated list of the fields to be searched in your Module's database table. |
additionalParams | Object | {} | Additional parameters that must be sent with the search request to the backend, for example: { active: true } . |
AppDataTableHead
The AppDataTableHead
Vue component is responsible for rendering the table head of your AppDataTable
Vue component. It's used internally by the AppDataTable
component when the headers
prop is passed to AppDataTable
. The AppDataTableHead
component can be used as follows:
<AppDataTable>
<template #TableHead>
<AppDataTableHead :headers="headers" />
</template>
</AppDataTable>
<script setup>
const headers = ['ID', 'Name', 'Actions']
</script>
The rendered Table Head will look like this:
ID | Name | Actions |
---|
AppDataTableHead Props:
Name | Type | Default | Description |
---|---|---|---|
headers | Array | [] | An array containing the header names for the table. |
AppDataTableRow
The AppDataTableRow
Vue component is responsible for rendering the table rows of your AppDataTable
Vue component. It's used internally by the AppDataTable
component inside the TableBody
slot. Typically, you will use it with a v-for
loop, iterating over the data that comes from the backend. The AppDataTableRow
component can be used as follows:
<AppDataTable v-if="customers.data.length" :headers="headers">
<template #TableBody>
<tbody>
<AppDataTableRow
v-for="customer in customers.data"
:key="customer.id"
>
<AppDataTableData>
{{ customer.id }}
</AppDataTableData>
<AppDataTableData>
{{ customer.name }}
</AppDataTableData>
<AppDataTableData>
<AppButton
class="btn btn-icon btn-primary"
@click="
$inertia.visit(
route(
'customer.edit',
customer.id
)
)
"
>
<i class="ri-edit-line"></i>
</AppButton>
</AppDataTableData>
</AppDataTableRow>
</tbody>
</template>
</AppDataTable>
<AppAlert v-if="!customers.data.length" class="mt-4">
No customers found.
</AppAlert>
<script setup>
const props = defineProps({
customers: {
type: Object,
default: () => {}
}
})
const headers = ['ID', 'Name', 'Actions']
</script>
The rendered result will look like this:
ID | Name | Actions |
---|---|---|
1 | Customer One | |
2 | Customer Two |
AppTableRow Slots
Name | Description |
---|---|
default | The content of the Table Row (<tr> element). |
AppDataTableData
The AppDataTableData
Vue component is responsible for rendering the table data of your AppDataTable
Vue component. It's used internally by the AppDataTableRow
component inside the TableBody
slot. The AppDataTableData
component can be used as follows:
<AppDataTable v-if="customers.data.length" :headers="headers">
<template #TableBody>
<tbody>
<AppDataTableRow
v-for="customer in customers.data"
:key="customer.id"
>
<AppDataTableData>
{{ customer.id }}
</AppDataTableData>
<AppDataTableData>
{{ customer.name }}
</AppDataTableData>
<AppDataTableData>
<AppButton
class="btn btn-icon btn-primary"
@click="
$inertia.visit(
route(
'customer.edit',
customer.id
)
)
"
>
<i class="ri-edit-line"></i>
</AppButton>
</AppDataTableData>
</AppDataTableRow>
</tbody>
</template>
</AppDataTable>
<script setup>
const props = defineProps({
customers: {
type: Object,
default: () => {}
}
})
const headers = ['ID', 'Name', 'Actions']
</script>
The rendered result will look like this:
ID | Name | Actions |
---|---|---|
1 | Customer One | |
2 | Customer Two |
AppDataTableData Slots
Name | Description |
---|---|
default | The content of the Table Data (<td> element). |
AppPaginator
The AppPaginator
Vue component is responsible for rendering the pagination links of your AppDataTable
Vue component. The AppPaginator
component can be used as follows:
<AppPaginator
:links="customers.links"
class="mt-4 justify-center"
></AppPaginator>
<script setup>
const props = defineProps({
customers: {
type: Object,
default: () => {}
}
})
</script>
Your Controller in modules/Customer/Http/Controllers/CustomerController.php
will look like this:
<?php
namespace Modules\Customer\Http\Controllers;
use Modules\Support\Http\Controllers\BackendController;
use Modules\Customer\Models\Customer;
use Inertia\Response;
class CustomerController extends BackendController
{
public function index(): Response
{
$customers = Customer::orderBy('id')
->search(request('searchContext'), request('searchTerm'))
->paginate(request('rowsPerPage', 15))
->withQueryString()
->through(fn ($customer) => [
'id' => $customer->id,
'name' => $customer->name
]);
return inertia('Customer/CustomerIndex', [
'customers' => $customers
]);
}
}
Note that in the Controller, the $customers
variable is an instance of the Illuminate\Pagination\LengthAwarePaginator
class. It contains the customers' data in the data
key, and pagination-related information in other keys.
As mentioned earlier in this documentation, the customers
prop received in the resources/js/Pages/Customer/CustomerIndex.vue
component, which utilizes the AppPaginator
component, will look like this:
{
"current_page": 1,
"data": [
{ "id": 1, "name": "Customer One" },
{ "id": 2, "name": "Customer Two" }
],
"first_page_url": "/customer?page=1",
"from": 1,
"last_page": 2,
"last_page_url": "/customer?page=2",
"links": [
{ "url": null, "label": "« Previous", "active": false },
{
"url": "/customer?page=1",
"label": "1",
"active": true
},
{
"url": "/customer?page=2",
"label": "2",
"active": false
},
{
"url": "/customer?page=2",
"label": "Next »",
"active": false
}
],
"next_page_url": "/customer?page=2",
"path": "/customer",
"per_page": 2,
"prev_page_url": null,
"to": 2,
"total": 3
}
The rendered result will look like this (pagination links are disabled in this example):
AppPaginator Props:
Name | Type | Default | Description |
---|---|---|---|
links | Array | [] | An array of Objects containing the links to build the paginator, like [{'url': '/customer?page=1', label: '1', active: true}] . |