True Custom Action
Keep on Learning!
If you liked what you've learned so far, dive in! Subscribe to get access to this tutorial plus video, code and script downloads.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeThe whole point of this "Pending Approval" section is to allow moderators to approve or delete questions. We can delete questions... but there's no way to approve them. Sure, we could add a little "Is Approved" checkbox to the form. But a true "approve" action with a button on the detail or index pages would be a lot nicer. It would also allow us to run custom code on approval if we need to. So let's create another custom action.
Adding the Action as a Button
Over in QuestionCrudController, say $approveAction = Action::new()... and I'll make up the word approve. Down at the bottom, add that to the detail page: ->add(Crud::PAGE_DETAIL, $approveAction).
| // ... lines 1 - 21 | |
| class QuestionCrudController extends AbstractCrudController | |
| { | |
| // ... lines 24 - 37 | |
| public function configureActions(Actions $actions): Actions | |
| { | |
| // ... lines 40 - 48 | |
| $approveAction = Action::new('approve'); | |
| // ... line 50 | |
| return parent::configureActions($actions) | |
| // ... lines 52 - 66 | |
| ->add(Crud::PAGE_DETAIL, $approveAction); | |
| } | |
| // ... lines 69 - 155 |
Before we try that, call ->addCssClass('btn btn-success') and ->setIcon('fa fa-check-circle'). Also add ->displayAsButton().
| // ... lines 1 - 37 | |
| public function configureActions(Actions $actions): Actions | |
| { | |
| // ... lines 40 - 48 | |
| $approveAction = Action::new('approve') | |
| ->addCssClass('btn btn-success') | |
| ->setIcon('fa fa-check-circle') | |
| ->displayAsButton(); | |
| // ... lines 53 - 70 | |
| } | |
| // ... lines 72 - 158 |
By default, an action renders as a link... where the URL is wherever you want it to go. But in this case, we don't want approval to be done with a simple link that makes a "GET" request. Approving something will modify data on the server... and so it should really be a "POST" request. This will cause the action to render as a button instead of a link. We'll see how that works in a minute.
Linking to a CRUD Action
Ok, we have now created the action... but we need to link it to a URL or to a CRUD action. In this case, we need a CRUD action where we can write the approve logic. So say linkToCrudAction() passing the name of a method that we're going to create later. Let's call it approve.
| // ... lines 1 - 37 | |
| public function configureActions(Actions $actions): Actions | |
| { | |
| // ... lines 40 - 48 | |
| $approveAction = Action::new('approve') | |
| ->linkToCrudAction('approve') | |
| // ... lines 51 - 71 | |
| } | |
| // ... lines 73 - 159 |
Sweet! Refresh and... duh! The button won't be here... but if we go to the detail page... got it! "Approve"!
Overriding the Template to Add a Form
Inspect element and check out the source code. Yup! This literally rendered as a button... and that's it. There's no form around this... and no JavaScript magic to make it submit. We can click this all day long and absolutely nothing happens. To make it work, we need to wrap it in a form so that, on click, it submits a POST request to the new action.
How can we do that? By leveraging a custom template. We know that EasyAdmin has lots of templates. Inside EasyAdmin... in its Resources/views/crud/ directory, there's an action.html.twig file. This is the template that's responsible for rendering every action. You can see that it's either an a tag or a button based on our config.
Copy the three lines on top that document the variables we have... and let's go create our own custom template. Inside templates/admin/, add a new file called approve_action.html.twig. Paste in the comments... and then... just to further help us know what's going on, dump that action variable: dump(action).
| {# @var ea \EasyCorp\Bundle\EasyAdminBundle\Context\AdminContext #} | |
| {# @var action \EasyCorp\Bundle\EasyAdminBundle\Dto\ActionDto #} | |
| {# @var entity \EasyCorp\Bundle\EasyAdminBundle\Dto\EntityDto #} | |
| {{ dump(action) }} |
To use this template, over in QuestionCrudController... right on the action, add ->setTemplatePath('admin/approve_action.html.twig').
| // ... lines 1 - 21 | |
| class QuestionCrudController extends AbstractCrudController | |
| { | |
| // ... lines 24 - 37 | |
| public function configureActions(Actions $actions): Actions | |
| { | |
| // ... lines 40 - 48 | |
| $approveAction = Action::new('approve') | |
| ->setTemplatePath('admin/approve_action.html.twig') | |
| // ... lines 51 - 72 | |
| } | |
| // ... lines 74 - 158 | |
| } |
Let's try it. Refresh and... cool! We see the dump and all the data on that ActionDto object. The most important thing for us is linkURL. This contains the URL we can use to execute the approve() action that we'll create in a minute.
And because this new template is only being used by our one action... we're free to do whatever we want! All the other actions are still using the core action.html.twig template. Add a form... with action="{{ action.linkUrl }}"... and then method="POST". Inside, we need the button. We could create it ourselves... or we can be lazy and {{ include('@EasyAdmin/crud/action.html.twig') }}.
| // ... lines 1 - 3 | |
| <form action="{{ action.linkUrl }}" method="POST"> | |
| {{ include('@EasyAdmin/crud/action.html.twig') }} | |
| </form> |
That's all we need! Reload the page... and inspect that element to see... exactly what we want: a form with the correct action... and our button inside. Though, we do need to fix the styling a little bit. Add class="me-2".
| // ... lines 1 - 3 | |
| <form action="{{ action.linkUrl }}" method="POST" class="me-2"> | |
| // ... lines 5 - 7 |
Refresh and... looks better!
Try clicking this. We get... a giant error! Progress!
The controller for URI "/admin" is not callable: Expected method "approve" on
[our class].
Let's add that custom controller method next, and learn how to generate URLs to other EasyAdmin pages from inside PHP.
23 Comments
Is there a way to add a popup containing a brief description of a given action?
Hey @ahmedbhs ,
That's possible, you can add some custom JavaScript that will intercept the click and show a popup... or even override some core EA templates to add a custom HTML code if needed.
I hope that helps!
Cheers!
Hi, thanks for the great course.
I have a small problem, I am working on adding a custom action in the detail page, and I created a custom twig template for it, and in order for it to function correctly I need to calculate some data in the backend first, and then pass the result to the frontend to use it in the action.
Here is how I am trying to do it:
and in the action template I have:
The problem is that the admin context in the frontend doesn't contain the variable I set in the backend. Is there another way to pass data from backend to frontend.
Hey @Omar-Drb!
I believe you are doing the correct thing. But I think the result is that you will have a variable called
teston the frontend: it will not be some new variable inside ofea. Try{{ dump() }}to see ALL of the variable you have available.Cheers!
Thank you very much for your reply,
I checked it but unfortunately these are the only variables that exists:
I also tried using the search functionality but there were no variable with the name 'test' anywhere
Hmm, dang! Here how it looks like it should work:
A) You call
parent::detail($context). That returns aKeyValueStoreobject.B) You call
$detail->set('test', $data);, which adds this to theKeyValueStoreC) You return the
KeyValueStorefrom the controllerD) A listener from EasyAdmin is called after your controller. It grabs your return value (the
KeyValueStore) - https://github.com/EasyCorp/EasyAdminBundle/blob/4.x/src/EventListener/CrudResponseListener.php#L28E) These are used as the variables when rendering the template - see https://github.com/EasyCorp/EasyAdminBundle/blob/4.x/src/EventListener/CrudResponseListener.php#L37 and https://github.com/EasyCorp/EasyAdminBundle/blob/4.x/src/EventListener/CrudResponseListener.php#L52
So, I don't know what's going wrong. But perhaps you can add some debugging code in the core of EasyAdmin to see what's not working as I expect.
Cheers!
Just wanted to share this insight:
If you need access to the current entity on a action data attribute, you can use this trick:
Ex show a modal if you want to block a user, that asks if you are sure to block user x?
entity is a var that lives in the twig action template, so you can abuse it like this:
Inside your template you can do this:
You still need to activate this function in Symfony:
More info: https://twig.symfony.com/doc/3.x/functions/template_from_string.html
Hey @Rudi-T
That's a nice trick, but it's not clear to me why you have to write the Twig template code inside a HTML attribute?
Cheers!
Hey @MolloKhan, It's to connect a stimulus controller and set the message of a modal.
A bit like the idea highlighted in this tutorial, that you could implement.
In our solution we don't need a custom template for each modal we show. Very dynamic with almost no code maintenance.
Is it possible that this is bugged on the latest version?
Warning: Undefined array key "ea"
Fix only works if I remove the form attribute of the button
Hey @Rudi-T!
Hmm, it's possible, though EasyAdmin hasn't gotten any major upgrades, so nothing "should" have broken. Where does this error come from - like what's the stack trace? And, is your above code (the 3x
setanddoa work-around for the error)?Cheers!
SomeWhere from this:
I tried adding this manually, but didn't do anything. The only workaround was removing the form attribute.
Hmm, yea, something isn't right. I'm actually a bit confused. Here's what I see:
1) We add a new action called
approve.2) We create a custom template to render this action's button -
approve_action.html.twig. This is ONLY responsible for rendering the "Approve" button and shouldn't affect anything else. This is also where your big fix code lives.3) This new button is surrounded by a
formthat will submit to the "approve" action.The confusing part is that, from your stacktrace, the error is coming from the
editaction - from after a form submit. What I can't figure out is how theapprove_action.html.twigis affecting this. When you click "Approve", it it executing theeditaction (and not theapproveaction)? If so, I'd love to see what theactionattribute looks like in theformin your HTML.Cheers!
Hi! In this lesson we added the "Approve" button to approve questions by users with ROLE_MODERATOR. But what I have to do if I want to show Boolean Field "isApproved" on index or other pages for users with lower roles, for example, ROLE_ADMIN in read-only status? I want that admins can see, approved this question or no.
If I did for this field ->setPermission('ROLE_ADMIN') - the field become editable, If I did ->setPermission('ROLE_MODERATOR') - the field disappears, function hideOn... or showOn do not give me the desired result too.
Thanks in advance!
Hey Ruslan,
Just add a
BooleanFieldand display it on PAGE_INDEX. You can render that field as a a label, see https://symfony.com/bundles/EasyAdminBundle/current/fields/BooleanField.html#renderasswitch - it will mean that users will only be able to read the value. And I suppose you can just show it to anyone, but keep the security role as ROLE_MODERATOR for editing only.Cheers!
Thank you, Viktor. It is not so simple. What, if on PAGE_INDEX this field should stay editable for ROLE_MODERATOR and not editable for ROLE_ADMIN?
Hey Ruslan,
I think it's pretty easy actually... just inject the Security service into your controller's constructor and use it to decide either the field should be rendered as a switch or as a label in the
configureFields(). And you can wrap in an if to hide it or make it only readonly on edit page for example.Cheers!
He there
Is there any other way to get the current entity in configureActions without using
The reason i am asking this is because i need a few parameters that is in the entity. I also have a custom template because the call is made by my stimulus controller. The only method i have found now is using callable on one of the link methods:
I have tried
$this->getContext()->getEntity()butgetContext()is null. I'm a bit stuck here, hope you can help.Hey Scott S.
You can only get the entity instance on the detail or edit page, so, you have to check that first, then you can get the instance through the AdminContext, or you can do it manually by getting the entityId from the request
Cheers!
Hello ! Unfortunately with this kind of action in POST, it causes something bad at the front-end. The action becomes a form as agreed, but it will take up a whole line, and the other actions will be shifted below. See for yourself
https://www.noelshack.com/2...
Hey Fabrice!
Hmm, you might be right! But, where/how are you seeing the icons that you posted? On the "index" page, by default, these should be rendered as a drop-down. And if I call
showEntityActionsInlined()to expand them, I still don't see the bad behavior. Is this on some other page?Cheers!
Hello! Yes, this is on one of my projects, in which the actions are rendered inlined. So maybe it's no wonder it doesn't do it for you. Yet the method of application is the same.
Hi!
Yea... that's super weird. I can't explain why your version looks bad but mine does not. But, in theory, it makes sense: a form will often be styled differently (often with display: block) than a normal button, which could cause this. In that case, you'll need to add some custom CSS, likely to set the form to display: inline. I'm doing some guessing, but that's the most likely culprit!
Cheers!
"Houston: no signs of life"
Start the conversation!