EasyCorp / EasyAdminBundle

EasyAdmin is a fast, beautiful and modern admin generator for Symfony applications.
MIT License
4.09k stars 1.02k forks source link

Custom actions confirmation #3455

Open soullivaneuh opened 4 years ago

soullivaneuh commented 4 years ago

Short description of what this feature will allow to do:

Be able to quickly configure a confirmation page/popup about a custom action

Example of how to use this feature

Something like this:

public function configureActions(Actions $actions): Actions
{
    return parent::configureActions($actions)
        ->add(
            Crud::PAGE_DETAIL,
            Action::new('reset', 'Reset')
                ->linkToCrudAction('reset')
                ->askConfirmation('Are you very very sure? This will destroy the world!')
            ,
        )
    ;
}

I found an option on the twig files that looks like the feature I want: https://github.com/EasyCorp/EasyAdminBundle/blob/a025860a53c722cb3d50fb34ed79803432dd6971/src/Resources/views/crud/form_theme.html.twig#L459

But I don't find anything on the documentation about how to use it.

soullivaneuh commented 4 years ago

Also related to #1279 and #2978.

You apparently did some work about a feature like this, but it was too complicated.

I'm not sure to understand. How a confirmation message to display is too complicated? The proposed configuration way looks ok to me. :thinking:

soullivaneuh commented 4 years ago

It looks we already have enough resource to make the confirmation modal with ease thanks to the delete action implement:

image

soullivaneuh commented 4 years ago

Another nice to have thing would be alternative actions to propose in addition to "Cancel". For example:

public function configureActions(Actions $actions): Actions
{
    return parent::configureActions($actions)
        ->add(
            Crud::PAGE_DETAIL,
            Action::new('reset', 'Reset')
                ->linkToCrudAction('reset')
                ->askConfirmation('Are you very very sure? This will destroy the world!', [
                    Action::new('index', 'Return to list')->linkToCrudAction('index'),
                ])
            ,
        )
    ;
}

What do you think?

soullivaneuh commented 4 years ago

The askConfirmation may even take 3 parameters: Title, message (optional), alternativeActions (optional).

This would look like this:

Action::new('reset', 'Reset')
    ->linkToCrudAction('reset')
    ->askConfirmation(
        'Are you very very sure to reset the source of life?',
        'This will destroy the world!',
        [
            Action::new('index', 'Return to list')->linkToCrudAction('index'),
        ],
    )
;
Quenway commented 2 years ago

Hi @soullivaneuh,

I had the same problem here... A lot of custom action who should ask confirm before process... I found a solution right now, and hope it can help you...

I take my inspiration from the delete-action which already toggle a confirm modal :D

So, here we go :

  1. Create a template in directory templates/bundles/EasyAdminBundle/layout.html.twig
{% extends '@!EasyAdmin/layout.html.twig' %}

{%  block content_footer_wrapper %}
    <div id="modal-confirm" class="modal fade" tabindex="-1">
        <div class="modal-dialog">
            <div class="modal-content">
                <div class="modal-body">
                    <h4>Êtes vous sûr ?</h4>
                    <p>Parce que ça va faire des trucs de fifou</p>
                </div>
                <div class="modal-footer">
                    <button type="button" data-bs-dismiss="modal" class="btn btn-secondary">
                        <span class="btn-label">{{ 'action.cancel'|trans([], 'EasyAdminBundle') }}</span>
                    </button>

                    <button type="button" data-bs-dismiss="modal" class="btn btn-success" id="modal-confirm-button">
                        <span class="btn-label">{{ 'action.confirm'|trans([], 'EasyAdminBundle') }}</span>
                    </button>
                </div>
            </div>
        </div>
    </div>
{% endblock %}
  1. Add HTML Attribute data-bs-toggle and data-bs-target to your custom action PLUS the CSS class confirm-action
        $validAction = Action::new('valid', 'Valider', 'fa fa-check')
            ->displayAsLink()
            ->linkToCrudAction('validate')
            ->addCssClass('confirm-action')
            ->setHtmlAttributes([
                'data-bs-toggle' => 'modal',
                'data-bs-target' => '#modal-confirm',
            ])
        ;
  1. Create a JS file in public/assets/js/, I named it confirm-modal.js (Globally, it prevent the redirection onClick, show the modal, and add a listener on confirm button in the modal in order to redirect to the href link.
document.addEventListener("DOMContentLoaded",(
    function() {
        document.querySelectorAll(".confirm-action").forEach((function(e){
            e.addEventListener("click",(function(t){
                t.preventDefault();
                document.querySelector("#modal-confirm-button").addEventListener("click",(function(){
                    location.replace(e.getAttribute("href"));
                }));
            }));
        }));
    }
));
  1. In the CRUD controller of your entity, create configureAssets function and call the JS file confirm-modal.js
public function configureAssets(Assets $assets): Assets
{
    $assets->addJsFile('assets/js/confirm-modal.js');

    return parent::configureAssets($assets);
}

It's my first shot, it's not perfectly integrated but it Works ! Now I just have to adjust some things to make this more generic.

I'm glad that I can share this with you, personally, this thing made my day 🥇

DARDORKE commented 2 years ago

Hello ! I tried this solution by updating 'edit/new actions' to get a confirmation modal but when I confirm into the modal window, the page page reload and nothing persist in the database. Any solutions ?

ModuleCrudController (src/Controller/Admin/ModuleCrudController.php) :

public function configureActions(Actions $actions): Actions
    {
        return $actions
            ->update(Crud::PAGE_INDEX, Action::NEW,
            fn(Action $action) => $action
                ->setLabel('Ajouter un module'))
            ->update(Crud::PAGE_INDEX, Action::BATCH_DELETE,
            fn(Action$action) => $action
                ->setLabel('Supprimer'))
            ->update(Crud::PAGE_NEW, Action::SAVE_AND_ADD_ANOTHER,
            fn(Action $action) => $action
                ->setLabel('Créer et ajouter un nouveau module')
                ->displayAsLink()
                ->addCssClass('confirm-action')
                ->setHtmlAttributes([
                    'data-bs-toggle' => 'modal',
                    'data-bs-target' => '#modal-confirm',
                ]));

ModuleCrudController (src/Controller/Admin/ModuleCrudController.php) :

public function configureAssets(Assets $assets): Assets
    {
        $assets->addJsFile('assets/js/confirm-modal.js');

        return parent::configureAssets($assets);
    }

layout.html.twig (templates/bundles/EasyAdminBundle/layout.html.twig) :

{% extends '@!EasyAdmin/layout.html.twig' %}

{%  block content_footer_wrapper %}
    <div id="modal-confirm" class="modal fade" tabindex="-1">
        <div class="modal-dialog">
            <div class="modal-content">
                <div class="modal-body">
                    <h4>Êtes-vous sûr ?</h4>
                    <p>Parce que ça va faire des trucs de fifou</p>
                </div>
                <div class="modal-footer">
                    <button type="button" data-bs-dismiss="modal" class="btn btn-secondary">
                        <span class="btn-label">{{ 'action.cancel'|trans([], 'EasyAdminBundle') }}</span>
                    </button>

                    <button type="button" data-bs-dismiss="modal" class="btn btn-success" id="modal-confirm-button">
                        <span class="btn-label">{{ 'action.confirm'|trans([], 'EasyAdminBundle') }}</span>
                    </button>
                </div>
            </div>
        </div>
    </div>
{% endblock %}

confirm-modal.js (public/assets/js/confirm-modal.js) :

document.addEventListener("DOMContentLoaded",(
    function() {
        document.querySelectorAll(".confirm-action").forEach((function(e){
            e.addEventListener("click",(function(t){
                t.preventDefault();
                document.querySelector("#modal-confirm-button").addEventListener("click",(function(){
                    location.replace(e.getAttribute("href"));
                }));
            }));
        }));
    }
));
elkouo commented 1 year ago

Are you on the EDIT or LIST page ?

Because on EDIT page, the layout is not include. You can rewrite "crud/detail.html.twig" with the same {% block content_footer_wrapper %}

lwillems commented 1 year ago

Same thing with stimulus.js (symfony ux)

overrided ea layout.html.twig

{% extends '@!EasyAdmin/layout.html.twig' %}
{% block content %}
    <div data-controller="confirm-modal">
        {{ include('/admin/crud/includes/_action_modal.html.twig') }}
        {{ parent() }}
    </div>
{% endblock %}

_action_modal.html.twig

<div id="modal-confirm" class="modal fade" tabindex="-1">
    <div class="modal-dialog">
        <div class="modal-content">
            <div class="modal-body">
                <h4>{{ 'action.modal.archive_title'|trans([], 'admin') }}</h4>
            </div>
            <div class="modal-footer">
                <button type="button" data-bs-dismiss="modal" class="btn btn-secondary">
                    <span class="btn-label">{{ 'action.cancel'|trans([], 'EasyAdminBundle') }}</span>
                </button>

                <button type="button" data-confirm-modal-target="confirm" data-bs-dismiss="modal" class="btn btn-danger" id="modal-confirm-button">
                    <span class="btn-label">{{ 'batch_action_modal.action'|trans(domain = 'EasyAdminBundle') }}</span>
                </button>
            </div>
        </div>
    </div>
</div>

In your controller custom action config

    $archiveBatch = Action::new('archive', 'action.archive', 'fa fa-archive')
        ->linkToCrudAction('archiveBatch')
        ->addCssClass('text-danger')
        ->setHtmlAttributes([
            'data-action' => 'click->confirm-modal#confirm',
            'data-bs-toggle' => 'modal',
            'data-bs-target' => '#modal-confirm',
        ]);

confirm_modal_controller.js

import { Controller } from '@hotwired/stimulus';

/*
* The following line makes this controller "lazy": it won't be downloaded until needed
* See https://github.com/symfony/stimulus-bridge#lazy-controllers
*/
/* stimulusFetch: 'lazy' */
export default class extends Controller {
    static targets = ['confirm']

    confirm(event) {
        event.preventDefault();
        this.confirmTarget.addEventListener('click', function() {
            location.replace(event.target.getAttribute('href'));
        });
    }
}
astronati commented 1 year ago

news about this?

alex-mbp commented 11 months ago

This would be a useful feature

radziejewicz commented 5 months ago

Bump this issue :D

Seros commented 4 days ago

Just leaving this here in case someone is looking for a quick and easy solution https://github.com/EasyCorp/EasyAdminBundle/issues/2978#issuecomment-1505515228