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.
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.
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.
Custom Links!
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!
External Links
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
- 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!
Hello there, is there a way to add a css attribute to a menu item ?