Are You Sure?

All we know the confirmation dialog boxes to be prompted to the user - e.g. on deletion actions - and for my Laravel applications I've consolidated an approach a bit more articulated then the classic (and ugly, and poorly informative) confirm('Are you sure?') attached to some submit or click Javascript event.

What I do is:

  • prepare an askdestroy route in my controller, returning a Bootstrap Modal
  • the modal contains proper graphics, informative text customized by the type of Model to be removed (e.g. Removing this, will be removed also that or Removing this, that will happen), and a form which action is the actual destroy route for the same Model. Sometime the form also has some addictional operation configurable by the user (e.g. This category is going to be removed, which other category assign to existing belonging items?) then performed in the destroy function of the controller
  • to the "Delete" button in the main interface, I attach the fetch and display of the modal in the askdetroy route

Using Larastrap for the Bootstrap modal and jBob for the client-side interaction of fetch + display of the same modal, most of the boilerplate code is then reduced to a new default route in my resource controllers and a few Blade templates.

// The route

Route::get('item/askdestroy/{id}', [ItemController::class, 'askdestroy'])->name('item.askdestroy');

// The function

public function askdestroy($id)
{
    $item = Item::find($id);
    return view('items.askdestroy', compact('item'));
}

// The modal

<x-larastrap::modal>
    <x-larastrap::form :action="route('item.destroy', $item->id)" method="DELETE">
        Are you sure?
    </x-larastrap:form>
</x-larastrap::modal>

// The trigger

<x-larastrap::link label="Delete" color="danger" classes="async-modal" :href="route('item.askdestroy', $item->id)" />

To reduce even further the boilerplates, I've started introducing my own special resource controllers automatic routing to add the askdestroy route by default (with the existing create, edit and so on). This can be done injecting a custom ResourceRegistrar into your own application container, able to handle the addictional route.

In AppServiceProvider I've added

use Illuminate\Routing\ResourceRegistrar;
use App\Services\ExtraResourceRegistrar;
use Illuminate\Routing\Router;

public function register()
{
  $this->app->bind(ResourceRegistrar::class, function ($app) {
    return new ExtraResourceRegistrar($app->make(Router::class));
  });
}

And then, in app/Services/ExtraResourceRegistrar.php file:

<?php

namespace App\Services;

use Illuminate\Routing\ResourceRegistrar;
use Illuminate\Routing\Router;

class ExtraResourceRegistrar extends ResourceRegistrar
{
  public function __construct(Router $router)
  {
    parent::__construct($router);

    /*
      In ResourceRegistrar, $resourceDefaults is an array containing all the 
      default routes to be managed for resource controllers.
      Here we append the new 'askdestroy'
    */
    $this->resourceDefaults[] = 'askdestroy';
  }

  /*
    Each default route must have his own addResource* function, which defines 
    the actual URL of the route, the name and so on
  */
  protected function addResourceAskdestroy($name, $base, $controller, $options)
  {
    $name = $this->getShallowName($name, $options);
    $uri = $this->getResourceUri($name).'/{'.$base.'}/askdestroy';
    $action = $this->getResourceAction($name, $controller, 'askdestroy', $options);
    return $this->router->get($uri, $action);
  }
}

Now askdestroy route is added to all Route::resource('items', ItemController::class);

Sooner or later I will pack all of this in a package...

Show Comments