Skip to content

Latest commit

 

History

History
632 lines (519 loc) · 15.8 KB

README.md

File metadata and controls

632 lines (519 loc) · 15.8 KB

laravel-form-builder

A library for building & rendering forms in Laravel.

We originally developed this library for our internal systems which are based off of Bootstrap 4 and CoreUI. We've tried to not be too opinionated about markup; however the library does generate markup with Bootstrap 4 classes in mind.

If you're not using Bootstrap 4, or are using an older version, you'll need to add compatible CSS classes to handle the styling of components.

This library is in early release and is pending unit tests.

Table of Contents

Installation

composer require balfour/laravel-form-builder

Creating a Form

You can either create your form on the fly by constructing a Form, or you can create a custom class such as CreateUserForm which builds the components in the class constructor.

namespace App\Forms;

use App\Models\Role;
use Balfour\LaravelFormBuilder\Form;
use Balfour\LaravelFormBuilder\Components\Checkboxes;
use Balfour\LaravelFormBuilder\Components\EmailInput;
use Balfour\LaravelFormBuilder\Components\MobileNumberInput;
use Balfour\LaravelFormBuilder\Components\NumberInput;
use Balfour\LaravelFormBuilder\Components\PhoneNumberInput;
use Balfour\LaravelFormBuilder\Components\Row;
use Balfour\LaravelFormBuilder\Components\TextInput;

class CreateUserForm extends Form
{
    public function __construct()
    {
        $this->route('post.create_user')
            ->button('Create')
            ->with([
                TextInput::build()
                    ->name('first_name')
                    ->required(),
                TextInput::build()
                    ->name('last_name')
                    ->required(),
                EmailInput::build()
                    ->required()
                    ->rule('unique:users'),
                Row::build()
                    ->with([
                        MobileNumberInput::build()
                            ->rule('unique:users'),
                        PhoneNumberInput::build()
                            ->name('office_number')
                            ->rule('unique:users'),
                    ]),
                Row::build()
                    ->with([
                        NumberInput::build()
                            ->name('phone_extension')
                            ->rule('unique:users'),
                        TextInput::build()
                            ->name('skype')
                            ->rule('unique:users'),
                    ]),
                Checkboxes::build()
                    ->name('roles')
                    ->options(Role::class)
                    ->setVisibility(auth()->user()->can('assign-role')),
            ]);
    }
}

You can create the form object in your controller and pass it through to your view.

return view('create_user', ['form' => new CreateUserForm()])

In your view, you can then call the render method on the form.

<html>
<head>
    <title>Create User</title>
</head>
<body>
{!! $form->render !!}
</body>
</html>

Filling a Form

The $form->fill() method can be used to set the default values for all form components.

This method can either take an array or key => value pairs, or a model.

As an example, here is an EditUserForm which populates from an existing user model.

namespace App\Forms;

use App\Models\Role;
use App\Models\User;
use Balfour\LaravelFormBuilder\Form;
use Balfour\LaravelFormBuilder\Components\Checkboxes;
use Balfour\LaravelFormBuilder\Components\EmailInput;
use Balfour\LaravelFormBuilder\Components\MobileNumberInput;
use Balfour\LaravelFormBuilder\Components\NumberInput;
use Balfour\LaravelFormBuilder\Components\PhoneNumberInput;
use Balfour\LaravelFormBuilder\Components\Row;
use Balfour\LaravelFormBuilder\Components\TextInput;
use Illuminate\Validation\Rule;

class EditUserForm extends Form
{
    public function __construct(User $user)
    {
        $this->route('post.edit_user', [$user])
            ->button('Save')
            ->with([
                TextInput::build()
                    ->name('first_name')
                    ->required(),
                TextInput::build()
                    ->name('last_name')
                    ->required(),
                EmailInput::build()
                    ->required()
                    ->disabled(),
                Row::build()
                    ->with([
                        MobileNumberInput::build()
                            ->rule(Rule::unique('users')->ignore($user)),
                        PhoneNumberInput::build()
                            ->name('office_number')
                            ->rule(Rule::unique('users')->ignore($user)),
                    ]),
                Row::build()
                    ->with([
                        NumberInput::build()
                            ->name('phone_extension')
                            ->rule(Rule::unique('users')->ignore($user)),
                        TextInput::build()
                            ->name('skype')
                            ->rule(Rule::unique('users')->ignore($user)),
                    ]),
                Checkboxes::build()
                    ->name('roles')
                    ->options(Role::class)
                    ->setVisibility(auth()->user()->can('assign-role'))
                    ->defaults(function () use ($user) {
                        return $user->roles
                            ->pluck('id')
                            ->toArray();
                    }),
                ToggleSwitch::build()
                    ->name('is_two_factor_auth_enforced')
                    ->label('2 Factor Authentication Enforced')
                    ->onLabel('Yes')
                    ->offLabel('No'),
                ToggleSwitch::build()
                    ->name('is_enabled')
                    ->label('Account Enabled')
                    ->onLabel('Yes')
                    ->offLabel('No'),
            ])->fill($user);
    }
}

When we construct the form, we just need to pass in a user model and all components will be populated from the model.

use App\Forms\EditUserForm;
use App\Models\User;

$user = User::find(3);
$form = new EditUserForm($user);

echo $form->render();

Form Validation

The library will automagically generate validation rules for you based on the nested components of the form. The $form->getValidationRules() method can be called to generate the rules.

The validation of the actual form data is left up to you.

Controller Validation

use App\Forms\CreateUserForm;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;

class UserController extends Controller
{
    public function postCreate(Request $request)
    {
        $request->validate((new CreateUserForm())->getValidationRules());
    }
}

Form Request Validation

namespace App\Http\Requests;

use App\Forms\CreateUserForm;
use Illuminate\Foundation\Http\FormRequest;

class CreateUserRequest extends FormRequest
{
    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return (new CreateUserForm())->getValidationRules();
    }
}

Components

The following components are bundled into this package:

You can also create your own custom components by implementing ComponentInterface, or FormControlInterface which extends ComponentInterface.

Checkbox (single)

use Balfour\LaravelFormBuilder\Components\Checkbox;

Checkbox::build()
    ->name('remember_me')
    ->label('Remember me for my next visit')
    ->defaults(1);
    
Checkbox::build()
    ->name('agree')
    ->label('I agree to the terms and conditions')
    ->rule('accepted');

Checkboxes (multiple)

use App\Models\Role;
use Balfour\LaravelFormBuilder\Components\Checkboxes;

$user = auth()->user();

// using an array of options
$roles = Role::pluck('name', 'id')
    ->toArray();

Checkboxes::build()
    ->name('roles')
    ->options($roles)
    ->defaults(function () use ($user) {
        return $user->roles
            ->pluck('id')
            ->toArray();
    });

// using a model, or options class
// the class must have a static listify() method which returns an array of key => value pairs
Checkboxes::build()
    ->name('roles')
    ->options(Role::class)
    ->setVisibility(auth()->user()->can('assign-role'))
    ->defaults(function () use ($user) {
        return $user->roles
            ->pluck('id')
            ->toArray();
    });
    
// using a callable to resolve options
Checkboxes::build()
    ->name('roles')
    ->options(function () {
        return Role::pluck('name', 'id')
            ->toArray();
    });

DateInput

use Balfour\LaravelFormBuilder\Components\DateInput;

// if no label is given, it's automagically generated from the component's name
DateInput::build()
    ->name('start_date');
   
// we can add a custom validation rule on top of the automagically generated ones
DateInput::build()
    ->name('start_date')
    ->rule('after:today');

EmailInput

use Balfour\LaravelFormBuilder\Components\EmailInput;

EmailInput::build()
    ->name('email');
    
// here's another custom validation rule on top of the 'email' one
EmailInput::build()
    ->name('email')
    ->rule('unique:users,email');

FieldSet

use Balfour\LaravelFormBuilder\Components\DateInput;
use Balfour\LaravelFormBuilder\Components\FieldSet;

FieldSet::build()
    ->legend('General')
    ->with([
        DateInput::build()
            ->name('start_date')
            ->required(),
        DateInput::build()
            ->name('end_date')
            ->required(),
    ]);

FileInput

use Balfour\LaravelFormBuilder\Components\FileInput;

FileInput::build()
    ->name('photo')
    ->required()
    ->mimes(['image/jpeg'])
    ->maxSize('5120')

HiddenInput

use Balfour\LaravelFormBuilder\Components\HiddenInput;

HiddenInput::build()
    ->name('user_id')
    ->defaults(1);

MobileNumberInput

use Balfour\LaravelFormBuilder\Components\MobileNumberInput;

MobileNumberInput::build()
    ->defaults(auth()->user()->mobile_number);

NumberInput

use Balfour\LaravelFormBuilder\Components\NumberInput;

NumberInput::build()
    ->name('age');

PasswordInput

use Balfour\LaravelFormBuilder\Components\PasswordInput;

PasswordInput::build();

// if you want a custom label...
PasswordInput::build()
    ->name('Your Password');

PhoneNumberInput

use Balfour\LaravelFormBuilder\Components\PhoneNumberInput;

PhoneNumberInput::build()
    ->defaults(auth()->user()->phone_number);

RadioButtonGroup

use Balfour\LaravelFormBuilder\Components\RadioButtonGroup;

RadioButtonGroup::build()
    ->name('food_preference')
    ->options([
        [
            'key' => 'chicken',
            'value' => 'Chicken',
        ],
        [
            'key' => 'beef',
            'value' => 'Beef',
        ],
    ])
    ->defaults('chicken');
    
// you can also add icons
RadioButtonGroup::build()
    ->name('food_preference')
    ->options([
        [
            'key' => 'chicken',
            'value' => 'Chicken',
            'icon' => 'fas fa-egg',
        ],
        [
            'key' => 'beef',
            'value' => 'Beef',
            'icon' => 'fas fa-hamburger',
        ],
    ])
    ->defaults('chicken');

RichTextEditor

use Balfour\LaravelFormBuilder\Components\RichTextEditor;

// this will render a textarea with a 'wysiwyg' class
// it's up to you to instantiate a rich text editor on this element, such as with
// TinyMCE or Froala Editor
RichTextEditor::build()
    ->name('description')
    ->rows(10)
    ->required();

Row

use Balfour\LaravelFormBuilder\Components\DateInput;
use Balfour\LaravelFormBuilder\Components\Row;

Row::build()
    ->with([
        DateInput::build()
            ->name('start_date')
            ->required(),
        DateInput::build()
            ->name('end_date')
            ->required(),
    ]);

Select

use App\Models\Country;
use Balfour\LaravelFormBuilder\Components\Select;

// using an array of options, and include a default 'empty' option
Select::build()
    ->name('country_id')
    ->options([
        1 => 'South Africa',
        2 => 'Amsterdam',
        3 => 'Italy',
    ])
    ->hasEmptyOption();

// using a model, or options class
// the class must have a static listify() method which returns an array of key => value pairs
Select::build()
    ->name('country_id')
    ->options(Country::class)
    ->defaults(1);
    
// using a callable to resolve options
Select::build()
    ->name('country_id')
    ->options(function () {
        return [
            1 => 'South Africa',
            2 => 'Amsterdam',
            3 => 'Italy',
        ];
    });

TextArea

use Balfour\LaravelFormBuilder\Components\TextArea;

TextArea::build()
    ->name('description')
    ->rows(15)
    ->required()
    ->defaults('This is the default description.');

TextInput

use Balfour\LaravelFormBuilder\Components\TextInput;

TextInput::build()
    ->name('first_name')
    ->required()
    ->defaults(auth()->user()->first_name);
    
// you can show a placeholder message
TextInput::build()
    ->name('first_name')
    ->required()
    ->placeholder('Your First Name')
    ->defaults(auth()->user()->first_name);

// you can also customise the type, and add a custom validation rule
TextInput::build()
    ->name('email')
    ->type('email')
    ->required()
    ->defaults(auth()->user()->email)
    ->rule('unique:users,email');

TimePicker

use Balfour\LaravelFormBuilder\Components\TimePicker;

// the component is rendered as an input with a 'time-picker' class
// you'd need to use a timepicker js lib of your choice to render it
TimePicker::build()
    ->name('start_time')
    ->required()
    ->defaults('14:30');

ToggleSwitch

use Balfour\LaravelFormBuilder\Components\ToggleSwitch;

ToggleSwitch::build()
    ->name('two_factor_authentication_enabled')
    ->defaults(true);
    
// use custom on and off labels
ToggleSwitch::build()
    ->name('subscribe')
    ->onLabel('Yes')
    ->offLabel('No')
    ->defaults(true);