Welcome dear reader, today you will learn how you can create an interactive frontend and add Alpine.js to a Laravel project. At re:Software S.L., we create innovative solutions for businesses that aim to digitize their processes and routines, and this topic today is part of one of our stories about digitization.
Table of contents
- What is Alpine.js and How Does it Work?
- How to Add Alpine.js to a Laravel Project?
- How to Start Alpine.js on Page Load with Laravel?
- Create a Blade Component with Alpine.js
- How to Register a Custom Blade Component?
What is Alpine.js and How Does it Work?
The Alpine.js JavaScript library is lightweight – in fact it is a collection of 15 attributes only, ranging from x-data
to x-bind
and x-on
, these will help you build dynamic views that do not need to re-compile fully before changes can be seen – in other words, it allows you to create interactive frontends.
One of the pre-requirements here will be that you know how to build a SaaS with Laravel, to achieve this I’d recommend that you first have a read here. The frontend that we will install, shall work with Laravel resources, using a RESTful HTTP API approach.
Typically, developers will utilize Alpine.js to “sprinkle” JavaScript onto their frontend only where it is needed, such as in order to render a dialog window.
How to Add Alpine.js to a Laravel Project?
You can do this using multiple ways with the simplest being to use Alpine.js via a CDN such as jsDelivr. But you could also install it using NPM or Yarn, such as to track the versioning of packages and to potentially install Alpine.js plugins.
Add Alpine.js via a CDN
This one is fairly simple, just add the following <script>
tag nested inside your website’s <head>
.
<script defer src="https://cdn.jsdelivr.net/npm/[email protected]/dist/cdn.min.js"></script>
And that’s it already, so your HTML document should look something like the following:
<!DOCTYPE html>
<html>
<head>
<script defer src="https://cdn.jsdelivr.net/npm/[email protected]/dist/cdn.min.js"></script>
</head>
<body>
</body>
</html>
Add Alpine.js via NPM or Yarn
Of course, it is possible to install Alpine.js using package managers including NPM and Yarn, just run one of the following commands:
# Using NPM
npm install alpinejs
# Using Yarn
yarn install alpinejs
How to Start Alpine.js on Page Load with Laravel?
Next, let’s make sure that Alpine.js is started and available on page load. The framework is very simple and so, the only we’ll have to do, is to import the library and then use the start()
method. You can add the following piece of source code in your resources/js/app.js
file:
import alpinejs from 'alpinejs'
// enables Alpine.js' attributes and properties
// such that you can use x-data, x-bind, etc.
alpinejs.start()
// If you want, you can also export it to re-use.
window.alpinejs = alpinejs
Now, that’s already it to get things running when you add Alpine.js to a Laravel project. After having added the above source code, you can safely use the attributes – x-data
, x-bind
, x-on
, etc. – to inline JavaScript code in your Blade components with any HTML tag, and you can use the methods alpinejs.data()
and alpinejs.store()
to process and store data.
Create a Blade Component with Alpine.js
With the Blade template engine, it is possibe to register components that are then available across your entire project. In this section, we’ll implement a <x-delete-button>
component that can be added to listings of your database resources.
First, we’ll have to define a PHP class under app/View/Components/
and this class must extend the Illuminate\View\Component
base class. Additionally, we specify that this component accepts two parameters: string $action
and array $fields
. The first will be used as the action
attribute value for the form, and the second should be an associative array with keys that will be attached to the body of the HTTP request, i.e. these fields and their value will be sent with the form.
Following is an example source code for the DeleteButton component class:
<?php
namespace App\View\Components;
use Illuminate\View\Component;
class DeleteButton extends Component
{
/**
* The form action, should be a route or URL.
* @var string
*/
public $action;
/**
* The fields that must be attached to the request.
* @var array
*/
public $fields;
/**
* Create the component instance.
*
* @param string $action
* @param array $fields
* @return void
*/
public function __construct($action, $fields)
{
$this->action = $action;
$this->fields = $fields;
}
/**
* Render the Blade template.
*
* @return \Illuminate\View\View
*/
public function render()
{
return view('components.delete-button');
}
}
We will continue this example by creating a view file for our newly created component. Create a new file named delete-button.blade.php
in the folder resources/views/components/
. This file will contain the HTML markup that forms our delete button component and the JavaScript necessary to bring some life into the component, such as making sure that a button click will actually send the form.
The below source code uses the @method("DELETE")
Blade directive to enable HTTP DELETE requests using underlying Laravel configuration. And we also use the @csrf_field directive to add the current CSRF hash to the request, this is necessary to find out about expired user sessions.
Add the following code to the delete-button.blade.php
file:
<div
x-data="my_delete_button()"
x-init="[initialize()]
x-cloak
>
<form :action="action" method="POST">
@method("DELETE")
{{ @csrf_field() }}
<template x-for="(value, field) in fields">
<input type="hidden" :name="field" :value="value" />
</template>
<button
@click.prevent="submitDelete()"
>
<!-- heroicons.com "archive-box-x-mark" -->
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6 hover:cursor-pointer hover:text-red-500">
<path stroke-linecap="round" stroke-linejoin="round" d="M20.25 7.5l-.625 10.632a2.25 2.25 0 01-2.247 2.118H6.622a2.25 2.25 0 01-2.247-2.118L3.75 7.5m6 4.125l2.25 2.25m0 0l2.25 2.25M12 13.875l2.25-2.25M12 13.875l-2.25 2.25M3.375 7.5h17.25c.621 0 1.125-.504 1.125-1.125v-1.5c0-.621-.504-1.125-1.125-1.125H3.375c-.621 0-1.125.504-1.125 1.125v1.5c0 .621.504 1.125 1.125 1.125z" />
</svg>
</button>
</form>
</div>
<script>
function my_delete_button() {
return {
action: @js($action),
fields: @js($fields),
initialize() {
console.log("component initialized");
},
submitDelete() {
// find the form in DOM
document.forms[0].submit();
return false;
}
}
}
</script>
In this view file, we make use of the x-data
, x-init
and x-cloak
attributes provided with Alpine.js. The x-data
attribute is used to populate the component with properties – such as passing it our form action and fields using the @js()
Blade directive. The x-init attribute is executed once upon initialization of the component, the expected value is an array
and permits to execute zero or more functions once upon rendering of the component.
And the x-cloak attribute tells Alpine.js that we do not want to display the component until it is initialized. We use this x-cloak attribute to prevent displaying an empty form and/or component.
Later, we use the <template x-for="">
component to add one or more hidden fields to the form. This is the part that makes sure that you can add your resource id to the HTTP DELETE
request. As we create the <button>
element with a @click.prevent
attribute, it will execute the submitDelete()
function when the button is clicked.
Next, we use a SVG file from heroicons.com to display an archive box with an x-mark that may remind the end-user of a delete operation. Also, we use TailwindCSS classes on the <svg>
element to style the button. The classes used are w-6 h-6 hover:cursor-pointer hover:text-red-500
. The two first classes are used to set the dimensions of our SVG image and the last two classes are used to style the image with a different color when the button is hovered with the mouse.
How to Register a Custom Blade Component?
Finally, how do we use this component we just created? Well, this part won’t be too complicated as the Blade
facade provides an easy Blade::component()
method that you can use to register custom components.
Registering custom app-level components, such that can be reused across your complete Laravel project, is done in the app/Providers/AppServiceProvider
class’ boot()
method. Following extract displays an example that registers our <x-delete-button>
component from above:
use Illuminate\Support\Facades\Blade;
class AppServiceProvider extends ServiceProvider
{
// ... register()
/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
Blade::component("delete-button", \App\View\Components\DeleteButton::class);
}
}
Using a custom Blade component in your views
And now, you have a fully baked <x-delete-button>
component, that you can add to any tabled data, as show in the example below:
<table class="table">
<thead>
<th><td>ID</td></th>
<th><td>Name</td></th>
<th><td>Actions</td></th>
</thead>
<tbody>
@foreach ($users as $user)
<tr>
<td>{{ $user->id }}</td>
<td>{{ $user->name }}</td>
<td>
<x-delete-button
action="/users/delete"
:fields="['user_id' => $user->id]"
/>
</td>
@endforeach
</tbody>
</table>
Compile your frontend with npm run build
or npm run prod
, and you are good to go. When you click this newly created button component, it will send a HTTP DELETE
Request to /users/delete
. So make sure to have a route configured for that in your routes/web.php
, with something as simple as the following being enough to test the above component:
use Illuminate\Http\Request;
// ...
Route::delete("/users/delete", function(Request $request) {
$user_id = $request->input("user_id", null);
$user = null;
if (null === $user_id || null === ($user = User::find($user_id))) {
return back();
}
$user->delete();
return redirect("/users")->with([
'message_type' => 'success',
'message' => 'The user was deleted successfully.',
]);
});
Bring all the above together to add Alpine.js to a Laravel project. What do you think of this <x-delete-button> component? It’s cool isn’t it? If you have any suggestions or questions, or feedback, let me know in the comments!