anthonyedmonds / laravel-form-builder
Create one-thing-per-page forms with forking logic and task lists; ideal for the GOV.UK Design System!
Requires
- php: ^8.3
- illuminate/support: ^12
Requires (Dev)
README
Laravel Form Builder
Create one-thing-per-page forms broken down into tasks; ideal for the GOV.UK Design System!
What's in the box?
- Laravel 12 Form Builder classes, interfaces, and traits
- Basic frontend pages for demonstration and testing purposes
The user interface is left up to you to implement and style as you desire.
Installation
Composer
You can install this library using Composer:
composer require anthonyedmonds/laravel-form-builder
Service Provider
The Form Builder service provider will be automatically registered.
If you need to manually load it, you can add the following to your config/app.php
file:
'providers' => [ // ... AnthonyEdmonds\LaravelFormBuilder\LaravelFormBuilderServiceProvider::class, ],
Publish files
The following can be published using php artisan vendor:publish
:
Key | Usage | Target |
---|---|---|
config | The Form Builder configuration file | config/form-builder.php |
views | The Form Builder blades and components | resources/vendor/form-builder/... |
The configuration file is required for registering forms.
The views are optional, used primarily as a basis for building your own form UI components.
Routes
Add the Form Builder routes to your routes/web.php
:
Route::laravelFormBuilder();
Configuration
The config/form-builder.php
file contains the following options:
Key | Usage | Type | Default |
---|---|---|---|
forms | A list of Forms available in the system | class-string[] | [] |
Structure
Each Form
follows a simple structure, with movement controlled automatically based on the user's choices:
Form > Start or Resume
Tasks > Task > Question
Summary > Submit or Draft > Confirmation > Exit
- Upon entering a
Form
, the system will check whether they have an active session- If they do, they will be asked whether they want to resume or start fresh
- If not, they will be shown a start page (if enabled)
- Users will then be shown a list of
Tasks
- They may pick any
Task
to complete, though aTask
can be disabled if they cannot start it yet
- They may pick any
- Entering a
Task
shows the user theQuestion
list- Entering a
Question
allows the user to provide answers to any number of fields - Once all
Question
classes within aTask
have been answered, they can return to theTasks
list
- Entering a
- Once all
Task
classes have been completed, the user can then view aSummary
of their answers- They may then submit their answers, or save as a draft (if enabled)
- After successful submission, users will be shown a confirmation screen (if enabled)
- Users then exit the
Form
This library provides all of the flow control basis required, based on the content you provide.
The user's answers are stored in a session directly on the related Model
as they progress through the Form
.
A diagram is available here which shows the internal workings.
Setting up a Form
Every Form starts by extending the Form
class.
As a minimum, you will need to provide a unique key()
for the Form, the modelClass()
, and a set of tasks()
.
You can customise the behaviour of your Form elements by overriding functions in your classes.
Model
The Model
to be used with your Form
must implement the UsesForm
interface.
The HasForm
trait is provided to fill in most of the needed functionality.
As a minimum with the trait, you will need to provide a viewRoute()
.
This will allow users to view their submission after completing the Form
.
Drafts
A flag is provided on the Model
to enable or disable saving as a draft.
If you enable saving as a draft, it is recommended to customise the saveAsDraft()
function appropriately.
Tasks
Every Form
is broken up into several Task
classes.
These are wrapped up in a Tasks
class.
You can have as many or as few Task
classes as you like; it is recommended to keep each Task
to a minimum.
As a minimum, you will need to provide a list of tasks()
.
Showing and hiding Task classes
You can control which Task
classes are shown by adding any needed logic to the tasks()
method.
Users may be confused by the constant appearance and disappearance of tasks, so it is recommended to keep it simple, and consistent.
You may find it is better to have a Task
in the Not Required
state, instead of hiding it entirely.
Start and Confirmation pages
Flags are provided on the Form
to control whether to show the Start
and Confirmation
pages.
If Start
is disabled, users will be taken straight to the Tasks
page.
If Confirmation
is disabled, users will be taken straight to the viewRoute()
of the Model
.
Creating a Task
Each Task
contains any number of Question
classes.
As a minimum, you will need to provide a unique key()
, and a list of questions()
.
Statuses
Every Task
has a status based on the status each Question
.
You can customise the logic of which statuses are shown by overriding the relevant functions on the Task
.
For more information, see the HasStates
trait.
Showing and hiding Questions
You can control which Question
classes are shown by adding any needed logic to the questions()
method.
Users may be confused by the constant appearance and disappearance of questions, so it is recommended to keep it simple, and consistent.
You may find it is better to have a Question
in the Not Required
state, instead of hiding it entirely.
Creating a Question
Each Question
contains any number of fields, which should directly correspond to attributes on the Model
.
A Question
can be used in any number of Task
classes, especially when abstracted if they are similar.
You can customise how values are read and written to the Model
; by default, it will use the provided field key.
Fields
A field is made up of a key, and a set of parameters:
[ 'name' => [ 'hint' => 'Provide their full name', 'label' => 'What is their name?', 'optional' => false, ], ]
In this example, name
is the key, which corresponds to the name
property on the Model
.
The label
property should be the question being asked, and will be used as the page title by default.
The optional
property controls whether the question is required or not.
If the optional
property is omitted, the field will be treated as required.
You can expand the list of available field properties based on the needs of your interface, such as the hint
property.
Skippable Questions
A flag is provided to allow users to skip a question.
This can be useful for optional questions, where it may be clearer that the user does not have to give a response.
If you enable skipping, it is recommended to customise the applySkip()
function appropriately.
Looping Questions
A flag is provided to redirect users back to the same Question
after saving.
This can be useful for adding multiple responses to the same question.
When doing this, the Question
should also be skippable, with the skip option being used to progress forward.
If skipping is not enabled, the user will must be provided with a way to move forward.
Statuses
Each Question
has a status based on the user's answers to each field.
You can customise the logic of which statuses are shown by overriding the relevant functions on the Question
.
For more information, see the HasStates
trait.
Using and accessing Forms
Users have two main entry points to any Form
, new and edit.
Start a new Form
Users can be redirected to the new
endpoint using any of the following:
Model::newForm()->route()
route('forms.new', MyForm::key())
Edit an existing Model
Users can be redirect to the edit
endpoint using any of the following:
$model->form()->route()
route('forms.edit', [MyForm::key(), $model->id])
Help and support
You are welcome to raise any issues or questions on GitHub.
If you wish to contribute to this library, raise an issue before submitting a forked pull request.
Licence
Published under the MIT licence.