Maurizio Bonani • 30 Oct 2020
What is Livewire
The first time you log into your Certify account, you will be prompted to complete the New User Setup Wizard.This article shows you how to navigate through the New User Setup Wizard to complete the setup of your Certify Account. As a new user to Certify, the Welcome to Certify page displays and you are prompted to personalize your account settings.The Progress Bar in the upper right-hand.
Livewire is a full-stack framework for Laravel, built by Caleb Porzio, that helps to build building dynamic interfaces in a simple way, without leaving the comfort of Laravel.
Livewire smooths the complexity of client-server code integration, allowing Laravel developers to build dynamic UIs without having to reach tools for front end tools like React or Vue, and without having to implement a dedicated API for backend communication.
- The setup wizards here typically create a self-extracting installer with all your files bundled into the installer itself, as is the norm these days. Note: if you are distributing your applications by CD or DVD, you may also be interested in the Free Autorun/Autoplay DVD and CD Menu Creation Software, which allows your program CD/DVD to automatically start when your users place them in the DVD drive.
- Steps To Create Setup.exe. Navigate to the “Start” menu and select “Run.”. Type “iexpress” (without quotes) and click “OK.”. The IExpress Wizard will appear. Select the “Create new Self Extraction Directive file” radio button and click “Next.”. The Package Purpose dialog opens.
- Setup Wizard free download - Setup Factory, Wireless Wizard, MiniTool Partition Wizard Free Edition, and many more programs.
- But after doing all these, the 'Setup Project' doesn't appear in the 'Create New Project' page. BTW, I had problem on installing the Visual Studio 2019; ' Visual Studio Installer Unable to install the files to the target location.
How does Livewire work?
With Livewire, HTML is rendered using Blade on the server side, but when a change is detected on the client a roundtrip AJAX request is made to the server that returns only the newly rendered DOM fragment as HTML. Livevewire then uses DOM diffing to know which HTML to replace with the new data.
GitHub uses this concept for some parts of its UI, but LiveWire is heavily inspired by Elixir's Phoenix LiveView, which leverages native WebSockets instead of AJAX.
What is Sendportal
Sendportal is an open-source self-hosted email marketing application, using the Laravel PHP framework.
It allow users to manage their own newsletters at a fraction of the cost of other options, and offers integrations with several email providers, including AWS SES, Mailgun, Sendgrid, Postmark and Mailjet.
Creating an Installer
Until a few weeks ago, the recommended way to install Sendportal was to make use of an Artisan command - php artisan sp:setup
.
The command performs the following:
Create Setup Wizards Free
- check that the
.env
file already exists, otherwise create it - check that the application key has been set
- check that the application url has been set and is different than
localhost
- check that the database connection is valid, otherwise prompts for valid credentials
- check that the migrations are up to date
- finally, prompt for the creation of the first admin account
Whilst this works really well, some users prefer the comfort of a GUI setup. This would also help Laravel Vapor users, who do not have access to the command line.
Our options were to either:
- use a frontend framework (like React or Vue) - but that would entail additional complexity (both in development and maintenance); or
- use regular Blade views and controllers - but that would not give us the front end reactivity that we were looking for
Since LiveWire is gaining pupularity, we thought that it would be a good opporunity to see if it (and we!) were up to the task. In all honesty, we were attracted by the idea of not having to write any JavaScript code. Not that we have anything against Javascript per se, but as SendPortal is a self-hosted project, we want to keep things as simple as possible.
Initial Steps
Livewire is a PHP package, and is easily installed using Composer:
Next we have to add the route which displays the setup page, so that it can be reached via the /setup
url.
Now we can go ahead and create the view, where LiveWire scripts and styles can be injected
Now you can run the following command in your terminal to generate the Livewire component:
This command creates two files app/Http/Livewire/Setup.php
and the corresponding view resources/views/livewire/setup.blade.php
.
Every Livewire class must contain at least the render()
method which tells your app which view to load. A Livewire component's render method gets called on the initial page load AND every subsequent component update. The Artisan command will automatically generate this method for you.
In the render
method you can return a Blade view or a Blade template string, if the component is simple. In any case Make sure your Blade view only has ONE root element.
We can add a simple message in the LiveWire setup.blade.php
view to check that its working correctly:
And finally we can include the component in our base view using <livewire:setup />
(or you can use @livewire('setup')
instead):
Thinking about state
We need to think about the state of the installer. We can take the php artisan sp:setup
command as a reference to lay out what we need:
- list of steps that need to be performed; and
- the step that we're currently on; and
- which steps have been completed and which haven't.
At first sight the state looks very simple, so we can try to implement it using PHP primitives, such as arrays and integers.
Where Is My Setup Wizard
Livewire components store and track data as public properties on the component class, so we can just go ahead and add them:
Public properties in Livewire are automatically made available to the view, so we can already update our LiveWire view to show the data:
We can now start improving the UI styling. The idea is to display the setup as an accordion, where the active step is open and the others are closed. Since we're using Bootstrap 4, we can leverage the accordion classes.
By using different CSS classes, we can determine which steps have been completed, the current step, and those that are still pending.
Its starting to look nice!
Create Setup Wizards Game
Actions
We now have to think about the actions that we want to perform in our UI:
- check if the current step has been completed
- if not, perform the action
- otherwise move to the next step
This is pretty much all the logic that our component will have to implement. However we're trying to make the Setup
component as 'dumb' as possible, so the actual logic of each step can be implemented by dedicated subclasses, following the Delegation pattern. This way the component will only have to manage the global state.
Ideally we want to (i) move through the steps back and forth, (ii) check()
if the current step has been completed, and (iii) run()
the next step. So for now we can just add the methods to the component
Those methods can be invoked from the UI using the LiveWire wire[event]='[action]'
directive. For example we can attach one of the functions to a HTML button
Since check()
and run()
will call the corresponding methods on each Step class, it's good practice to add an interface for those classes:
Now we can go ahead and create all the steps classes. Explaining the implemented logic is outside the scope of this blog post (the source is on GitHub, if you are curious), but here's a brief recap of what they do:
Class | Check | Run |
---|---|---|
Env | The .env file exists | Create it using the .env.example file |
Key | The application key has been set | Set the application key |
Url | The application url is different than http://localhost | Prompt the user for the desired application url |
Database | Verifies the connection to the Database | Prompt the user for valid Database connection parameters |
Migrations | Verifies if there are any pending migrations | Run the migrations |
Admin | Verifies if a user already exists | Prompt the user for the credentials of the first account of Sendportal |
So how do we bind the check()
and run()
methods of each Step to the Setup
class?
First of all let's add the related class to each step in our initial array:
Then, from the Setup class itself we can invoke the methods:
Both functions are very similar. As you can see, we are creating a new instance of the handler class that corresponds to the current Step and we run the corresponding method on it. The Setup
component doesn't have any knowledge of the logic of each step, as long as the function signature is respected. Another benefit is that testing gets easier.
Isolating the views
Following the same logic we also want to isolate the views of each step such as they don't have to depend from the LiveWire Setup
component. In other words we don't want to reference any step-specific UI inside the setup.blade.php
view, for example using an if
statement or a switch
- that would get messy really quickly!
Initially we tried to isolate each view inside a LiveWire nested component, but that turned out to be difficult because one of the limitations of nested components is that:
'Nested components CAN accept data parameters from their parents, HOWEVER they are not reactive like props from a Vue component.'
The solution however was already there: we ended up creating a Blade partial for each Step
class.
We also updated each step to hold a static reference to the view.
We therefore changed the list of steps to look like the following:
We also changed the setup.blade.php
to remove any reference to specific steps:
Finally, let's take a look at the UI specific to the Env
class, so you can have a better understanding at the structure of each view the check()
and run()
actions are called using the wire:click
directive
We can see the Env
step in action here:
Check the initial state
When the page loads for the first time it's obvious that the completed
value of the first step is false
because we still have to run the check()
method. Therefore we want to be able to call it as soon as possible, perhaps before the component is even rendered, so we don't waste a server round-trip.
Luckily LiveWire offers a few lifecycle hooks, which are pretty similar to those available in Vue.
We can make use of the mount
hook and run the first check()
immediately after the component is instantiated.
There is another small detail that we also have to take into account. Let's say, for example, that the environment has already been configured manually, and the only missing steps are the database migrations and the user account - we ideally want to jump straight to the next pending step.
With the current code, if we refresh the component we will start from the beginning. We can easily fix this by calling check()
recursively, up to the first step that returns false
or to the last step:
The same goes for the run()
method, so we don't have to click Next in order to move to the next step:
Handle form submission
Now let's take a look at how we can accept form submissions from the user.
In LiveWire you can bind public properties of the LiveWire component to the values of HTML input elements, so that they are synchronized. So when the value changes in the frontend, the change is also reflected in the backend and vice-versa. This is called two-way data binding and anyone that has worked with any frontend framework (from Knockout to Vue or Angular) should already be very familiar with it.
However, since our views are dynamic and the logic is delegated to Step subclasses we decided to check if it was possible to achieve the same result without having to bind form fields to LiveWire properties. In the end we are not interested in sending an AJAX request every time the values change. Two-way data binding doesn't make a lot of sense in this case because we are only interested in those values once the form has been submitted.
For example, let's take the form that accepts the application url. It looks like the following:
and it's coded like this:
How can we send the content of the form to the backend without binding the url
input?
Luckily, there was already someone with the same needs and Caleb was kind enough to provide a solution that perfectly fits our requirements.
Using modern JavaScript APIs like FormData and Object.fromEntries we are actually able to send the content of the form by passing it as a parameter to the run()
function using the wire:submit
event.
$event.target
is a reference to the object onto which the event was dispatched, which is the form itself.
We are also using the prevent
event modifier to prevent the default operation of the form.
Finally the only thing that's left is changing the signature of the run()
method to allow optional input data as first parameter
Validation
LiveWire supports validation out of the box. For this reason adding a validation layer to the Setup
component is a trivial task.
Just like we did with the previous methods, we can delegate the validation to each Step
subclass. For example let's see the validate function of the Url
class. You will notice that this is plain Laravel code that leverages the Validator
:
In the Setup component we can check if the current Step class has a validate
method and call it if it exists:
If the validation does not pass, the line $handler->validate($data)
will throw a new ValidationException
. LiveWire will then catch it and populate the Laravel $errors
object so we can actually display the errors in the view:
It's important to acknowledge that there's a little gotcha here. This GIF explains it perfectly
As you can see the old data disappears entirely from the form. This is due to the fact that we're not binding the input to a public property of the component; as far as LiveWire is concerned, that data doesn't exist when it sends the diffed HTML back to the client.
This is not really a big issue, because we can just flash the old data ourselves:
We also make sure to catch the ValidationException
and then throw it again, after we flashed the old input to the session. This means that we can use the good old old
method (pardon the pun here 😅) to retrieve the old input:
We can also leverage the HTML5 client validation by changing the input type to url
and adding the required
attribute, so we can let the browser handle the validation.
UI Niceties
The installer is finally taking form. We can now add some UI niceties to make the overall experience even more satisfying.
Add a spinner
Livewire allows you to easily display loading states during a roundtrip to the server, which is always a good indicator to the user that something is actually happening.
Using the wire:loading
directive we can show specific HTML only while waiting for actions to complete (network requests). Let's add a spinner to the setup.blade.php
view.
The spinner is particularly important on the Migrations step, which can take more time to complete:
We can also use the wire:loading.attr
directive to disable the submit button during the server roundtrip, so the user doesn't accidentally submit the form multiple times:
Add a progress bar
An installer is not really complete if it lacks a progress bar. Thanks to LiveWire, implementing a progress bar is straightforward.
We already have all the necessary information: the current step and the total number of steps. We only have to compute a value in the [0 - 100] interval, so we can map the progress to a percentage value.
Just like VueJs and other frontend frameworks, LiveWire offers an API for dynamic properties, also known as Computed Properties. We can use this API to compute the percentage value from the current step, the number of completed steps and the total number of steps.
Finally we can use this value to set the width of our Bootstrap progress bar.
You may have noticed that, unlike public properties, computed properties have to be referenced with the $this
keyword.
Show the final message
We can also use the progress
computed property to show a success message at the end of the process.
The Final Cut
Wizards, by their nature, can be somewhat complicated beasts. At the end of this process we learned thatLiveWire is powerful and it definitely has its own space in the Laravel ecosystem.
By using Laravel Livewire, we were able to implement a relatively complex and responsive UI, whilst still keeping the code modular and maintainable. Adding another step to the wizard, for example, would be really easy. All without writing a single line of JavaScript.