Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine
This tutorial has a new version, check it out!

Customizing the Menu

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.

Start your All-Access Pass
Buy just this tutorial for $8.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

What 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.

Adding Menu Separators & Sub-Menus

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.

Re-organizing the Config

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

  1. Wow! It's huge!

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!

Leave a comment!

12
Login or Register to join the conversation

Hello there, is there a way to add a css attribute to a menu item ?

Reply

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!

Reply
Default user avatar
Default user avatar Tanariel | posted 5 years ago

Thanks very much for such a great tutorial :)

Reply

Hey Tanariel

Thanks for your kind words, we are very happy to hear you like our tutorials :)

Cheers!

Reply
Default user avatar

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)?

Reply

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!

Reply

Just for future reference - in v2 you can use permission option:
https://symfony.com/doc/mas...

1 Reply
Default user avatar

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.

Reply

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

Reply
Default user avatar
Default user avatar Entwickler | posted 5 years ago

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.

Reply

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!

Reply
Default user avatar
Default user avatar Entwickler | Victor | posted 5 years ago | edited

Thank you very much @victor

I started Symfony RESTful API course.

Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

This tutorial is built on an older version of Symfony & EasyAdminBundle. Many of the concepts are the same, but you can expect major differences in newer versions.

What PHP libraries does this tutorial use?

// 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
    }
}
userVoice