ycs77 / laravel-wizard

A web Setup Wizard for Laravel application.
MIT License
122 stars 18 forks source link

How can I handle several unique cases? #50

Closed mrizkihidayat66 closed 2 months ago

mrizkihidayat66 commented 2 months ago

Hi there,

I apologize for the frequent questions, but I'm truly grateful for your amazing library—it's been incredibly helpful. However, due to my limited skills and understanding, along with the unique requirements of my project, I'm finding it challenging to apply the library effectively.

Could you kindly assist me with the following?

Thank you so much for your help!

ycs77 commented 2 months ago

Hi @mrizkihidayat66, I think your problem must be solved using JavaScript.

First is publish the views to custom, like this:

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

Then you can add some JavaScript code below resources/views/vendor/wizard/base.blade.php:

    </div>

    <script>
    // do something...
    </script>
@endsection

This has some examples:

How can I intercept the "Next" button when it's pressed?

<script>
document.querySelector('form').addEventListener('submit', function (e) {
    if (have_something_should_prevent_next_button) {
        e.preventDefault();
    }
});
</script>

In a specific scenario, how can I force skip next step with "Next" button or programmatically?

The skip for step you can see Skip step, then remove the click event of "Skip" button.

Now you must add the id skip to "Skip" button and next to "Next" button, then add click event using JS:

<script>
// call the "Next" button programmatically
document.getElementById('skip').click();

// call the "Next" button programmatically
document.getElementById('next').click();
</script>

How can I disable the "Back" button in certain situations?

Add the disabled for button html or using blade @if:

<button type="button" class="btn btn-primary" disabled>
    @lang('wizard::generic.back')
</button>

@if ($condition)
<button type="button" class="btn btn-primary">
    @lang('wizard::generic.back')
</button>
@endif

I can only give you these tips, but as long as you have a blade view, you can use PHP and JavaScript to do what you want to do.

mrizkihidayat66 commented 2 months ago

This is an interesting approach, but I was expecting it to use steps and a controller like before. However, I'll give this approach a try. Thank you.

ycs77 commented 2 months ago

If you want to use steps and a controller, this has some examples:

How can I intercept the "Next" button when it's pressed?

Use the beforeWizardStepSave() hook in the WizardController, it will called before step validation and any processing:

use Illuminate\Http\Request;

class UserWizardController extends Controller
{
    protected function beforeWizardStepSave(Request $request)
    {
        // do somthing
        dd('clicked Next button');

        // or return redirect response
        return back();
    }
}

In a specific scenario, how can I force skip next step with "Next" button or programmatically?

use Illuminate\Http\Request;

class UserWizardController extends Controller
{
    protected function beforeWizardStepSave(Request $request)
    {
        // first you need cache the current status
        if ($this->wizard()->option('cache')) {
            $step = $this->getWizardStep($request, $request->route('step'));
            $step->cacheProgress($request);
        }

        // force redirect to next
        return $this->redirectTo();

        // or redirect to other step, pass the step slug
        return $this->redirectTo('email');
    }
}

How can I disable the "Back" button in certain situations?

Add to all steps:

class EmailStep extends Step
{
    protected $back = true;

    public function showBackButton(bool $show = null)
    {
        if ($show !== null) {
            $this->back = $show;
        }

        return $this->back;
    }
}

Add into view:

base.blade.php

@if ($stepRepo->hasPrev() && $step->showBackButton())
    <button type="button" class="btn btn-primary" ...>
        @lang('wizard::generic.back')
    </button>
@endif

Called the methods:

$step->showBackButton(); // get back button show status
$step->showBackButton(false); // set back button is hidden
mrizkihidayat66 commented 2 months ago

Hi, mate. When I try to implement beforeWizardStepSave in the controller, the validation rules are not being executed

ycs77 commented 2 months ago

Hi, mate. When I try to implement beforeWizardStepSave in the controller, the validation rules are not being executed

https://github.com/ycs77/laravel-wizard/blob/0e7ccc24bceacff3d29e82c7c773443685af3982/src/Wizardable.php#L95-L119

Because the beforeWizardStepSave() is before the validation, if you do not intercept and is to do when validated, you should use the wizardStepFormValidated() hook.

mrizkihidayat66 commented 2 months ago
class LicenseWizardController extends Controller
{
    protected function beforeBackWizardStep(Request $request)
    {
        /** @var mixed $step */
        $step = $this->wizard()->stepRepo()->current();

        if (method_exists($step, 'beforeBack')) {
            $step->beforeBack();
        }

        return true;
    }

    protected function wizardStepSaved(Request $request)
    {
        /** @var mixed $step */
        $step = $this->wizard()->stepRepo()->current();

        if (method_exists($step, 'beforeNext') && $request->query('_trigger') !== 'back') {
            $step->beforeNext();
        }
    }

    ...
class CustomerStep extends Step
{
    public function beforeNext() {
        \Illuminate\Support\Facades\Log::info('a');
    }

    ...
class ProductStep extends Step
{
    public function beforeBack() {
        try {
            $cart = app(\App\Services\CartService::class);
            $cart->clearCart();
        }
        catch (\Exception $e) {
            Log::error('Error: ' . $e->getMessage());
        }
    }

    public function beforeNext() {
        \Illuminate\Support\Facades\Log::info('b');

        if (!$this->validation()) {
            //
        }
    }

    public function validation()
    {
        try {
            $cart               = app(\App\Services\CartService::class);
            $customer_detail    = optional($this->find('customer'))->data() ?? [];
            $cartItems          = $cart->getCart();

            function checkOrderType($customer_detail) {
                $type = 'new';

                if (($customer_detail['status'] ?? '') === 'no' || ($customer_detail['order'] ?? '') === 'renew') {
                    $type = 'renew';
                }

                return $type;
            }

            switch (checkOrderType($customer_detail)) {
                case 'renew':
                    {

                    }
                    break;

                default:
                    {
                        $allValid   = collect($cartItems)->every(function ($item) {
                            return $item['type'] === 'new' &&
                                   $item['item_id'] === null &&
                                   $item['duration'] <= 3;
                        });

                        if (!$allValid) {
                            throw new \Exception('Cart validation failed for new order type. Ensure all items meet the criteria.');
                        }

                        return true;
                    }
                    break;
            }
        }
        catch (\Exception $e) {
            Log::error('Error: ' . $e->getMessage());
            return false;
        }
    }

    ...

This kind of interception for the back/next buttons works well, but I can't get it to influence the intercepted workflow. For example, in the following scenario, I want to prevent advancing to the next step.

if (!$this->validation()) {
        //
}
ycs77 commented 2 months ago

Oh, I understand your intention 😅, you want add some custom validation, could use ValidationException to throw the custom validation message:

use Illuminate\Validation\ValidationException;

class ProductStep extends Step
{
    public function saveData(Request $request, $data = null, $model = null)
    {
        // saving the request data...

        // the 'your_field' is the input field name to show the validation message
        throw ValidationException::withMessages([
            'your_field' => 'The error after saved data.',
        ]);
    }

    public function beforeBack() {
        try {
            $cart = app(\App\Services\CartService::class);
            $cart->clearCart();
        } catch (\Exception $e) {
            // the 'your_field' is the input field name to show the validation message
            throw ValidationException::withMessages([
                'your_field' => $e->getMessage(),
            ]);
        }
    }
}

Then set the cache to false for the WizardController:

class LicenseWizardController extends Controller
{
    /**
     * The wizard options.
     *
     * @var array
     */
    protected $wizardOptions = [
        'cache' => false,
    ];

    protected function beforeBackWizardStep(Request $request)
    {
        /** @var mixed $step */
        $step = $this->wizard()->stepRepo()->current();

        if (method_exists($step, 'beforeBack')) {
            $step->beforeBack();
        }

        return true;
    }
}

And I have changed the behavior for beforeBackWizardStep, please upgrade the package to ^4.1.0:

composer update ycs77/laravel-wizard:^4.1

I know you are unfamiliar with this package, but I need to give you a little reminder. If you open an issue to ask a question, you need to provide the context and more information for the issue, and the package author or contributor then knows your intention and will give back the best answer, it saves time.

For example in this conversation, the last code I know your intention is "throw the custom validation error or message", but it is not contained in the original question. I read the "How can I intercept the Next button when it's pressed?" I got the "How to intercept the button", so my first answer is to give you the JavaScript code.

I give you a method, I think this method not only applies to this package. If you want to ask a question about the software or package, you can provide some information:

And if you provide the example code with markdown, you could add syntax highlighting to increase readability.

I think it can speed up to resolution of the issue, the above is provided for your reference.

If you have any problem welcome to open a new issue, happy coding!

mrizkihidayat66 commented 2 months ago

Noted. I'm unfamiliar with this library and need more flexibility from it. Could you review my private project and provide your advice based on my unique case? If you can, I'll send you an invitation.

ycs77 commented 2 months ago

Could you review my private project and provide your advice based on my unique case?

The support of the private project is not in the range with this package, so if you want my support, which contains the code review and advice, please add a month of $30 to my sponsor, you could add just one month and then cancel it then get only one month support.

My Patreon link is: https://www.patreon.com/ycs77

Or you can keep the current method to continue to open the issues.

mrizkihidayat66 commented 2 months ago

Done, please check your patreon message~

ycs77 commented 2 months ago

Thanks to your sponsors❤️