$ laravel new livewire-search && cd livewire-search
$ composer require livewire/livewire
npx
. Alternatively, you can link to Tailwind CSS directly from the CDN. For production usage, the documentation recommends that you set it up as a PostCSS plugin. Generate the Tailwind CSS files by running:$ npx tailwindcss-cli@latest build -o public/css/tailwind.css
tailwind.css
file in the public/css
folder. We can then import it to our Blade templates using HTML <link>
tags as we would any other stylesheet.bio
field and a FULLTEXT index.database/migrations/2014_10_12_000000_create_users_table.php
) and replace its content with the following:<?php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Schema; class CreateUsersTable extends Migration { public function up() { Schema::create('users', function (Blueprint $table) { $table->id(); $table->string('name'); $table->string('email')->unique(); $table->mediumText('bio'); $table->timestamp('email_verified_at')->nullable(); $table->string('password'); $table->rememberToken(); $table->timestamps(); }); DB::statement( 'ALTER TABLE users ADD FULLTEXT fulltext_index(name, email, bio)' ); } public function down() { Schema::dropIfExists('users'); } }
database/factories/UserFactory.php
(feel free to create it if it doesn’t exist) and replace the definition
method with the code block below:<?php public function definition() { return [ 'name' => $this->faker->name, 'email' => $this->faker->unique()->safeEmail, 'bio' => $this->faker->text(200), 'email_verified_at' => now(), 'password' => Hash::make("password"), 'remember_token' => Str::random(10), ]; }
run
method of the database seeder class (/database/seeders/DatabaseSeeder.php
) to the one below.public function run() { \App\Models\User::factory(50)->create(); }
users
table when the seeder is run. Apply the migrations and the seeders by running the set of commands below.$ php artisan migrate && php artisan db:seed
Search
trait that we can use from any Laravel model by adding a $searchable
field to the model. This should represent the fields that have been added to a FULLTEXT index. Then, create a new file, named Search.php
, file in app/Models
, and add the trait implementation shown below:<?php namespace App\Models; trait Search { private function buildWildCards($term) { if ($term == "") { return $term; } // Strip MySQL reserved symbols $reservedSymbols = ['-', '+', '<', '>', '@', '(', ')', '~']; $term = str_replace($reservedSymbols, '', $term); $words = explode(' ', $term); foreach($words as $idx => $word) { // Add operators so we can leverage the boolean mode of // fulltext indices. $words[$idx] = "+" . $word . "*"; } $term = implode(' ', $words); return $term; } protected function scopeSearch($query, $term) { $columns = implode(',', $this->searchable); // Boolean mode allows us to match john* for words starting with john // (https://dev.mysql.com/doc/refman/5.6/en/fulltext-boolean.html) $query->whereRaw( "MATCH ({$columns}) AGAINST (? IN BOOLEAN MODE)", $this->buildWildCards($term) ); return $query; } }
buildWildCards
and scopeSearch
. buildWildCards
cleans up the search term by:+
and *
) to the search term. This helps it to take advantage of MySQL’s boolean mode.scopeSearch
on the other hand is a Laravel local scope (identified by the “scope” prefix). Models are automatically searchable via a static search
method once they:Search
trait.$searchable
array that contains the columns that should be searched e.g the User
model below searches the name
, email
, and bio
columns.Search
trait into the User
model class, and set up the $searchable
fields as shown below:<?php namespace App\Models; /* --- existing code here --- */ class User extends Authenticatable { use HasFactory, Notifiable; use Search; // Use the search trait we created earlier /* --- existing code here --- */ protected $searchable = [ 'name', 'email', 'bio', ]; /* -- rest of user class code --- */ }
@livewireStyles
and @livewireScripts
directive within the <head>
tag, and at the end of the <body>
tag respectively, in your app layout. So, in resources/views/welcome.blade.php
add the code below:<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>@yield('title', 'My cool Livewire App')</title> @livewireStyles </head> <body> <!-- other app content goes here--> @livewireScripts </body> </html>
@livewire
directive or the livewire
tag, e.g:<div> @livewire('search-users') </div> <!-- This also works --> <div> <livewire:search-users /> </div>
app/Http/Livewire
and are automatically generated when you generate a component with php artisan make:livewire
.wire:
attribute. For instance, The snippet below binds the value of the text field to a $name
variable in the component class, and the value of $name
is rendered within the h1
tag as it changes. <input wire:model="message" type="text"> <h1>{{ $message }}</h1>
SearchUser
component by running the artisan command below, in the project folder.$ php artisan make:livewire SearchUser
app/Http/Livewire/SearchUser.php
: The component class that interacts with our database and prepares the data to be rendered.resources/views/livewire/search-user.blade.php
: The component template that holds the UI for the component.app/Http/Livewire/SearchUser.php
) and add the code below to it:<?php namespace App\Http\Livewire; use App\Models\User; use Livewire\Component; class SearchUser extends Component { public $term = ""; public function render() { sleep(1); $users = User::search($this->term)->paginate(10); $data = [ 'users' => $users, ]; return view('livewire.search-user', $data); } }
search
method on the User
model class (which User
inherited from the Search
trait) and paginates the result. The result is then returned with the component template in the same way we would do it from a regular Laravel controller.sleep
call to the code above. This is to delay the code execution to simulate a page load. This delay will help us see Livewire’s loading state in action in our development environment.resources/views/livewire/search-user.blade.php
) and add the code block below to it:<div> <div class="px-4 space-y-4 mt-8"> <form method="get"> <input class="border-solid border border-gray-300 p-2 w-full md:w-1/4" type="text" placeholder="Search Users" wire:model="term"/> </form> <div wire:loading>Searching users...</div> <div wire:loading.remove> <!-- notice that $term is available as a public variable, even though it's not part of the data array --> @if ($term == "") <div class="text-gray-500 text-sm"> Enter a term to search for users. </div> @else @if($users->isEmpty()) <div class="text-gray-500 text-sm"> No matching result was found. </div> @else @foreach($users as $user) <div> <h3 class="text-lg text-gray-900 text-bold">{{$user->name}}</h3> <p class="text-gray-500 text-sm">{{$user->email}}</p> <p class="text-gray-500">{{$user->bio}}</p> </div> @endforeach @endif @endif </div> </div> <div class="px-4 mt-4"> {{$users->links()}} </div> </div>
$term
variable in the component class to the search field. We’ve also used wire:loading
to show the div
when our data is loading. Finally, we used wire:loading.remove
to hide the div containing the search results when loading.welcome.blade.php
file that was automatically generated by Laravel, and rendering the component within the welcome
template. While we’re at it, we will also include the Tailwind CSS file we generated while setting up the application.welcome.blade.php
file and replace its content with the code below:<!DOCTYPE html> <html lang="{{ str_replace('_', '-', app()->getLocale()) }}"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>uSearch</title> <link href="/css/tailwind.css" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Nunito:wght@400;600;700&display=swap" rel="stylesheet"> <style> body { font-family: 'Nunito'; } </style> @livewireStyles </head> <body> <header class="bg-gray-900 text-gray-200 w-full py-4 px-4"> uSearch </header> <livewire:search-user/> @livewireScripts </body> </html>
php artisan serve
in a terminal and the command should launch the server on http://localhost:8000
. Visit the application URL (http://localhost:8000) in your browser to see the home page below: