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 SubscribeWhat else can we do with the menu? Well, of course, we can expand the simple entity items to get more control. To expand it, add entity: User
. Now we can go crazy with label: Users
:
... lines 1 - 80 | |
easy_admin: | |
... line 82 | |
design: | |
... lines 84 - 94 | |
menu: | |
... line 96 | |
- { entity: 'User', label: 'Users' } | |
... lines 98 - 177 |
Remember, you can also specify the label under the User
entity itself. If we added a label
key here, it would apply to the menu, the header to that section and a few other places. But under menu
, it just changes the menu text.
Do the same thing for GenusNote
, with label: Notes
, and also for sub families: entity:
SubFamily, label: Sub-Families:
... lines 1 - 80 | |
easy_admin: | |
... line 82 | |
design: | |
... lines 84 - 94 | |
menu: | |
... lines 96 - 99 | |
- { entity: 'GenusNote', label: 'Notes' } | |
- { entity: 'SubFamily', label: 'Sub-Families' } | |
... lines 102 - 177 |
At this point, it should be no surprise that we can control the icon for each menu. Like, icon: user
, icon: sticky-note
and icon: ''
:
... lines 1 - 80 | |
easy_admin: | |
... line 82 | |
design: | |
... lines 84 - 94 | |
menu: | |
... line 96 | |
- { entity: 'User', label: 'Users', icon: 'user' } | |
... lines 98 - 99 | |
- { entity: 'GenusNote', label: 'Notes', icon: 'sticky-note' } | |
- { entity: 'SubFamily', label: 'Sub-Families', icon: '' } | |
... lines 102 - 177 |
Before configuring anything, each item has a little arrow icon. With empty quotes, even that icon is gone.
Oh, but the fanciness does, not, stop! The menu does a lot more than just simple links: it has separators, groups and sub-links. Above Genus
, create a new item that only has a label:
... lines 1 - 80 | |
easy_admin: | |
... line 82 | |
design: | |
... lines 84 - 94 | |
menu: | |
... line 96 | |
- { entity: 'User', label: 'Users', icon: 'user' } | |
- { label: 'Genus' } | |
... lines 99 - 178 |
Yep, this has no route
key and no entity
key. We're not linking to anything.
Instead, this just adds a nice separator. Or, you can go a step further and create a sub-menu. Change this new menu item to use the expanded format. Then, add a children
key. Indent all the other links so that they live under this:
... lines 1 - 80 | |
easy_admin: | |
... line 82 | |
design: | |
... lines 84 - 94 | |
menu: | |
... line 96 | |
- { entity: 'User', label: 'Users', icon: 'user' } | |
- | |
label: 'Genus' | |
children: | |
- Genus | |
- GenusHorde | |
... line 103 | |
- { entity: 'GenusNote', label: 'Notes', icon: 'sticky-note' } | |
- { entity: 'SubFamily', label: 'Sub-Families', icon: '' } | |
... lines 106 - 181 |
And just to make it even nicer, add a separator called Related
:
... lines 1 - 80 | |
easy_admin: | |
... line 82 | |
design: | |
... lines 84 - 94 | |
menu: | |
... line 96 | |
- { entity: 'User', label: 'Users', icon: 'user' } | |
- | |
label: 'Genus' | |
children: | |
- Genus | |
- GenusHorde | |
- { label: 'Related' } | |
- { entity: 'GenusNote', label: 'Notes', icon: 'sticky-note' } | |
- { entity: 'SubFamily', label: 'Sub-Families', icon: '' } | |
... lines 106 - 181 |
Try it! Try it! Nice! The Genus
menu expands to show the sub-items and the Related
separator.
We can also link to the different entity sections, but with different sort options. We already have a Genus
link that will take us to that page with the normal sort. But let's not limit ourselves! We could also add another link to that same section, with a different label: Genuses (sorted by ID)
and a params
key:
... lines 1 - 80 | |
easy_admin: | |
... line 82 | |
design: | |
... lines 84 - 94 | |
menu: | |
... lines 96 - 97 | |
- | |
label: 'Genus' | |
children: | |
- Genus | |
- | |
entity: 'Genus' | |
label: 'Genuses (sorted by ID)' | |
params: | |
... lines 106 - 188 |
Here, we can control whatever query parameters we want, like sortField: id
, sortDirection: ASC
and... heck pizza: delicious
:
... lines 1 - 80 | |
easy_admin: | |
... line 82 | |
design: | |
... lines 84 - 94 | |
menu: | |
... lines 96 - 97 | |
- | |
label: 'Genus' | |
children: | |
- Genus | |
- | |
entity: 'Genus' | |
label: 'Genuses (sorted by ID)' | |
params: | |
sortField: 'id' | |
sortDirection: 'ASC' | |
pizza: 'delicious' | |
... lines 109 - 188 |
That last query parameter won't do anything... but it doesn't make it any less true!
Ok, refresh! Then try out that new link. Yea! We're sorting by id and you might also notice in the address bar that pizza=delicious
.
On that note, one of the other query parameters is action
, which we can also set to anything. Copy this entire new menu link and - at the top of children
- paste it. This time, let's link to the show page of 1 specific genus... our favorite "Pet genus". To do that, set action
to show
and id
to some id in the database, like 2:
... lines 1 - 80 | |
easy_admin: | |
... line 82 | |
design: | |
... lines 84 - 94 | |
menu: | |
... lines 96 - 97 | |
- | |
label: 'Genus' | |
children: | |
- | |
entity: 'Genus' | |
label: 'Pet genus' | |
icon: 'paw' | |
params: | |
action: 'show' | |
id: 2 | |
- Genus | |
... lines 109 - 195 |
This isn't anything special, we're just taking advantage of how the query parameters work in EasyAdminBundle.
And while we're here, it might also be nice to add a link to the front-end of our app. This is also nothing special: add a new link that points to the app_genus_list
route called "Open front-end":
... lines 1 - 80 | |
easy_admin: | |
... line 82 | |
design: | |
... lines 84 - 94 | |
menu: | |
... line 96 | |
- { label: 'Open front-end', route: 'app_genus_list' } | |
... lines 98 - 196 |
Refresh! And try that link. Nice!
In addition to routes, if you want, you can just link to external URLs. Go to the bottom of the list... and make sure we're at the root level. Add a new section called "Important stuff" with icon: explanation
and a children
key:
... lines 1 - 80 | |
easy_admin: | |
... line 82 | |
design: | |
... lines 84 - 94 | |
menu: | |
... lines 96 - 120 | |
- | |
label: 'Important stuff' | |
icon: 'exclamation' | |
children: | |
... lines 125 - 208 |
I'll paste a couple of very important external links for silly kittens and wet cats:
... lines 1 - 80 | |
easy_admin: | |
... line 82 | |
design: | |
... lines 84 - 94 | |
menu: | |
... lines 96 - 120 | |
- | |
label: 'Important stuff' | |
icon: 'exclamation' | |
children: | |
- | |
label: 'Silly kittens' | |
url: 'https://www.youtube.com/results?search_query=silly+kittens' | |
target: '_blank' | |
- | |
label: 'Wet cats' | |
url: 'http://www.boredpanda.com/funny-wet-cats/' | |
target: '_blank' | |
... lines 133 - 208 |
Yep, instead of entity
or route
keys, you can skip all of that and just add url
. And of course, you can set the target
on any item.
Ok team, our admin menu is complete! The last thing I want to show you isn't anything special to this bundle: it's just a nice way to organize any configuration. In fact, this trick will become the standard way to organize things in Symfony 4.
Right now, well, our admin configuration goes from line 81 of config.yml
to line
To clear things up, I'd like to create a new file called admin.yml
. Copy all of this config, remove it, and add it to admin.yml
:
easy_admin: | |
site_name: 'Aqua<i>Note</i>' | |
design: | |
brand_color: '#81b9ba' | |
assets: | |
css: ['css/custom_backend.css'] | |
js: | |
- 'https://unpkg.com/snarkdown@1.2.2/dist/snarkdown.umd.js' | |
- 'js/custom_backend.js' | |
templates: | |
field_id: 'admin/fields/_id.html.twig' | |
form_theme: | |
- horizontal | |
- easy_admin/_form_theme.html.twig | |
menu: | |
- { label: 'Dashboard', route: 'admin_dashboard', default: true } | |
- { label: 'Open front-end', route: 'app_genus_list' } | |
- { entity: 'User', label: 'Users', icon: 'user' } | |
- | |
label: 'Genus' | |
children: | |
- | |
entity: 'Genus' | |
label: 'Pet genus' | |
icon: 'paw' | |
params: | |
action: 'show' | |
id: 2 | |
- Genus | |
- | |
entity: 'Genus' | |
label: 'Genuses (sorted by ID)' | |
params: | |
sortField: 'id' | |
sortDirection: 'ASC' | |
pizza: 'delicious' | |
- GenusHorde | |
- { label: 'Related' } | |
- { entity: 'GenusNote', label: 'Notes', icon: 'sticky-note' } | |
- { entity: 'SubFamily', label: 'Sub-Families', icon: '' } | |
- | |
label: 'Important stuff' | |
icon: 'exclamation' | |
children: | |
- | |
label: 'Silly kittens' | |
url: 'https://www.youtube.com/results?search_query=silly+kittens' | |
target: '_blank' | |
- | |
label: 'Wet cats' | |
url: 'http://www.boredpanda.com/funny-wet-cats/' | |
target: '_blank' | |
list: | |
title: 'List of %%entity_label%%' | |
actions: ['show', 'export'] | |
entities: | |
Genus: | |
class: AppBundle\Entity\Genus | |
controller: AppBundle\Controller\EasyAdmin\GenusController | |
label: Genuses | |
help: Genuses are not covered under warranty! | |
list: | |
help: Do not feed! | |
actions: | |
- { name: 'edit', icon: 'pencil', label: 'Edit' } | |
- { name: 'show', icon: 'info-circle', label: '' } | |
fields: | |
- 'id' | |
- 'name' | |
- 'isPublished' | |
- { property: 'firstDiscoveredAt', format: 'M Y', label: 'Discovered' } | |
- 'funFact' | |
- { property: 'speciesCount', format: '%b' } | |
sort: 'name' | |
search: | |
help: null | |
fields: ['id', 'name'] | |
show: | |
actions: | |
- | |
name: 'genus_feed' | |
type: 'route' | |
label: 'Feed genus' | |
css_class: 'btn btn-info' | |
icon: 'cutlery' | |
- { name: 'changePublishedStatus', css_class: 'btn' } | |
# templates: | |
# field_id: 'admin/fields/_id.html.twig' | |
form: | |
fields: | |
- { type: 'group', css_class: 'col-sm-6', label: 'Basic information' } | |
- name | |
- speciesCount | |
- { property: 'firstDiscoveredAt', type_options: { widget: 'single_text' }} | |
- { property: 'subFamily', type: 'easyadmin_autocomplete' } | |
- { type: 'section', label: 'Optional' } | |
- { property: 'funFact', type: 'textarea', css_class: 'js-markdown-input' } | |
- isPublished | |
- { type: 'group', css_class: 'col-sm-6', label: 'Studied by ...' } | |
- | |
property: 'genusScientists' | |
type: 'text' | |
type_options: | |
mapped: false | |
attr: { class: 'js-genus-scientists-field' } | |
- | |
type: 'group' | |
css_class: 'col-sm-6 new-row' | |
label: 'Identification' | |
icon: 'id-card-o' | |
help: 'For administrators' | |
- | |
property: id | |
type_options: {disabled: true} | |
- | |
property: 'slug' | |
help: 'unique auto-generated value' | |
type_options: { disabled: true } | |
new: | |
fields: | |
- '-id' | |
- '-slug' | |
GenusHorde: | |
class: AppBundle\Entity\Genus | |
label: HORDE of Genuses |
Perfect!
Now, we just need to make sure that Symfony loads this file. At the top of config.yml
, just load another resource: admin.yml
:
imports: | |
... lines 2 - 4 | |
- { resource: admin.yml } | |
... lines 6 - 81 |
And that is it! When we refresh, everything still works!
Phew, we're done! EasyAdminBundle is great. But of course, depending on how custom you need things, you might end up overriding a lot of different parts. Many things can be done via configuration. But by using the tools that we've talked about, you can really override everything. Ultimately, customizing things will still be a lot faster than building all of this on your own.
All right guys, thank you so much for joining me! And a huge thanks to my co-author Andrew for doing all the actual hard work.
Ok, seeya next time!
Hey Ahmedbhs,
I suppose so, try css_class
for this. Check more information in the docs: https://symfony.com/bundles/EasyAdminBundle/2.x/book/menu-configuration.html#css-classes - though this version of EasyAdmin is legacy and not maintained anymore, I'd recommend you upgrade to the v3/v4 version instead, and we have a screencast about the updated EasyAdmin here: https://symfonycasts.com/screencast/easyadminbundle
Cheers!
Hey Tanariel
Thanks for your kind words, we are very happy to hear you like our tutorials :)
Cheers!
Thanks for a great tutorial.
One question about menu: if we have admin and moderator (different roles), could we hide some menu items for Moderator (only admin can see all menu items)?
Hey Axa,
Really good question. Please, take a look at Javier's comment here: https://github.com/javiereg... - I think it will answer your question. In shorts, you can use CSS tricks to implement it, or by overriding EasyAdminBundle templates, where you can use is_granted() Twig function and do whatever you want. But for more complex things better use SonataAdminBundle instead.
Cheers!
Just for future reference - in v2 you can use permission option:
https://symfony.com/doc/mas...
Hello Victor,
I am reading last Javier's comment :(
Yes, Javier also said: "that this bundle supports only 80% of features. if you need 100% better to use Sonata." .
Issue is : you cannot know in advance what will need. For example, after a year , client can demand something and that can be nightmare.
Looks that better to avoid this bundle :(
Back to ugly sonata.
Hey @axa
Well, that's true, you never know what will be the next thing that your client will ask, but if your system doesn't heavily deppends on multiple Roles/Permissions, you could give it a try
Have a nice day
Hello Ryan,
Thank you very much for this nice tutorial.
I have a small (or big) question:
When I implement such an Application with EasyAdmin Bundle, I would like to make Mobile Apps with Ionic (2 or 3) to connect to the implemented EasyAdmin Bundle Web-App. I can imagine that I need to implement an API for data flow for Mobile Apps.
Which Knp University tutorial(s) would you suggest to implement an API to use with third party Frameworks to build Mobile Apps based on EasyAdmin Bundle?
Thanks in advance.
Hey Entwickler ,
Yes, sounds correct. Or maybe you even can implement Mobile App which is a wrapper for browser, so you will load this entire EasyAdmin dashboard, then you don't need to implement API endpoints at all. EasyAdmin responsive design is pretty good for mobile devices. Actually, it depends on your needs.
But what about API, we have an entire track about RESTful APIs, I think it all can be useful for you more or less, it's a complex question, you know. Just depends on how strong your knowledges about it. Check it out here: https://knpuniversity.com/t...
There're also a Symfony-related RESTful APIs screencasts where you can find some useful bundles as well: https://knpuniversity.com/t...
Cheers!
// composer.json
{
"require": {
"php": ">=5.5.9",
"symfony/symfony": "3.3.*", // v3.3.18
"doctrine/orm": "^2.5", // v2.7.2
"doctrine/doctrine-bundle": "^1.6", // 1.10.3
"doctrine/doctrine-cache-bundle": "^1.2", // 1.3.5
"symfony/swiftmailer-bundle": "^2.3", // v2.6.7
"symfony/monolog-bundle": "^2.8", // v2.12.1
"symfony/polyfill-apcu": "^1.0", // v1.17.0
"sensio/distribution-bundle": "^5.0", // v5.0.25
"sensio/framework-extra-bundle": "^3.0.2", // v3.0.29
"incenteev/composer-parameter-handler": "^2.0", // v2.1.4
"knplabs/knp-markdown-bundle": "^1.4", // 1.7.1
"doctrine/doctrine-migrations-bundle": "^1.1", // v1.3.2
"stof/doctrine-extensions-bundle": "^1.2", // v1.3.0
"composer/package-versions-deprecated": "^1.11", // 1.11.99
"javiereguiluz/easyadmin-bundle": "^1.16" // v1.17.21
},
"require-dev": {
"sensio/generator-bundle": "^3.0", // v3.1.7
"symfony/phpunit-bridge": "^3.0", // v3.4.40
"nelmio/alice": "^2.1", // v2.3.5
"doctrine/doctrine-fixtures-bundle": "^2.3" // v2.4.1
}
}
Hello there, is there a way to add a css attribute to a menu item ?