wisam-alhennawi/lara-forms-builder

This is my package lara-forms-builder

Maintainers

Package info

github.com/wisam-alhennawi/lara-forms-builder

Language:Blade

pkg:composer/wisam-alhennawi/lara-forms-builder

Statistics

Installs: 4 762

Dependents: 0

Suggesters: 0

Stars: 2

Open Issues: 3

v2.3.0 2026-04-28 13:52 UTC

README

Latest Version on Packagist GitHub Tests Action Status GitHub Code Style Action Status Total Downloads

About

The main functionality of this package is:

  • Generate Livewire forms (Show, Create, Update) by using one command and one Livewire component.

Laravel and PHP versions compatibility

lara-forms-builder Laravel PHP
<= v2.1.* ^9.0 | ^10.0 ^8.1
>= v2.2.* ^10.0 | ^11.0 | ^12.0 ^8.1 | ^8.2 | ^8.3 | ^8.4

Requirements

The following dependencies are required to use the package:

Dependency Version
PHP 8.x
Laravel 10.x
Jetstream 5.x 💡
Livewire 3.x 💡
Alpine.js 3.x 💡
TailwindCSS 3.x 💡
Pikaday 1.x 💡
Moment 2.x 💡

💡 => You can install it with Auto Setup & Configuration.

Note that (pikaday & moment) npm packages is required only if you have a date field within your form.

Installation

composer require wisam-alhennawi/lara-forms-builder

Upgrade Guide

If you want to use the package in an existing project that use v1 the following changed are required.

all placed uses $this->property should be replaced with $this->formProperties['property']

     // V1
     $this->email
     // V2
     $this->formProperties['email']

     // V1
     $this->rules['email']
     // V2
     $this->rules['formProperties.email']

Auto Setup & Configuration

php artisan make:lara-forms-builder-setup

This command will do the following:

  • Install "laravel/jetstream": "^5.0" with "livewire/livewire": "^3.0" if not installed. Installing jetstream will install "tailwindcss": "^3.0" & "alpinejs": "^3.0".
  • Install "pikaday": "^1.0" and "moment": "^2.0" npm packages and make required configuration.
  • Add confirmation modal component to app.blade.php layout.
  • publish lara-forms-builder.php config file and make required configuration.
  • publish lara-forms-builder.css assets file and make required configuration.
  • Run npm install & npm run build.

Configuration

Publishing Assets

  1. Config (Mandatory)

    You Must publish the config file and add it to the tailwind.config.js in order to apply the styles:

    php artisan vendor:publish --tag="lara-forms-builder-config"

    This is the contents of the published config file:

    return [
        'default_group_wrapper_class' => 'lfb-group-wrapper',
        'default_field_wrapper_class' => 'lfb-field-wrapper',
        'card_field_error_wrapper_classes' => 'shadow mb-4 overflow-hidden rounded-md col-span-2 sm:col-span-2',
        'primary_button_classes' => 'lfb-primary-button',
        'secondary_button_classes' => 'lfb-secondary-button',
        'footer_buttons_wrapper_classes' => 'lfb-buttons-wrapper',
        'previous_button_classes' => 'lfb-secondary-button',
        'next_button_classes' => 'lfb-primary-button',
    ];

    Update tailwind.config.js:

    export default {
         content: [
         './config/lara-forms-builder.php',
        ],
         theme: {
             extend: {
                 colors: {
                     'primary': '', // #7c8e63
                     'secondary': '', // #aebf85
                     'danger': '' // #DC3545
                 },
             },
         },
    };
  2. CSS (Mandatory)

    Publishing css file is Mandatory to apply styles.

    php artisan vendor:publish --tag="lara-forms-builder-assets"

    That will make a new css file lara-forms-builder.css in the public/vendor/lara-forms-builder/css/ directory. After that you must import this file with your resources/css/app.css by adding:

    @import "../../public/vendor/lara-forms-builder/css/lara-forms-builder.css";

    And feel free to edit the default style in lara-forms-builder.css to fulfil your form requirements style.

  3. Translation (optional)

    php artisan vendor:publish --tag="lara-forms-builder-translations"
  4. Views (optional)

    php artisan vendor:publish --tag="lara-forms-builder-views"

Using date in form (optional)

Like it mention in the Requirements section if your form has a date field you must install required dependencies by following these steps:

  • Installing pikaday:

    npm install pikaday

    add these to your resources/js/app.js

    import Pikaday from 'pikaday';
    window.Pikaday = Pikaday;

    add these to your resources/css/app.css

    @import 'pikaday/css/pikaday.css';
  • Installing moment:

    npm install moment

Use confirmation modal

In order to use the confirmation modal within your project you must include it globally in the default layout of your blade view where you want to use it. So you can add @livewire('modals.confirmation') to your views/layouts/app.blade.php inside the html <body> tag.

Usage

Create new Livewire from

By using this command you can create a new Livewire form depending on a model, additionally you can add [--langModelFileName=] tag to specify a lang file for model fields labels:

php artisan make:lara-forms-builder name model --langModelFileName=

Examples:

php artisan make:lara-forms-builder UserForm User --langModelFileName=users
php artisan make:lara-forms-builder Users.UserForm User --langModelFileName=users       //this will make a UserForm component inside Users directory.

Form Component Types and Attributes

Forms are specified in a declarative manner as an array of the fields function in the form component class.

beforeFormProperties(), afterFormProperties() functions used to set options, values, etc. before/after setting the form properties (e.g. developers options in the following example).

saveDevelopers() function used to make operation after create/update a model (e.g. developers ids & project id will be saved in pivot table in the following example).

Note: saveDevelopers()functions should follow the naming convention of the form field name developers.

public array $developersOptions = []; // For 'checkbox-group' options
public array $userIdOptions = []; // For 'user_id' options

public function beforeFormProperties(): void
{
    $this->developersOptions = User::all(['id as value', 'name as label'])->toArray();
}

public function afterFormProperties(): void
{
    $this->formProperties['developers'] = $this->model->developers->pluck('id');
}

protected function fields(): array
    {
        return [
            [
                'fields' => [
                    'title' => [
                        'type' => 'input',
                        'label' => __('models/projects.fields.title'),
                        'readOnly' => true
                    ],
                    'status' => [
                        'type' => 'select',
                        'label' => __('models/projects.fields.status'),
                        'styled' => true,
                        'searchable' => true,
                        'options' => [
                            [
                                'value' => '1',
                                'label' => __('Pending')
                            ],
                            [
                                'value' => '2',
                                'label' => __('Active')
                            ],
                            [
                                'value' => '3',
                                'label' => __('Completed')
                            ]
                        ]
                    ],
                    'type' => [
                        'type' => 'radio',
                        'label' => __('models/projects.fields.type'),
                        'options' => [
                            '1' => __('Public'),
                            '2' => __('Private')
                        ]
                    ],
                    'starting_date' => [
                        'type' => 'date-picker',
                        'label' => __('models/projects.fields.starting_date'),
                    ],
                    'user_id' => [
                        'type' => 'search-picker',
                        'label' => __('models/projects.fields.user_id'),
                        'searchPickerResultsProperty' => 'userIdOptions',
                        'placeholder' => __('Search for Team Leader'),
                    ],
                    'is_accepted' => [
                        'type' => 'checkbox',
                        'label' => __('models/projects.fields.is_accepted'),
                    ],
                    'is_active' => [
                        'type' => 'yes-no-toggle-switch',
                            'label' => __('Active'),
                            'options' => [
                                0 => __('No'),
                                1 => __('Yes'),
                            ],
                    ],
                    'description' => [
                        'type' => 'textarea',
                        'label' => __('models/projects.fields.description'),
                        'field_wrapper_class' => 'col-span-2',
                    ],
                    'developers' => [
                        'type' => 'checkbox-group',
                        'label' => __('models/projects.fields.developers'),
                        'options' => $this->developersOptions,
                    ],
                    'document' => [
                        'type' => 'file',
                        'label' => __('models/projects.fields.document'),
                    ],
                ]
            ]
        ];
    }

public function saveDevelopers($validated_date) {
    $this->model->developers()->sync($validated_date);
}

public function getUserIdOptions($searchPickerTerm)
{
    return User::select('id', 'name')
        ->where('name', 'like', '%' . $searchPickerTerm . '%')
        ->get()
        ->each(function($row){
            $row->key = $row->id;
            $row->value = $row->name;
            $row->labels = collect(['White', 'Green', 'Blue', 'Red', 'Yellow'])->random(rand(0, 3))->toArray();
        })
        ->toArray();
}

// will return ($user_id_search_picker_selected_value) which used in blade to display the selected option
public function getUserIdSearchPickerSelectedValueProperty()
{
    if ($this->formProperties['user_id']) {
        return User::find($this->formProperties['user_id'])->name;
    }
    return null;
}

The definition above is then rendered as displayed in this screenshot (with additional language dependent translations for the labels required):

Rendered Form according to code snippet

All form components have the following general properties:

  • type (mandatory): Specifies the kind of form component, see the following subsections for supported types and their individual properties
  • label (mandatory): Label text of the component, can also include HTML tags (please handle responsibly and do not allow user input to be used as labels)
  • validationAttribute (optional): If set, the value will be used as the attribute label for validation messages. If not set, the label is also used as a field name for validation messages.
  • helpText (optional): Help text displayed at the bottom of the component
  • readOnly (optional): When set to true, the form field does not allow input or changes and only displays the current value
  • rules (optional): Validation rules to be applied for this field. If not set, Eloquent model rules for the field with the same name will be used if available, otherwise no rules are applied.
  • field_wrapper_class (optional): CSS class(es) to be added to the div that encloses the form component
  • tooltip (optional): supports string or array and renders a tooltip icon next to the label.
    • String usage: pass plain tooltip text.
    • Array usage: pass text for tooltip content and optional iconView for a custom icon Blade partial.
    • Width classes are applied automatically based on tooltip text length (small, medium, large).
'tooltip' => [
    'text' => 'Curabitur at felis non libero suscipit fermentum...',
    'iconView' => 'partials.icons.info-tooltip',
],

Type input

The input form field is a classic html input element. It has the following additional properties:

  • inputType (optional): Specifies the specific type of input, e.g. email, number, url. Default if not provided is text.
  • secretValueToggle (optional): Flag relevant for input type password, if set to true, an icon is displayed at the right end of the input field that allows to toggle the visilibity of the masked value on click.
  • fieldModifier (optional): This parameter allows customization of Livewire's input handling behavior with Livewire's native modifiers. Simply pass a Livewire modifier string. For example:
    • live.debounce.400ms - Debounces input with 400ms delay
    • live.throttle.750ms - Throttles input to maximum 1 event per 750ms

If no modifier is specified, Livewire's default live modifier will be applied.

Example:

    'weight' => [
        'type' => 'input',
        'inputType' => 'number',
        'label' =>  __('models/dogs.fields.weight'),
        'fieldModifier' => 'live.debounce.400ms',
    ]

Type textarea

The textarea form field represents a text area and has the following additional properties:

  • rows (optional): Defines the number of rows of the text area, default if not set is 5.

Type select

The select form field is a select box for single selection. It has the following additional properties:

  • options (mandatory): Specifies the options to be displayed in the select box, provided as an array of objects with attributes value and label or a nested array of group label to array of objects with attributes value and label when grouped.
  • isGrouped (optional): Defines whether the options are grouped, default when not set is false.
  • styled (optional, not combinable with isGrouped): When set to true, uses a stylable div based select component instead of the html select element.
  • searchable (optional, only when styled is true): When set to true, adds a search field that allows to filter the options for search expressions (simple case insensitive substring matching)

Type radio

The radio form field represents radio buttons and has the following additional properties:

  • options (mandatory): Specifies the options for the radio button selection as a simple array value => label.

Type cards

The cards form field is a special layout element used to select among different options by clicking on one of several cards with rich content and an icon. It has the following additional properties:

  • options (mandatory): Specifies the options to be selected as an array of objects with attributes value and label (can be HTML).
  • icon (optional): SVG markup or URL to be displayed as icon in all cards, if not set, no icon is displayed.
  • card_field_error_wrapper_class (optional): CSS class(es) to be added to error message boxes.
  • errorMessageIcon (optional): Customized icon markup to be displayed in an error message, if not set, a default icon is used.

Type checkbox

The checkbox form field is a single checkbox and does not have any additional properties.

Type checkbox-group with Extra Input Element

The checkbox-group form field is a multi-select group of checkboxes. It has the following additional properties:

  • options (mandatory): Specifies the values and labels for the checkboxes, provided as an array of objects with attributes value and label or a nested array of category label to array of objects with attributes value and label when grouped by category.

  • hasCategory (optional): Defines whether the checkbox entries are grouped by category, default if not set is false.

  • enable_extra_elements (optional): Enables the extra input feature for this checkbox group. Default is false.

  • has_extra_element (optional): When set to true for an option, renders a text input field below the checkbox. The input remains read-only/disabled until its corresponding checkbox is selected. The option-level setting only has an effect when enable_extra_elements is true.

    Behavior:

    • The extra input field is automatically disabled when the checkbox is unchecked
    • The field becomes editable as soon as the checkbox is selected
    • Extra input values are stored separately from checkbox values in formProperties.{key}_extra.{value}

    Data Structure:

    • Selected checkbox values: $this->formProperties['{field_key}'] (array of selected values)
    • Extra input values: $this->formProperties['{field_key}_extra'] (associative array where keys are checkbox values and values are the input text)

    Setup Guide (Example Naming)

    Example field key: related_items

    'related_items' => [
        'type' => 'checkbox-group',
        'label' => 'Related Items',
        'options' => $this->relatedItemsOptions,
        'rules' => 'array',
        'enable_extra_elements' => true,
    ],

    Each option must include has_extra_element (boolean):

    public function beforeFormProperties(): void
    {
        $this->relatedItemsOptions = RelatedItem::query()
            ->select('id as value', 'name as label', 'has_extra_element')
            ->orderBy('name')
            ->get()
            ->map(function ($row) {
                $row->value = (string) $row->value;
    
                return $row;
            })
            ->toArray();
    }

    Initialize values after loading the model:

    public function afterFormProperties(): void
    {
        $this->formProperties['related_items'] = (filled($this->model) && $this->model->exists)
            ? $this->model->relatedItems->pluck('id')->map('strval')->toArray()
            : [];
    
        $this->formProperties['related_items_extra'] = (filled($this->model) && $this->model->exists)
            ? $this->model->relatedItems
                ->pluck('pivot.extra_element', 'id')
                ->mapWithKeys(function ($value, $key) {
                    return [(string) $key => $value];
                })
                ->toArray()
            : [];
    }

    Keep extra inputs in sync when the checkbox group changes:

    public function updatedRelatedItems($value): void
    {
        $selectedIds = $this->formProperties['related_items'] ?? [];
        $selectedIds = is_array($selectedIds) ? $selectedIds : [];
        $extraValues = $this->formProperties['related_items_extra'] ?? [];
    
        foreach ($this->relatedItemsOptions as $option) {
            $id = (string) ($option['value'] ?? '');
            $requiresExtra = (bool) ($option['has_extra_element'] ?? false);
    
            if ($requiresExtra && ! in_array($id, $selectedIds, true)) {
                $extraValues[$id] = null;
            }
        }
    
        $this->formProperties['related_items_extra'] = $extraValues;
    }

    Validate extra inputs when required:

    protected function extraValidate(array $validatedData = []): bool
    {
        $selectedIds = $validatedData['related_items'] ?? [];
        $extraValues = $this->formProperties['related_items_extra'] ?? [];
    
        foreach ($this->relatedItemsOptions as $option) {
            $id = (string) ($option['value'] ?? '');
            $requiresExtra = (bool) ($option['has_extra_element'] ?? false);
            $isSelected = in_array($id, $selectedIds, true);
            $value = $extraValues[$id] ?? null;
    
            if ($requiresExtra && $isSelected && (is_null($value) || trim((string) $value) === '')) {
                $this->addError('formProperties.related_items_extra.'.$id, 'This field is required.');
                return false;
            }
        }
    
        return true;
    }

    Persist the pivot data in a save... hook:

    public function saveRelatedItems($validatedData): void
    {
        $selectedIds = $validatedData ?? [];
        $extraValues = $this->formProperties['related_items_extra'] ?? [];
        $syncData = [];
    
        foreach ($selectedIds as $relatedItemId) {
            $syncData[$relatedItemId] = [
                'extra_element' => $extraValues[(string) $relatedItemId] ?? null,
            ];
        }
    
        $this->model->relatedItems()->sync($syncData);
    }

Type date-picker

The date-picker form field adds a Pikaday date picker. It does not have any additional properties.

Type file

The file form field represents a file input for a single file upload. It has the following additional properties:

  • preview (optional): If set to the value image, a preview of the uploaded or existing file is displayed. In order to properly show a preview of an existing file (when editing an existing model), the URL to download the existing file must be set as an additional property $this.{$key . '_preview'}, e.g. contact_photo_preview for a field key contact_property. The preview URL can be determined and set in the afterFormProperties function.
  • removeIcon (optional): Customized icon markup to be displayed as the icon to remove/reset a selected file before saving. If not set, a default icon is used.

Since binary files are usually not directly stored as properties of an Eloquent model, but must be processed separately, this type of form field will in most cases need additional file handling logic in the implementing form. The implementation follows the principles of Livewire file uploads (https://laravel-livewire.com/docs/2.x/file-uploads) and essentially provides the view part of file upload. It is suggested to add the file handling in one of the callback or override functions, e.g. the save... function of the property such as the following code snippet for a property attachment:

public function saveAttachment($attachmentValue) {
    $attachmentValue->storeAs('attachments', $attachmentValue->getClientOriginalName());
}

Type search-picker

The search-picker form field is an input field with search functionality which display the results as a list under the field. It has the following additional properties:

  • placeholder (optional): Specifies the placeholder of the input field.
  • searchPickerResultsProperty (mandatory): Refers to the defined array which has the search results. Every element in the array should have the following structure:
    [
        'key' => '',
        'value' => '',
        'labels' => [] // optional
    ]

Note: getUserIdOptions(), getUserIdSearchPickerSelectedValueProperty() functions should follow the naming convention of the form field name user_id.

Type trix-editor

The trix-editor form field is a rich text editor which uses Trix under the hood. It has the general properties mentioned above excluding readOnly.

In order to use the trix-editor form field you have to add the following to your blade:

<head>
  <link rel="stylesheet" type="text/css" href="https://unpkg.com/trix@2.0.8/dist/trix.css">
  <script type="text/javascript" src="https://unpkg.com/trix@2.0.8/dist/trix.umd.min.js"></script>
</head>

Please see the full documentation on the official Trix page .

Type yes-no-toggle-switch

The yes-no-toggle-switch form field is a two-state toggle that displays Yes/No labels and stores the selected value in formProperties. It has the following additional properties:

  • options (optional): Specifies the two options as a simple array value => label. Defaults to [0 => 'No', 1 => 'Yes'] if not set.

Example:

    'is_active' => [
        'type' => 'yes-no-toggle-switch',
        'label' => __('Active'),
        'options' => [
            0 => __('No'),
            1 => __('Yes'),
        ],
    ]

Group Info (Layout)

group_info is a layout helper that wraps a group of fields. It can render as a normal group or as an accordion.

Common group_info options:

  • title (optional): Group title.
  • description (optional): Group description text.
  • description_view (optional): Blade view to include under the title/description.
  • group_wrapper_class (optional): CSS class(es) for the inner fields wrapper.
  • default_group_wrapper_class (optional): If false, only group_wrapper_class is used. Default is true.

Accordion options (optional):

  • visibility (array): Defines visibility behavior for the group.
    • accordion (boolean): Enables accordion behavior (group fields are hidden until enabled).
    • controlled_by (string): Field key inside the same fields group used to control open/close state.
    • default (optional): Initial value used only when the controller field has no value yet. Can be true/false (for checkbox) or the corresponding toggle value.

controlled_by concept:

  • The controller is a normal form field included in the same fields array.
  • If controlled_by points to a field with type: checkbox, the header renders a checkbox.
  • If controlled_by points to a field with type: yes-no-toggle-switch, the header renders a yes/no toggle.
  • The controller field is still part of form data and is persisted like any other field.

Important: Handling group data based on whether the controller is checked/enabled, and showing validation messages for required fields inside that group, is not managed automatically by LFB and must be implemented in the form logic.

Example: normal group (no accordion)

[
    'group_info' => [
        'title' => __('Dog Info'),
        'description' => __('Basic information about the dog.'),
        'group_wrapper_class' => 'grid grid-cols-2 gap-4',
    ],
    'fields' => [
        'name' => [
            'type' => 'input',
            'label' => __('models/dogs.fields.name'),
        ],
        'breed' => [
            'type' => 'input',
            'label' => __('models/dogs.fields.breed'),
        ],
    ],
],

Example: accordion controlled by checkbox

[
    'group_info' => [
        'title' => __('Advanced settings'),
        'description' => __('Enable to show advanced fields.'),
        'visibility' => [
            'accordion' => true,
            'controlled_by' => 'is_active',
            'default' => true,
        ],
        'group_wrapper_class' => 'grid grid-cols-2 gap-4',
    ],
    'fields' => [
        'is_active' => [
            'type' => 'checkbox',
            'label' => __('Enable advanced settings'),
            'rules' => 'boolean',
        ],
        'microchip' => [
            'type' => 'input',
            'label' => __('Microchip'),
        ],
    ],
],

Example: accordion controlled by yes-no-toggle-switch

[
    'group_info' => [
        'title' => __('Advanced settings'),
        'description' => __('Enable to show advanced fields.'),
        'visibility' => [
            'accordion' => true,
            'controlled_by' => 'is_active',
            'default' => true,
        ],
        'group_wrapper_class' => 'grid grid-cols-2 gap-4',
    ],
    'fields' => [
        'is_active' => [
            'type' => 'yes-no-toggle-switch',
            'label' => __('Enable advanced settings'),
            'options' => [
                0 => __('No'),
                1 => __('Yes'),
            ],
            'rules' => 'boolean',
        ],
        'timezone' => [
            'type' => 'select',
            'label' => __('Timezone'),
            'options' => [
                'UTC' => 'UTC',
                'Europe/Rome' => 'Europe/Rome',
            ],
        ],
    ],
],

Repeater options (optional):

  • repeater (array): Define if the group will have a repeter functionality.
    • group_id (string): a unique identifier for the related repeated groups.

The main point is that we always have a group_id and a prefix to distinguish the repeated fields in addition to set the postfix _0 for the first field. All repeated fields will take the same validation rules and validation attributes as the first field.

The following is a dummy code snippet for concept explanation (a project form which has many developers):

public function beforeFormProperties(): void
{
    $this->rules["formProperties." . $this->groupRepeaterPrefix . 'developer_name_0'] = 'required';
}

public function setRepeatedFields(): void
{
    $developers = $this->model->developers;
    if ($developers->isEmpty()) {
        return;
    }
    $fieldsRepeatingCount = $developers->count() - 1;
    for ($i = 0; $i < $fieldsRepeatingCount; $i++) {
        $this->processGroupRepeating('developers'); // group_id
    }
    foreach ($developers as $index => $developer) {
        $this->formProperties[$this->groupRepeaterPrefix . "developer_name_{$index}"] = $developer->name;
    }
}

protected function fields(): array
{
    return [
        [
            'group_info' => [
                'repeater' => [
                    'group_id' => 'developers'
                ],
            ],
            'fields' => [
                $this->groupRepeaterPrefix . 'developer_name_0' => [
                    'type' => 'input',
                    'label' => __('Name'),
                ]
            ]
        ],
    ];
}

public function saveGroupRepeater($validated_data): void
{
    // Saving logic
}

Type custom-view

The custom-view form field allows rendering a custom Blade view or a dedicated Livewire component inside the form. It has the following additional properties:

  • view (optional): Blade view name to include (for example forms.fields.custom-note).
  • livewire_component (optional): Livewire component name to render (for example forms.custom-note).
  • data (optional): Global array of data/props passed as-is to the custom Blade view or Livewire component.

At least one of view or livewire_component should be provided.

When rendered:

  • For Blade view: @include($field['view'], $field['data'] ?? [])
  • For Livewire component: @livewire($field['livewire_component'], $field['data'] ?? [])

Pass only what you need through data.

Example with custom Blade view:

'custom_note' => [
    'type' => 'custom-view',
    'label' => __('Custom Note'),
    'view' => 'forms.fields.custom-note',
    'data' => [
        'mode' => $this->mode,
        'model' => $this->model,
        'formProperties' => $this->formProperties,
    ],
]

Example with custom Livewire component:

'custom_note' => [
    'type' => 'custom-view',
    'label' => __('Custom Note'),
    'livewire_component' => 'forms.custom-note',
    'data' => [
        'mode' => $this->mode,
        'model' => $this->model,
        'formProperties' => $this->formProperties,
        'fieldKey' => 'custom_note',
    ],
]

Form Layout

Tabs

  • A custom layout for components that utilize tabs to organize content.
  • Each tab is represented as an array (in fields() method) containing 'key', 'title', and 'content'. The key represents an internal technical key uniquely identifying the tab. The title property is used as label for both the tab navigation and the heading of the form; if an alternative (usually shortened) title for the navigation should be used, this can be specified with the optional property navTitle. The 'content' array includes information about the form fields, their types, labels, options, and styling.

Multi-Step

  • The Multi-Step feature is designed to facilitate the creation of multi-step forms with a Tabs Layout. It provides methods to initialize steps, set the active step number, navigate between steps, and retrieve information about the form's multi-step structure.
  • Usage
    • It works for now only with Tabs Layout and it is deactivated by default.
    • To enable the multi-step functionality, set the isMultiStep property to true when configuring the form.
  • Example:
    use WisamAlhennawi\LaraFormsBuilder\LaraFormComponent;
    use WisamAlhennawi\LaraFormsBuilder\Traits\HasTabs;
    
    class CustomerForm extends LaraFormComponent
    {
        use HasTabs;
    
        public function mount(Customer $customer)
        {
            $this->mountForm($customer, [
                'activeTab' => 'address-data',
                'hasTabs' => true,
                'isMultiStep' => true, // Optional if you want to use the multi-step form feature
            ]);
        }
    
        protected function fields(): array
        {
            return [
                [
                    'key' => 'address-data',
                    'title' => __('Address Data'),
                    'content' => [
                        'group_info' => [
                            'group_wrapper_class' => 'grid grid-cols-4 gap-6',
                            'default_group_wrapper_class' => false
                        ],
                        'fields' => [
                            // Fields for the 'Address Data' tab
                            // Example:
                            'company' => [
                                'type' => 'input',
                                'label' => __('models/customers.fields.company'),
                                'field_wrapper_class' => 'col-span-4',
                            ],
                            // ... other fields
                        ],
                    ],
                ],
                [
                    'key' => 'contact-data',
                    'title' => __('Contact Data'),
                    'content' => [
                        'group_info' => [
                            'group_wrapper_class' => 'grid grid-cols-6 gap-6',
                            'default_group_wrapper_class' => false
                        ],
                        'fields' => [
                            // Fields for the 'Contact Data' tab
                            // Example:
                            'phone' => [
                                'type' => 'input',
                                'label' => __('customers.fields.phone'),
                                'field_wrapper_class' => 'col-span-2',
                            ],
                            // ... other fields
                        ],
                    ],
                ],
                // .. other tabs
            ];
        }
    
        // ... other component properties and methods
    }

Multi-Step Customization Options

The following options allow you to further customize the appearance and behavior of multi-step forms.

Show Step Numbers

Set the $showStepNumber property to true to display a numeric index next to each step name in the navigation.

class CustomerForm extends LaraFormComponent
{
    use HasTabs;

    public bool $showStepNumber = true;

    // ...
}

Each step label will be prefixed with its sequential number (e.g. 1 Address Data, 2 Contact Data).

Top Navigation Bar

Set the $hasTopNavigation property to true to render the Previous / Next navigation buttons above the form content in addition to the default bottom position.

class CustomerForm extends LaraFormComponent
{
    use HasTabs;

    public bool $hasTopNavigation = true;

    // ...
}
Custom Button Labels

Override getPreviousStepLabel() and/or getNextStepLabel() in your form component to replace the default button text. The default implementations already return __('Previous Step') and __('Next Step') respectively.

public function getPreviousStepLabel(): string
{
    return __('Go Back');
}

public function getNextStepLabel(): string
{
    return __('Continue');
}
Navigation Icons

Set the $multiStepNavigationIconsEnabled property to true to display the built-in chevron icons on the Previous / Next navigation buttons.

class CustomerForm extends LaraFormComponent
{
    use HasTabs;

    public bool $multiStepNavigationIconsEnabled = true;

    // ...
}

When enabled, a left-chevron icon is shown on the Previous button and a right-chevron icon on the Next button by default.

To use custom icons instead, override getPreviousStepIcon() and/or getNextStepIcon() in your form component and return raw SVG markup. The override is applied regardless of the value of $multiStepNavigationIconsEnabled.

public function getPreviousStepIcon(): ?string
{
    return '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-4 h-4"><path d="M15 19l-7-7 7-7"/></svg>';
}

public function getNextStepIcon(): ?string
{
    return '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-4 h-4"><path d="M9 5l7 7-7 7"/></svg>';
}

Form Methods

  • protected function successMessage()

    • Purpose:
      • The successMessage() function is responsible for generating a success message based on the outcome of a form submission.
    • Custom Success Message:
      • If a custom success message is provided through the $customSuccessMessage property, it will be used.
    • Default Success Messages:
      • If no custom success message is set, default success messages are used for create and update modes.
        trans('A new entry has been created successfully.')
        trans('Changes were saved successfully.')
      • These default messages can be further customized by adding translation keys in the language file.
        'A new '.$modelName.' has been created successfully.'
        'The '.$modelName.' has been updated successfully.'
        Example:
        'A new user has been created successfully.'
        'The user has been updated successfully.'
    • Displaying the Success Message:
      • The success message is displayed to the user either as a flash message or through a browser event. The display method depends on the value of the $hasSession property, which is true by default.
    • Another way to customize the success message is to override the successMessage() method in the component class.
  • protected function canSubmit(): bool

    • Purpose: Check if the user is authorized to submit the form
  • protected function fieldIndicator($fieldKey): ?string

    • Purpose:
      • Display an indicator (*) for a required field in the form based on required rule in the rules array
    • Customization:
      • An indicator can be displayed based on a custom check (eg. conditional required rules like required_if), to do that shouldDisplayIndicatorBasedOnCustomCheck($key) should be overwritten: Example:
          protected function shouldDisplayIndicatorBasedOnCustomCheck($key): bool
          {
              if ($key == 'email' && $this->formProperties['type'] == 'email') {
                  return true;
              }
              return false;
          }

Scroll to First Error

The scrollToFirstError feature allows the form to automatically scroll to the first field with an error after clicking the submit button. This enhances user experience by immediately directing attention to the area that needs correction.

How to Enable

To enable this feature, add the following line in the mount method of your form component:

$this->scrollToFirstError = true;

Changelog

Please see CHANGELOG for more information on what has changed recently.

Contributing

Please see CONTRIBUTING for details.

Security Vulnerabilities

Please review our security policy on how to report security vulnerabilities.

Credits

License

The MIT License (MIT). Please see License File for more information.