Skip to content

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:

template
<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
<?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:

json
{
  "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": "&laquo; 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 &raquo;",
      "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.

vue
<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):

IDNameActions
1Customer One
2Customer Two
« Previous
12Next »

AppDataTable Props:

NameTypeDefaultDescription
headersArray[]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

NameDescription
TableHeadThe Head of the AppDataTable.
TableBodyThe 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:

template
<AppDataSearch
    v-if="customers.data.length || route().params.searchTerm"
    :url="route('customer.index')"
    fields-to-search="name"
/>
vue
<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:

NameTypeDefaultDescription
urlString-The route path that loads the resource, usually your Module's Controller index method.
fieldsToSearchString-Comma-separated list of the fields to be searched in your Module's database table.
additionalParamsObject{}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:

template
<AppDataTable>
    <template #TableHead>
        <AppDataTableHead :headers="headers" />
    </template>
</AppDataTable>
vue
<script setup>
const headers = ['ID', 'Name', 'Actions']
</script>

The rendered Table Head will look like this:

IDNameActions

AppDataTableHead Props:

NameTypeDefaultDescription
headersArray[]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:

template
<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>
vue
<script setup>
const props = defineProps({
  customers: {
    type: Object,
    default: () => {}
  }
})

const headers = ['ID', 'Name', 'Actions']
</script>

The rendered result will look like this:

IDNameActions
1Customer One
2Customer Two

AppTableRow Slots

NameDescription
defaultThe 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:

template
<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>
vue
<script setup>
const props = defineProps({
  customers: {
    type: Object,
    default: () => {}
  }
})

const headers = ['ID', 'Name', 'Actions']
</script>

The rendered result will look like this:

IDNameActions
1Customer One
2Customer Two

AppDataTableData Slots

NameDescription
defaultThe 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:

template
<AppPaginator
    :links="customers.links"
    class="mt-4 justify-center"
></AppPaginator>
vue
<script setup>
const props = defineProps({
  customers: {
    type: Object,
    default: () => {}
  }
})
</script>

Your Controller in modules/Customer/Http/Controllers/CustomerController.php will look like this:

php
<?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:

json
{
  "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": "&laquo; 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 &raquo;",
      "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):

« Previous
12Next »

AppPaginator Props:

NameTypeDefaultDescription
linksArray[]An array of Objects containing the links to build the paginator, like [{'url': '/customer?page=1', label: '1', active: true}].