What we learned in previous article is simple search within the data which is already available to client; let’s up the ante and employ Fetch API to make AJAX calls from within Alpine.js in order to dynamically update the data table from backend database.
Laravel 9.x requires a minimum PHP version of 8.0. This tutorial assumes that you are using Linux, MacOS or WSL on Windows and Node.js, Composer and PHP 8 along with required modules i.e bcmatch, sqlite, mbstring, xml, zip, gd, mcrypt are properly installed. Also make sure that SQLite is installed as we’ll be using SQLite to keep things simple and quick; you may use MySQL or PostgreSQL as you like, however make sure the corresponding PHP module is installed too.
Using Composer, enter following command in terminal to install Laravel 9;
composer create-project laravel/laravel app 9.*
Now go inside newly created app directory by executing;
cd app
A simple way to add Alpine.js in Laravel 9 app is by installing default Breeze starter kit which will incorporate basic authentication to your Laravel app along with adding Alpine.js as dependency.
So, using Composer, add default Breeze package by executing following command;
composer require laravel/breeze:* --dev
Now install Breeze package by executing;
php artisan breeze:install
The above command will also install required node modules and compile the UI assets for the first time.
Next, setup database and Laravel environment file. Here, I’m configuring SQLite database for simplicity purposes.
Being in parent directory (app), execute following command in order to create new SQLite database file;
touch database/database.sqlite
Then find .env config file in the parent directory, open it and amend DB_CONNECTION line as follows;
DB_CONNECTION = sqlite
You can remove DB_HOST, DB_PORT, DB_DATABASE, DB_USERNAME, DB_PASSWORD from .env when using SQLite database.
Save and close .env file. Now enter following command in order to define database structure:
php artisan migrate
In order to populate database table quickly, let’s enter following command to invoke tinker;
php artisan tinker
And then enter following command to create 100 users;
User::factory(100)->create()
The above command will utilize database\factories\UserFactory.php to generate fake data for app testing.
Note down any of the generated email addresses for logging in later. Now type ‘quit’ and press Enter to exit tinker.
Next, create a UserController by entering the following command:
php artisan make:controller UserController
The above command will create app/Http/Controllers/UserController.php file. Then put the following code inside this newly generated UserController.php;
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\User;
class UserController extends Controller
{
public function index()
{
$users = User::all();
return view('users.index', compact('users'));
}
public function findUsers($param=null)
{
$users = null;
if($param) {
$users = User::where('name', 'LIKE', '%'.$param.'%')->get();
}
else {
$users = User::all();
}
return response()->json($users);
}
}
Now, edit routes/web.php by importing UserController at the upper side and then inserting a couple of routes as follows:
// ...
use App\Http\Controllers\UserController;
// ...
Route::get('/users', [UserController::class, 'index'])
->middleware('auth');
Route::get('/findUsers/{param?}', [UserController::class, 'findUsers'])
->middleware('auth');
Next, create directory named users inside resources/views directory. If you are in parent directory (app), you may execute following command to make this directory;
mkdir resources/views/users
Now create a file named index.blade.php inside resources/views/users directory. You can execute following command inside parent directory (app) in order to create this file;
touch resources/views/users/index.blade.php
Insert following code inside index.blade.php;
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
{{ __('Users') }}
</h2>
</x-slot>
<div
x-data="{
search: '',
users: {{$users}},
async fetchUsers() {
let res = await fetch('/findUsers/'+this.search);
this.users = await res.json();
},
}"
>
<div class="py-6">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-4 bg-white border-b border-gray-200">
<input x-model="search" @input.debounce.500ms="fetchUsers" class="mb-2 p-1 border border-gray-400 rounded-md" placeholder="Search...">
<table class="shadow-lg">
<thead>
<tr class="bg-gray-400 text-white font-extrabold">
<td class="px-4 py-1 text-center">ID</td>
<td class="px-4 py-1">Name</td>
<td class="px-4 py-1">Email</td>
</tr>
</thead>
<tbody>
<template x-for="user in users" :key="user.id">
<tr>
<td x-text="user.id" class="border px-4 py-1 text-center"></td>
<td x-text="user.name" class="border px-4 py-1"></td>
<td x-text="user.email" class="border px-4 py-1"></td>
</tr>
</template>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</x-app-layout>
Here, we have used x-data attribute of Alpine.js in order to define two data properties i.e., search, users and a function fetchUsers() respectively. search field is kept empty initially whereas users field carries the $users data as passed on by UserController‘s index function. fetchUsers() function triggers whenever there is a change in search input element and sends AJAX call to backend from where updated data is returned. This updated data only contains those records where name contains the value which exists in search field; if the search field is null, fetchUsers() function returns all records just like as index function.
We utilized x-model attribute in order to synchronize the search data field with search input element in two-way binding i.e., if one changes, the other one changes too simultaneously. Finally, we used x-for along with x-text attributes within <template> tags in order to iterate through records within users property.
There are couple of other noteworthy things: first, we’ve used {param?} in the findUsers route in routes/web.php. ? after param signifies that the param is optional, so that param may have a value or null value – suppose if we type something in search field and then remove it, then this will be taken as null. That’s why findUsers() function in UserController has if-else condition whereof if the param has null value (which is also specified as default value in function definition) then all the records are returned just like index function.
Second, we have used @input.debounce.500ms with function call in search input element. This will make sure that there would be a time interval of 500 milliseconds between sending AJAX calls – it’s necessary in order to avoid flooding server with a number of unnecessary AJAX calls. You may increase from 500ms to 5000ms for fun and observe the 5 seconds delay between AJAX calls. Obviously that’s not advisable either : )
Now execute following command in parent directory to compile UI assets:
npm run build
Finally, execute following command in parent directory to run PHP development server;
php artisan serve
Open browser and enter following URL in address bar;
http://localhost:8000
Login with the noted email address as above (just remember, the default password for all users generated through above factory is ‘password’). After logging in, enter following URL to list all users and then use search input field to filter records;
http://localhost:8000/users
Again, empty the field and see the results.
Good post!
great issues altogether, you simply received
a new reader. What could you recommend about your
submit that you simply made some days ago? Any certain?