This package allows you to create beautiful forms, modals and slide-overs with ease. It utillises the great Filament Forms package for creating the forms and the awesome TALL-stack for the design.
You can install the package via composer:
composer require ralphjsmit/tall-interactive
The package requires the following dependencies:
- Laravel Livewire
- Alpine.js
- Tailwind CSS
- Filament Forms
- Toast notification (not required, but very handy)
You can install them manually or skip to the Faster installation section for new projects.
Please follow the Laravel Livewire installation instructions if you haven't done so yet.
Please follow the Filament Forms installation instructions to install Alpine.js, Tailwind CSS and Filament Forms.
Add the following to the content
key of your tailwind.config.js
file:
module.exports = {
content: [
'./vendor/ralphjsmit/tall-interactive/resources/views/**/*.blade.php',
// All other locations
],
///
Using the Toast TALL notifications package is not required, but it is recommended if you need to send notifications to your users, for example on submitting a form.
If you decide to use Toast, please follow their setup instructions.
After installing the package and setting up the dependencies, add the following code to your Blade files so that it's loaded on every page. For example in your layouts/app.blade.php
view:
<x-tall-interactive::actionables-manager />
Now you're ready to go and build your first actionables!
If you want a faster installation process, you could check out my ralphjsmit/tall-install package. This package provides you with a simple command that runs the installation process for all the above dependencies in a plain Laravel installation.
It works like this:
# First, create a new plain Laravel installation, for example with: laravel new name # OR: composer create-project laravel/laravel name # Next, require the `tall-install` package and run the `php artisan tall-install` command: composer require ralphjsmit/tall-install composer dump-autoload php artisan tall-installThe
tall-install
command also has a few additional options you can use, like installing Pest, Browsersync and DDD. Please check out the documentation for that.
You can build a modal, a slide-over or an inline form (together I call them 'actionables') with three things:
- With a Filament Form
- With a Livewire component
- With custom Blade contents
To start building our first actionable, let's do some preparation first. Create a new file in your app/Forms
directory (custom namespaces are also fine). You could call it UserForm
or however you like.
Add the following contents to the file:
<?php
namespace App\Forms;
use RalphJSmit\Tall\Interactive\Forms\Form;
class UserForm extends Form
{
public function getFormSchema(): array
{
return [];
}
public function submit(): void
{
//
}
public function mount(): void
{
//
}
/** Only applicable for Modals and SlideOvers */
public function onOpen(): void
{
//
}
}
Creating a form with Filament is very easy. The field classes of your form reside in the getFormSchema()
method of the Form class.
For all the available fields, check out the Filament documentation.
public function getFormSchema(Component $livewire): array
{
return [
TextInput::make('email')
->label('Enter your email')
->placeholder('john@example.com')
->required(),
Grid::make()->schema([
TextInput::make('firstname')
->label('Enter your first name')
->placeholder('John'),
TextInput::make('lastname')
->label('Enter your last name')
->placeholder('Doe'),
]),
TextInput::make('password')
->label('Choose a password')
->password(),
MarkdownEditor::make('why')
->label('Why do you want an account?'),
Placeholder::make('open_child_modal')
->disableLabel()
->content(
new HtmlString('Click <button onclick="Livewire.emit(\'modal:open\', \'create-user-child\')" type="button" class="text-primary-500">here</button> to open a child modal🤩')
),
];
}
The field values are stored on the $data
array property on the $livewire
component. You can set default values by using the Filament ->default()
method:
public function getFormSchema(Component $livewire): array
{
return [
TextInput::make('year')
->label('Pick your year of birth')
->default(now()->subYears(18)->format('Y'))
->required(),
];
}
You can also add a fill()
method on your form class. This will be passed to the $this->form->fill()
method and can be used for pre-filling values:
Here is an example of pre-filling a form based on a Blade component parameter:
public int $personId;
public function mount(array $params): void
{
$this->personId = $params['personId'];
}
public function fill(): array
{
$person = Person::find($this->personId);
return [
'year' => $person->birthdate->format('Y'),
];
}
You can use the submit()
method to provide the logic for submitting the form.
use Illuminate\Support\Collection;
public function submit(Collection $state): void
{
User::create($state->all());
toast()
->success('Thanks for submitting the form! (Your data isn\'t stored anywhere.')
->push();
}
You may register custom validation messages by implementing the getErrorMessages()
function:
public function getErrorMessages(): array
{
return [
'email.required' => 'Please fill in your e-email',
'age.required' => 'Please enter your age',
'age.numeric' => 'Your age must be a number',
];
}
The tall-interactive
package also provides dependency injection for all the methods in a form class, similar to Filament Forms.
You can specify the following variables in each of the above methods:
$livewire
to get the current Livewire instance$model
to get the current model (if any)$formClass
to access the current instance of the form class. You could use this to set and get parameters (see Storing data).$formVersion
to access the current form version. You could use this to dinstinguish between different versions of your form (like a 'create' and 'edit' version of the same form).$state
to access the currently submitted form data. This is a collection. Only available in thesubmit
method.$close
to get a closure that allows you to close an actionable. You may pass the closure a string with theid
of an actionable in order to close that actionable. It defaults to the current actionable. If you pass anid
that doesn't exist nothing will happen.$forceClose
to get a closure that allows you to close all actionables.$params
to get an array with all additional parameters passed to the actionable Blade component.
Using the $close()
and $forceClose()
closures are a very advanced way of customization which actionables should be open and which actionables not.
You may mix-and-match those dependencies however you like and only include those that you need. Similar to Filament's closure customization.
For example:
use Closure;
use Livewire\Component;
use App\Models\User;
public function submit(Component $livewire, User $model, Collection $state, Closure $close, Closure $forceClose): void
{
$model
->fill($state->except('password'))
->save();
if ($state->has('password')) {
$model->update(
$state->only('password')->all()
);
}
/* Close current actionable */
$close();
/* Close another actionable */
$close('another-peer-actionable');
/* Close all open actionables */
$forceClose();
}
In order to open a modal on a page, include the following somewhere on the page:
<x-tall-interactive::modal id="create-user" />
If you want to open a slide-over instead of a modal, use the following tag:
<x-tall-interactive::slide-over id="create-user" />
Both the modal
component and the slide-over
component work exactly the same.
The only required parameter here is the id
of an actionable. This id
is required, because you need it when emitting a Livewire event to open the actionable. The id
for an actionable should be different for each actionable on a page, otherwise multiple actionables would open at the same time.
You can open an actionable by dispatching a modal:open
or slideOver:open
event with the id
as it's first parameter:
<button onclick="Livewire.emit('modal:open', 'create-user')" type="button" class="...">
Open Modal
</button>
You can close an actionable by dispatching a modal:close
or slideOver:close
event with the id
as it's first parameter:
<button onclick="Livewire.emit('modal:close', 'create-user')" type="button" class="...">
Close modal
</button>
You can close an actionable without knowing its type by dispatching a :close
event with the id
as it's first parameter:
<button onclick="Livewire.emit(':close', 'create-user')" type="button" class="...">
Close modal
</button>
For all the below examples you can always change modal
for slide-over
to use a slide-over instead.
Currently the actionable is empty. Let's fix that by displaying our form. In order to display a form, add the form
property:
<x-tall-interactive::modal
id="create-user"
:form="\App\Forms\UserForm::class"
/>
Now, when you emit the modal:open
event, the modal will contain a nice form.
You may specify the name of a Livewire component to be used instead of a form, by using the livewire
attribute:
<x-tall-interactive::modal
id="create-user"
livewire="users.edit"
/>
If you specify both the form
and the livewire
attribute, only the form
will be displayed.
You can also give custom Blade content to an actionable by putting in the slot of component:
<x-tall-interactive::modal id="create-user">
<p>My custom Blade content in this actionable!<p>
</tall-interactive::modal>
It is recommended not to make the Blade content too big. A few paragraphs is fine, but displaying a 10,000-word article will probably cause significant lag.
The following attributes are available for configuring your actionable.
Closing a modal on successfully submitting the form
If you specify the closeOnSubmit
attribute, the actionable will automatically close on submit. This attribute is true
by default, meaning that the actionable will close after successfully submitting the form.
If you specify the forceCloseOnSubmit
attribute, all actionables will be closed upon successfully submitting this form. This could be handy for situations like this: Edit User > Delete User > Submit. This attribute is false
by default.
<x-tall-interactive::modal
id="create-user"
:form="\App\Forms\UserForm::class"
closeOnSubmit="false"
/>
If you need more advanced customization of which actionables to close and which to keep open, you should innject and use the $close()
and $forceClose()
in the submit()
method in the formclass.
Adding a title
You may specify the title of a form with the title
attribute. If you omit the title
attribute, the title will not be visible.
<x-tall-interactive::modal
id="create-user"
:form="\App\Forms\UserForm::class"
title="Create a user"
/>
Adding a description
You may specify the description of a form with the description
attribute. If you omit the description
attribute, the description will not be visible.
<x-tall-interactive::modal
id="create-user"
:form="\App\Forms\UserForm::class"
title="Create a user"
description="Use this form to grant a user access to your system."
/>
Text on submit button
You may set the text on the submit-button by specifiying the submitWith
attribute:
<x-tall-interactive::modal
id="create-user"
:form="\App\Forms\UserForm::class"
submitWith="Create user"
/>
Closing a form before submitting
You may allow an actionable to be dismissed (closed) before it is submitted by specifiying the dismissable
attribute. By default this is disabled.
<x-tall-interactive::modal
id="create-user"
:form="\App\Forms\UserForm::class"
dismissable
/>
You may specify the text on the close-button of an actionable with the dismissableWith
attribute. By default the text will be 'Close'.
If you specify the dismissableWith
attribute, you are allowed to omit the dismissable
attribute:
<x-tall-interactive::modal
id="create-user"
:form="\App\Forms\UserForm::class"
dismissableWith="Go back"
/>
Hiding the buttons
You may hide the buttons at the bottom of an actionable by specifiying the hideControls
attribute:
<x-tall-interactive::modal
id="create-user"
:form="\App\Forms\UserForm::class"
hideControls
/>
Setting a maximum width
You may set the maximum width of an actionable by specify the maxWidth
attribute. Allowed values are: sm
,md
,lg
,xl
,2xl
, 3xl
, 4xl
, 5xl
, 6xl
, 7xl
.
<x-tall-interactive::modal
id="create-user"
:form="\App\Forms\UserForm::class"
maxWidth="2xl"
/>
Model binding
You may give the form an (Eloquent) model, which can be used for things like edit forms:
@foreach (\App\Models\User::get() as $user)
<x-tall-interactive::modal
id="edit-user"
:form="\App\Forms\UserForm::class"
:model="$user"
/>
@endforeach
Check out the section about binding to model properties for this.
You may pass any custom parameters to the Blade component, however you like. Those are collected in an array and can be used in your form:
<x-tall-interactive::modal
id="create-user"
:form="\App\Forms\UserForm::class"
closeOnSubmit
x="test"
:y="8*8"
z
/>
public function getFormSchema(array $params): array
{
// $params
// [ 'x' => 'test', 'y' => 64, 'z' => true ]
return [
// ..
];
}
You may also display inline forms on a page like this:
<x-tall-interactive::inline-form />
For an inline form, you don't need to specify an id
.
An inline form takes the following parameters:
container
controlsDesign
form
title
description
submitWith
hideControls
maxWidth
model
Parameters 3-9 work the same as explained above, so I'm not going to repeat them here.
Putting an inline form in a container
You may specify the container
attribute to put an inline form in a nice container. A container is a simple wrapper that places the form in a white box with a nice shadow.
Changing the controls design
You may specify the controlsDesign
to change the design of the buttons at the bottom of the form. It takes on of two values: minimal
and classic
. By default it is minimal
.
In some cases it can be handy to store data in the instance of your form class. You can use that data later in the process, for example when submitting the form.
You may add public
properties on your form class to store data in. A good place to do so could be the mount()
method, as shown below.
You can use the mount()
method on the form class to mount your form. This can be useful for storing / setting data in the form class when it is invoked for the first time.
You may use all the dependency injection functionality that's available as well (for a list of all the possible parameters, see above):
public User $user;
public string $x = '';
public function mount(array $params, User $model): void
{
$this->user = $model;
$this->x = $params['x'];
}
public function getFormSchema(): array
{
return [
Hidden::make('user_id')->default($this->user->id)
];
}
You may add an onOpen()
method to your form class to react to the event of opening of the actionable. As you might expect, this method is only available for modals and slide-overs.
public function onOpen(): void
{
// ...
}
You may also pass parameters to the events when opening a form:
<button onclick="Livewire.emit('modal:open', 'create-user', 8, 'admin')" type="button" class="...">
Open Modal
</button>
Use the $eventParams
variable to access the parameters passed to the event.
public User $user;
public array $prefilledValues = [];
public function onOpen(array $eventParams, self $formClass): void
{
$formClass->user = User::find($eventParams[0]);
$formClass->prefilledValues['type'] = $eventParams[1];
}
Optionally, you can publish the views using (not recommended, they can get outdated):
php artisan vendor:publish --tag="tall-interactive-views"
🐞 If you spot a bug, please submit a detailed issue and I'll try to fix it as soon as possible.
🔐 If you discover a vulnerability, please review our security policy.
🙌 If you want to contribute, please submit a pull request. All PRs will be fully credited. If you're unsure whether I'd accept your idea, feel free to contact me!
🙋♂️ Ralph J. Smit