ycs77/laravel-wizard

A web setup wizard for Laravel application.

Fund package maintenance!
Patreon

v4.5.0 2024-09-17 03:15 UTC

README

Latest Version on Packagist Software License GitHub Tests Action Status Style CI Build Status Total Downloads

A web setup wizard for Laravel application.

This package is adapted from smajti1/laravel-wizard.

Laravel wizard main image.

Table of Contents

Version Compatibility

Installation

Install the package via composer:

composer require ycs77/laravel-wizard

Publish config:

php artisan vendor:publish --tag=wizard-config

Usage

1. Generate controller and wizard steps

Now you can qckly generate the wizard controller and the wizard steps:

php artisan make:wizard User NameStep,EmailStep

This command generates the UserWizardController, NameStep, and EmailStep class, and appends the wizard route to routes/web.php.

routes/web.php

use App\Http\Controllers\UserWizardController;
use Illuminate\Support\Facades\Route;
use Ycs77\LaravelWizard\Facades\Wizard;

...

Wizard::routes('/wizard/user', UserWizardController::class, 'wizard.user');

If you can't use auto append route, you can set config/wizard.php attribute append_route to false.

2. Set steps

This is generated NameStep class, you can to model method set the model, to rules method set form validation, and save $data to your database via the saveData method, for example:

app/Steps/User/NameStep.php

<?php

namespace App\Steps\User;

use App\User;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;
use Ycs77\LaravelWizard\Step;

class NameStep extends Step
{
    /**
     * The step slug.
     *
     * @var string
     */
    protected $slug = 'name';

    /**
     * The step show label text.
     *
     * @var string
     */
    protected $label = 'Name';

    /**
     * Set the step model instance or the relationships instance.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Relations\Relation|null
     */
    public function model(Request $request)
    {
        return User::find(1);
    }

    /**
     * Save this step form data.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  array|null  $data
     * @param  \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Relations\Relation|null  $model
     * @return void
     */
    public function saveData(Request $request, $data = null, $model = null)
    {
        $data = Arr::only($data, 'name');
        $model->update($data);
    }

    /**
     * Validation rules.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return array
     */
    public function rules(Request $request)
    {
        return [
            'name' => 'reqred',
        ];
    }
}

And add some steps view, for example:

resources/views/steps/user/name.blade.php

<div class="form-group mb-3">
    <label for="name">Name</label>
    <input type="text" name="name" id="name" @class(['form-control', 'is-invalid' => $errors->has('name')]) value="{{ old('name', $step->data('name')) }}">

    @error('name')
        <span class="invalid-feedback">{{ $message }}</span>
    @enderror
</div>

resources/views/steps/user/email.blade.php

<div class="form-group mb-3">
    <label for="email">E-mail</label>
    <input type="email" name="email" id="email" @class(['form-control', 'is-invalid' => $errors->has('email')]) value="{{ old('email', $step->data('email')) }}">

    @error('email')
        <span class="invalid-feedback">{{ $message }}</span>
    @enderror
</div>

Next, browse the URL /wizard/user, and start to use the Laravel Wizard.

If you want to get the layout you can copy Laravel layouts/app.blade.php to resources/views/layouts/app.blade.php

3. Install wizard steps CSS package

The CSS for this package default view is based on the Bootstrap Steps, use NPM installation to use:

npm install bootstrap bootstrap-steps
// or yarn
yarn add bootstrap bootstrap-steps

Import to the app.scss file and run npm run dev or yarn run dev:

resources/sass/app.scss

...

@import 'bootstrap/scss/bootstrap';
@import 'bootstrap-steps/scss/bootstrap-steps';

Cache

Database Driver

To use the database wizard cache driver, you will need a database table to hold the cache data of the wizard. Generate a migration that creates this table, and runs the wizard:table Artisan command:

php artisan wizard:table

php artisan migrate

Disable Cache

Set cache in config/wizard.php to false to disable cache input data:

'cache' => false,

Or set it to your WizardController wizardOptions property:

protected $wizardOptions = [
    'cache' => false,
];

If disabled cache, the data will be saved in the data immediately after each step is sent. If you are afraid to save the data repeatedly, you can hide the Prev button, or use Model::updateOrCreate() (https://laravel.com/docs/6.x/eloquent#other-creation-methods).

Controller

Setting Configuration

Add wizardOptions property to your wizard controller, you can use the cache, driver, connection, and table options to override configuration.

app/Http/Controllers/UserWizardController.php

/**
 * The wizard options.
 *
 * @var array
 */
protected $wizardOptions = [
    'cache' => true,
    'driver' => 'session',
    'table' => 'wizards',
];

Customize View

This package layout view uses Bootstrap 5, but if you don't want to use default views, you can publish views to custom it:

php artisan vendor:publish --tag=wizard-views-bs5

If you used Bootstrap 4, you could publish the layouts:

php artisan vendor:publish --tag=wizard-views-bs4

If you used Tailwind CSS, you could publish the layouts:

php artisan vendor:publish --tag=wizard-views-tailwind

Now you can customize resources/views/vendor/wizard/*.blade.php in your Laravel project.

But if you want a custom-only one wizard view base view, you can copy the views from resources/views/vendor/wizard/*.blade.php to resources/views/wizards/user/*.blade.php. (user is wizardName property value on your wizard controller),

Step

Get cached data

For example, FirstStep has name and email fields, and SecondStep has age and phone fields. you can use the data method of step to get step data:

$name = $firstStep->data('name');
// 'Lucas'

$data = $secondStep->data();
// ['age' => '30', 'phone' => '0900111222']

Or you can use the step repository to get other step data:

$data = $secondStep->find('first')->data();
// ['name' => 'Lucas']

$name = $secondStep->find('first')->data('name');
// 'Lucas'

Redirect to the step

If you want to manually redirect to another step, you can use the redirectToStep() of the wizard, it will return a redirect response.

// given a step slug
return $wizard->redirectToStep('second');

// given a step insatnce
return $wizard->redirectToStep($secondStep);

Step repository

Step repository saves all steps data, if you want to use another step, you need to use it:

From wizard:

$stepRepo = $wizard->stepRepo();

From step:

$stepRepo = $step->getRepo();

Get the previous step:

$prevStep = $step->prev();
// same as:
$prevStep = $step->getRepo()->prev();

Get the next step:

$prevStep = $step->next();
// same as:
$nextStep = $step->getRepo()->next();

Step repository all can use method detailed reference: src/StepRepository.php

Upload Files

Since v2.3.3 upload files in Cache and No Cache are supported, if use the Cache Mode you can cache all input data and uploaded files to save in the last step:

<?php

class LastStep extends Step
{
    public function model(Request $request)
    {
        return $request->user();
    }

    public function saveData(Request $request, $data = null, $model = null)
    {
        $data = [
            'avatar' => $this->find('has-avatar-step')->data('avatar'),
        ];

        $data['avatar'] = $data['avatar']->store('avatar', ['disk' => 'public']);

        $model->update($data);
    }
}

Then add a step view to upload the avatar image:

resources/views/steps/user/has-avatar.blade.php

<div class="form-group mb-3">
    <label for="avatar">Avatar</label>
    <input type="file" name="avatar" id="avatar" @class(['form-control', 'is-invalid' => $errors->has('avatar')])>

    @error('avatar')
        <span class="invalid-feedback">{{ $message }}</span>
    @enderror
</div>

Skip step

Note: v2.3.3+

To make Step skippable, set the $skip property to true, then this Step will skip the validation and save data:

app/Steps/User/NameStep.php

<?php

class NameStep extends Step
{
    /**
     * Is it possible to skip this step.
     *
     * @var boolean
     */
    protected $skip = true;
}

Passing data to views

Because each step is injected into the view of the step, so just add the method to return the data in the step class. For example, pass the data of the select options to view:

app/Steps/User/NameStep.php

<?php

class NameStep extends Step
{
    public function getOptions()
    {
        return [
            'Taylor',
            'Lucas',
        ];
    }
}

resources/views/steps/user/name.blade.php

<div class="form-group mb-3">
    <label for="name">Select name</label>
    <select id="name" name="name" @class(['form-control', 'is-invalid' => $errors->has('name')])>
        <option value="">Select...</option>
        @foreach ($step->getOptions() as $option)
            <option
                value="{{ $option }}"
                @selected($option === old('name', $step->data('name')))
            >
                {{ $option }}
            </option>
        @endforeach
    </select>

    @error('name')
        <span class="invalid-feedback">{{ $message }}</span>
    @enderror
</div>

The getOptions method is custom and can be changed at will.

Save data on another step

Suppose there are now two Steps NameStep and EmailStep. First, don't set the Model for all Steps, but don't use the last one:

app/Steps/User/NameStep.php

<?php

class NameStep extends Step
{
    public function model(Request $request)
    {
        //
    }

    public function saveData(Request $request, $data = null, $model = null)
    {
        //
    }
}

Next, receive all the data in the last Step and save the Model:

app/Steps/User/EmailStep.php

<?php

class EmailStep extends Step
{
    public function model(Request $request)
    {
        return new User();
    }

    public function saveData(Request $request, $data = null, $model = null)
    {
        $data = $this->getStepsData();
        $model->fill($data)->save();
    }
}

Set relationships model

Similarly, you can set the relationships model in the model method of the step.

use Illuminate\Support\Arr;

/**
 * Set the step model instance or the relationships instance.
 *
 * @param  \Illuminate\Http\Request  $request
 * @return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Relations\Relation|null
 */
public function model(Request $request)
{
    return $request->user()->posts();
}

/**
 * Save this step form data.
 *
 * @param  \Illuminate\Http\Request  $request
 * @param  array|null  $data
 * @param  \Illuminate\Database\Eloquent\Model\Illuminate\Database\Eloquent\Relations\Relation|null  $model
 * @return void
 */
public function saveData(Request $request, $data = null, $model = null)
{
    $data = Arr::only($data, ['title', 'content']);
    $model->create($data);
}

Force restart from first step

If you don't keep the cache on click the start wizard button, you can change the link to /wizard/user?_reset=1.

If you have any custom cache data you want to clean up, you can set the clean up into cleanUpWizard() hook in your WizardController, then it will clean on wizard done or visit route with ?_reset=1 parameter:

app/Http/Controllers/UserWizardController.php

/**
 * Clean up the wizard event.
 *
 * @return void
 */
protected function cleanUpWizard(Request $request)
{
    // Cleanup wizard cache...
}

Commands

Make wizard:

The make:wizard command and make:wizard:controller command difference, is make:wizard command will append the route and not confirm generate step.

php artisan make:wizard User NameStep,EmailStep

Make controller:

The make:wizard:controller command only generates the WizardController, NameStep, and EmailStep class.

php artisan make:wizard:controller UserController --steps=NameStep,EmailStep

Make step:

php artisan make:wizard:step NameStep

With step label and wizard:

php artisan make:wizard:step NameStep --label="Name" --slug=name --wizard=user

Add custom view path:

php artisan make:wizard:step NameStep --label="Name" --slug=name --view=steps.user.name --wizard=user

Sponsor

If you think this package has helped you, please consider Becoming a sponsor to support my work~ and your avatar will be visible on my major projects.

Become a Patron

Credits

License

MIT LICENSE