Robin S Posted September 7, 2021 Share Posted September 7, 2021 This module lets you add some custom menu items to the main admin menu, and you can set the dropdown links dynamically in a hook if needed. Sidenote: the module config uses some repeatable/sortable rows for the child link settings, similar to the ProFields Table interface. The data gets saved as JSON in a hidden textarea field. Might be interesting to other module developers? Custom Admin Menus Adds up to three custom menu items with optional dropdowns to the main admin menu. The menu items can link to admin pages, front-end pages, or pages on external websites. The links can be set to open in a new browser tab, and child links in the dropdown can be given an icon. Requires ProcessWire v3.0.178 or newer and AdminThemeUikit. Screenshots Example of menu items Module config for the menus Link list shown when parent menu item is not given a URL Advanced Setting child menu items dynamically If needed you can set the child menu items dynamically using a hook. Example: $wire->addHookAfter('CustomAdminMenus::getMenuChildren', function(HookEvent $event) { // The menu number is the first argument $menu_number = $event->arguments(0); if($menu_number === 1) { $colours = $event->wire()->pages->findRaw('template=colour', ['title', 'url', 'page_icon']); $children = []; foreach($colours as $colour) { // Each child item should be an array with the following keys $children[] = [ 'icon' => $colour['page_icon'], 'label' => $colour['title'], 'url' => $colour['url'], 'newtab' => false, ]; } $event->return = $children; } }); Create multiple levels of flyout menus It's also possible to create multiple levels of flyout submenus using a hook. For each level a submenu can be defined in a "children" item. Example: $wire->addHookAfter('CustomAdminMenus::getMenuChildren', function(HookEvent $event) { // The menu number is the first argument $menu_number = $event->arguments(0); if($menu_number === 1) { $children = [ [ 'icon' => 'adjust', 'label' => 'One', 'url' => '/one/', 'newtab' => false, ], [ 'icon' => 'anchor', 'label' => 'Two', 'url' => '/two/', 'newtab' => false, 'children' => [ [ 'icon' => 'child', 'label' => 'Red', 'url' => '/red/', 'newtab' => false, ], [ 'icon' => 'bullhorn', 'label' => 'Green', 'url' => '/green/', 'newtab' => false, 'children' => [ [ 'icon' => 'wifi', 'label' => 'Small', 'url' => '/small/', 'newtab' => true, ], [ 'icon' => 'codepen', 'label' => 'Medium', 'url' => '/medium/', 'newtab' => false, ], [ 'icon' => 'cogs', 'label' => 'Large', 'url' => '/large/', 'newtab' => false, ], ] ], [ 'icon' => 'futbol-o', 'label' => 'Blue', 'url' => '/blue/', 'newtab' => true, ], ] ], [ 'icon' => 'hand-o-left', 'label' => 'Three', 'url' => '/three/', 'newtab' => false, ], ]; $event->return = $children; } }); Showing/hiding menus according to user role You can determine which menu items can be seen by a role by checking the user's role in the hook. For example, if a user has or lacks a role you could include different child menu items in the hook return value. Or if you want to conditionally hide a custom menu altogether you can set the return value to false. Example: $wire->addHookAfter('CustomAdminMenus::getMenuChildren', function(HookEvent $event) { // The menu number is the first argument $menu_number = $event->arguments(0); $user = $event->wire()->user; // For custom menu number 1... if($menu_number === 1) { // ...if user does not have some particular role... if(!$user->hasRole('foo')) { // ...do not show the menu $event->return = false; } } }); https://github.com/Toutouwai/CustomAdminMenushttps://processwire.com/modules/custom-admin-menus/ 14 4 Link to comment Share on other sites More sharing options...
netcarver Posted September 7, 2021 Share Posted September 7, 2021 Nice, thank you Robin. Seems to work well. 2 Link to comment Share on other sites More sharing options...
DrQuincy Posted September 7, 2021 Share Posted September 7, 2021 Ooh, this looks great! Thanks Robin. I'm still on .165 at the moment so will have to upgrade. ? Out of interest is there a technical reason why it's limited to three menus? Also, do you think there will ever be support for the navJSON type links? I.e. Main > drop down item > third-level item. Link to comment Share on other sites More sharing options...
DV-JF Posted September 7, 2021 Share Posted September 7, 2021 This looks great! I'll give it definitely a try in an upcoming project. Thank you! 1 Link to comment Share on other sites More sharing options...
DrQuincy Posted September 8, 2021 Share Posted September 8, 2021 Just installed this and it is really good! Thanks @Robin S, I am abandoning my Process class from the other thread. ? One suggestion. Can you dynamically set permissions? If so, it might be nice to restrict the appearance of the menus to a permission. It doesn't really matter for my use case but thought it might be worth adding if it's easy enough. 2 Link to comment Share on other sites More sharing options...
DrQuincy Posted September 8, 2021 Share Posted September 8, 2021 P.S. One “gotcha” that caught me out was the items did not appear in the mobile nav until I logged in and out again. ? 1 Link to comment Share on other sites More sharing options...
Robin S Posted September 9, 2021 Author Share Posted September 9, 2021 On 9/7/2021 at 9:49 PM, DrQuincy said: Out of interest is there a technical reason why it's limited to three menus? No, but there needed to be some limit set and I can't personally imagine needing more than three custom menus. If anyone using the module finds they need more then I'd consider increasing the limit. On 9/7/2021 at 9:49 PM, DrQuincy said: Also, do you think there will ever be support for the navJSON type links? I.e. Main > drop down item > third-level item. Not actual navJSON ajax-loaded menus because those require a Process module. But in the latest version I've allowed for the possibility to define multiple levels of submenus using a hook. See the updated readme. 16 hours ago, DrQuincy said: Can you dynamically set permissions? If so, it might be nice to restrict the appearance of the menus to a permission. In the hook you can check if the user has or lacks a role and return different child item data accordingly. And in the latest version if you return false from the hook then the menu is not shown at all. See the updated readme. 20 hours ago, DrQuincy said: P.S. One “gotcha” that caught me out was the items did not appear in the mobile nav until I logged in and out again. Should be fixed in the latest version. 2 Link to comment Share on other sites More sharing options...
DrQuincy Posted September 9, 2021 Share Posted September 9, 2021 Great work, thanks! Link to comment Share on other sites More sharing options...
prestoav Posted January 12, 2022 Share Posted January 12, 2022 Nice work @Robin S! One Issue I've come across that I'd be really keen on a solution for is to support $config->urls in the URL field. For example, I have a 'Site Settings' page in all my sites where the client can edit the global company details like phone, email, address etc. I'd like to add a link in the top admin bar to this page so it's easy to find (some sites have a lot pf pages in the tree)! I can do it once the site is launched with an absolute URL but I'm struggling with a relative URL that I can add to my Framework site. This is because the correct relative link depends on the page the 'Settings Page' was accessed from: This would work from another edit page: ../../admin/page/edit/?id=1016 However, it would need to be this from the page tree: ../admin/page/edit/?id=1016 One fix would be to support $config->urls in {} e.g. {$urls->admin}page/edit/?id=1016 As per these docs: https://processwire.com/api/ref/paths/ Hope this helps and thanks for the module! Link to comment Share on other sites More sharing options...
Robin S Posted January 12, 2022 Author Share Posted January 12, 2022 6 hours ago, prestoav said: I can do it once the site is launched with an absolute URL but I'm struggling with a relative URL that I can add to my Framework site. Personally I would use the admin page name in the URL but not the scheme/domain. So a relative URL but relative to the site root. In your example it looks like the admin page name is "admin" so the URL would be: /admin/page/edit/?id=1016 If you want to get the admin URL dynamically from $config for some reason then you just need to use the CustomAdminMenus::getMenuChildren hook method instead of entering your URLs as plain text in the admin. Link to comment Share on other sites More sharing options...
prestoav Posted January 13, 2022 Share Posted January 13, 2022 @Robin S Thanks for getting back to me. Your initial suggestion does work on live sites in a domain root but, sadly, not in a development environment where different sites are housed on sub folders. For example, if the site is at www.mydomain.com then the resulting link is www.mydomain.com/admin/page/edit/?id=1016 and this works from all admin pages. However, if the site is at localhost:8888/dev_site_1/ then the link created is localhost:8888/admin/page/edit/?id=1016 (i.e. the 'dev_site_1' is missing) and the resulting link does't work. I'll investigate the hook method of course but many devs might find this useful to work in their framework sites. Thanks again for the good work. Link to comment Share on other sites More sharing options...
Robin S Posted January 13, 2022 Author Share Posted January 13, 2022 10 minutes ago, prestoav said: Your initial suggestion does work on live sites in a domain root but, sadly, not in a development environment where different sites are housed on sub folders. As a general thing to make your life as a developer easier I suggest looking into virtual hosts for local development. That way your local site will be at something like mysite.dev and you can seamlessly move from local to remote without any URL issues. You can use virtual hosts with any LAMP/WAMP/etc, and in particular Laragon will give you an automatic virtual host for every site without needing any configuration. 1 Link to comment Share on other sites More sharing options...
prestoav Posted January 13, 2022 Share Posted January 13, 2022 3 minutes ago, Robin S said: As a general thing to make your life as a developer easier I suggest looking into virtual hosts for local development. That way your local site will be at something like mysite.dev and you can seamlessly move from local to remote without any URL issues. You can use virtual hosts with any LAMP/WAMP/etc, and in particular Laragon will give you an automatic virtual host for every site without needing any configuration. I'll certainly look into that. Link to comment Share on other sites More sharing options...
Robin S Posted January 15, 2022 Author Share Posted January 15, 2022 @prestoav In the newly released v0.1.3 there is a config option to prepend $config->urls->root to URLs that start with a forward slash. 3 Link to comment Share on other sites More sharing options...
prestoav Posted January 16, 2022 Share Posted January 16, 2022 16 hours ago, Robin S said: @prestoav In the newly released v0.1.3 there is a config option to prepend $config->urls->root to URLs that start with a forward slash. Superb, thanks I'll try that out! Link to comment Share on other sites More sharing options...
prestoav Posted January 16, 2022 Share Posted January 16, 2022 4 minutes ago, prestoav said: Superb, thanks I'll try that out! Works a treat, thank you ? 1 Link to comment Share on other sites More sharing options...
taotoo Posted April 14, 2022 Share Posted April 14, 2022 This module looks great! Should it work with the Reno theme, or are they incompatible? Link to comment Share on other sites More sharing options...
Robin S Posted April 19, 2022 Author Share Posted April 19, 2022 On 4/14/2022 at 8:06 PM, taotoo said: This module looks great! Should it work with the Reno theme, or are they incompatible? Thanks. It looks like the legacy Default and Reno themes don't call AdminThemeFramework::getPrimaryNavArray() so the hooks added by this module don't have any effect. Therefore AdminThemeUikit is a requirement - I've updated the module install requirements and readme to reflect this. 1 1 Link to comment Share on other sites More sharing options...
bernhard Posted December 6, 2023 Share Posted December 6, 2023 Hey @Robin S thx for the helpful module. Used it today for the first time and unfortunately the "open in new tab" checkbox has no effect. I didn't find any occurences of "target" or "_blank" in the module's code, so I think it's missing ? 1 Link to comment Share on other sites More sharing options...
Robin S Posted December 6, 2023 Author Share Posted December 6, 2023 @bernhard, thanks for the heads-up. The new tab option was working for child items but not for the top level menu items. Fixed in v0.1.5 and in this version I've also added an option to define an icon for the top-level items - these are only visible in the sidebar menu so probably not seen all that often but still good to have control over. 2 1 Link to comment Share on other sites More sharing options...
Ivan Gretsky Posted December 7 Share Posted December 7 Good day, @Robin S! Thanks for the module! I've used it with a great pleasure on my single-language sites. But now I need to set things up for the ML site. How would you suggest to do that? I think I would need both language-dependant labels AND urls. Is it better to handle this with hooks, or there could be a way to do it from the UI? Link to comment Share on other sites More sharing options...
Robin S Posted December 10 Author Share Posted December 10 On 12/7/2024 at 11:55 PM, Ivan Gretsky said: I think I would need both language-dependant labels AND urls. Is it better to handle this with hooks, or there could be a way to do it from the UI? Unfortunately I don't think this module can be easily adapted to support multi-language menus, because to get the repeater-like interface for variable numbers of child items working I needed to come up with a custom way to store that data in JSON format rather than use the core way of storing individual inputfield data. So multi-language support would mean creating a new way of storing multi-language data that's separate from PW's way and I don't want to try to reinvent that wheel. But you could hook some of the same methods that the module does to add your own custom menus to the admin. Example: $wire->addHookBefore('ProcessController::execute', function(HookEvent $event) { // Prevent admin menus from being cached $this->wire()->session->removeFor('AdminThemeUikit', 'prnav'); $this->wire()->session->removeFor('AdminThemeUikit', 'sidenav'); }); $wire->addHookAfter('AdminThemeFramework::getPrimaryNavArray', function(HookEvent $event) { $items = $event->return; $user = $event->wire()->user; $data = [ [ 'label' => [ 'default' => 'Shirts', 'french' => 'Chemises', ], 'url' => [ 'default' => '/shirts/', 'french' => '/chemises/', ], 'children' => [ [ 'label' => [ 'default' => 'Small', 'french' => 'Petit', ], 'url' => [ 'default' => '/shirts/small/', 'french' => '/chemises/petit/', ], 'icon' => 'smile-o', ], [ 'label' => [ 'default' => 'Medium', 'french' => 'Moyen', ], 'url' => [ 'default' => '/shirts/medium/', 'french' => '/chemises/moyen/', ], 'icon' => 'thumbs-o-up', ], ], ], ]; foreach($data as $item) { $menu = [ 'id' => 0, 'parent_id' => 0, 'name' => '', 'title' => $item['label'][$user->language->name], 'url' => $item['url'][$user->language->name], 'icon' => '', 'children' => [], 'navJSON' => '', ]; foreach($item['children'] as $child) { $menu['children'][] = [ 'id' => 0, 'parent_id' => 0, 'name' => '', 'title' => $child['label'][$user->language->name], 'url' => $child['url'][$user->language->name], 'icon' => $child['icon'], 'children' => [], 'navJSON' => '', ]; } $items[] = $menu; } $event->return = $items; }); 1 Link to comment Share on other sites More sharing options...
Recommended Posts
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new accountSign in
Already have an account? Sign in here.
Sign In Now