#Menu Builder This Module allows you to easily create custom menus/navigation lists in the ProcessWire Admin Panel using drag and drop. In the backend, it uses the nestedSortable jQueryUI plugin by Manuele J Sarfatti.
##Features
- Visual menu builder
- Ability to create menus that do not mirror your ProcessWire Page Tree hierarchy/structure
- Menus can contain both ProcessWire pages and custom links
- Create menu hierarchies and nesting via drag and drop
- Lock down menus for editing
- Easily apply CSS IDs and Classes to each and every menu item if you wish
- Optionally set custom links to open in a new tab
- Readily view the structure and settings for each menu and menu item
- For each menu, multiple configurable ways to add menu items from ProcessWire pages - PageAutocomplete OR AsmSelect[default] AND ProcessWire Selector
- Using a Selector, you can do for example, template=basic-page, limit=20, sort=title.
- Batch edit menus
- Menus stored as pages (note: just the menu, not the items!)
- Menu items stored as JSON in a field in the menu pages (empty values not stored)
- For page fields, you can specify a selector to return only those specified pages for selection in the page field (i.e. Asm and Autocomplete)
- For page fields, you can also add CSS classes and IDs as you add the items (similar to custom menu items)
- Menu settings for nestedSortable - e.g. maxLevels (limit nesting levels)
- Advanced features (e.g. add pages via selector, menu settings) permissible to superadmins only
- Delete single or all menu items without deleting the menu itself
- Easily render menus and breadcrumbs in the frontend using MarkupMenuBuilder
- Fully multilingual
##How to Install
The module has two components:
- ProcessMenuBuilder: for creating menus in the ProcessWire Admin
- MarkupMenuBuilder: for displaying menus in the frontend (unstyled, unordered list by default)
- Install the module from within the ProcessWire admin or download the module and copy the file contents to /site/modules/MenuBuilder/
- In Admin, click Modules > check for new modules
- Click install ProcessMenuBuilder. The module will automatically install MarkupMenuBuilder
- Go to Setup > Menu Builder and start creating your menus
##Note
- The module installs three fields: 'menu_items', 'menu_pages' and 'menu_settings' and one template 'menus'. If any similarly named fields/template are already present on your site, the module will not install but throw an error instead. You would need to rename your fields/template first.
- To allow access to the Menu Builder admin, a non-superuser must have the permission menu-builder. The permission is created on install.
- Some Menu Builder admin options are only available to Superusers by default. Other users would require specific permissions as described below.
##API
MarkupMenuBuilder has two methods available to users.
###render()
This method renders a menu/navigation list of a specified menu. The method accepts two arguments/parameters.
render($menu, $options);
The first argument is not optional and can be a Page object, a title, name or id of a menu or an array of menu items returned from a menu's menu_items field. Note that for multilingual environments, you cannot pass the method a title or a name; only the other three choices will work. The second argument is an optional array and will fall back to defaults if no user configurations are passed to the method.
The available render() options are:
$defaultOptions = array(
'wrapper_list_type' => 'ul',//ul, ol, nav, div, etc.
'list_type' => 'li',//li, a, span, etc.
'menu_css_id' => '',//a CSS ID for the menu
'menu_css_class' => '',//a CSS Class for the menu
'submenu_css_class' => '',//CSS Class for sub-menus
'has_children_class' => '',//CSS Class for any menu item that has children
'first_class'=>'',//CSS Class for the first item in
'last_class' => '',
'current_class' => '',
'default_title' => 0,//0=show saved titles;1=show actual/current titles
'include_children' => 4,//show 'natural' MB non-native descendant items as part of navigation
'm_max_level' => 1,//how deep to fetch 'include_children'
'current_class_level' => 1,//how high up the ancestral tree to apply 'current_class'
);
###renderBreadcrumbs()
This method renders a breadcrumb navigation of a specified menu. The method also accepts two arguments/parameters.
render($menu, $options);
Similar to render(), the first argument is not optional and can be a Page object, a title, name or id of a menu or an array of menu items returned from a menu's menu_items field. This means that you only have to retrieve a menu once and pass that to both render() and renderBreadcrumbs(). Note that for multilingual environments, you cannot pass the method a title or a name; only the other three choices will work. The second argument is an optional array and will fall back to defaults if no user configurations are passed to the method. The options are very similar to those of render(). Hence, as applicable, you can create one array of options and pass it to both render() and renderBreadcrumbs(). The methods will pick up what's of relevance to them.
The available renderBreadcrumbs() options are:
$defaultOptions = array(
'wrapper_list_type' => 'ul',//ul, ol, nav, div, etc.
'list_type' => 'li',//li, a, span, etc.
'menu_css_id' => '',
'menu_css_class' => '',
'current_css_id' => '',
'divider' => '»',// e.g. Home >> About Us >> Leadership
//prepend home page at the as topmost item even if it isn't part of the breadcrumb
'prepend_home' => 0,//=> 0=no;1=yes
'default_title' => 0,//0=show saved titles;1=show actual/current titles
'include_children' => 4,//show 'natural' MB non-native descendant items as part of navigation
'b_max_level' => 1,//how deep to fetch 'include_children'
);
###Options
- Unless indicated otherwise, all the following options apply to both menus (render()) and breadcrumbs (renderBreadcrumbs()).
- The term navigation is used in the context of both menus and breadcrumbs.
- The term 'Class(es)' indicates that multiple CSS Classes can be applied, separated by space.
- wrapper_list_type: Outer HTML/Markup wrapper for navigation items. The default is <ul>. You can also use <div>, <nav>, <ol>, etc, to suit your needs.
- list_type: The HTML/Markup wrapper for the navigation items themselves. The default is <li>. You can also use <span>, etc or even nothing. For menus, in case you specify you want to use nothing, i.e. 'list_type' => '', nagivation items' CSS Class(es) and IDs will be applied to the **<a>, i.e. the link, rather than the default which would have been the list type.
- menu_css_id: A CSS ID applied to the top-most 'wrapper_list_type', i.e. the outermost HTML/Markup wrapper for navigation items. Nothing is applied by default unless you specify you want to use the option.
- menu_css_class: A CSS Class applied to the top-most 'wrapper_list_type', i.e. the outermost HTML/Markup wrapper for navigation items. Nothing is applied by default unless you specify you want to use the option.
- submenu_css_class: For menus only, a CSS Class(es) applied to any nested 'wrapper_list_type', i.e. inner HTML/Markup wrapper for sub-menus. Nothing is applied by default unless you specify you want to use the option.
- has_children_class: For menus only, a CSS Class(es) applied to the 'list_type' of any menu item that has children items, i.e. a parent menu item or in other words, a menu item containing one or more nested menu items. Nothing is applied by default unless you specify you want to use the option.
- first_class: For menus only and at all menu levels (i.e. nesting), a CSS Class(es) applied to the 'list_type' of any menu item that is at the top-most position at each menu level. Nothing is applied by default unless you specify you want to use the option.
- last_class: For menus only and at all menu levels (i.e. nesting), a CSS Class(es) applied to the 'list_type' of any menu item that is at the bottom-most position at each menu level. Nothing is applied by default unless you specify you want to use the option. At any level, if there is only a single menu item, it means that it is both the first and last menu item. If you specified both 'last_class' and 'first_class' in your menu options, that menu item will have both of these applied to it.
- current_class: For menus only, a CSS Class(es) applied to the 'list_type' of the current menu item, i.e. the menu item that corresponds to the page that is currently being viewed in your browser. Nothing is applied by default unless you specify you want to use the option.
- current_css_id: For breadcrumbs only, a CSS ID applied to the 'list_type' of the current breadcrumb item, i.e. the breadcrumb item that corresponds to the page that is currently being viewed in your browser. Nothing is applied by default unless you specify you want to use the option. Note that by default, current breadcrumb items are not wrapped around <a> (anchor/link) tags as it makes no sense to do so.
- divider: For breadcrumbs only, a HTML Character Entity applied after the anchor tag <a> of breadcrumb items to indicate ancestry, i.e. items to the left of the divider are ancestral parents, grandparents, great grandparents, etc. to the breadcrumb item on the right of the divider. The default character used is ». Note that if there is only one breadcrumb item in the navigation, the divider is not applied. Also, it is not applied after the last breadcrumb item which is always the current breadcrumb item. You are not limited to using HTML Character Entities but can use whatever character suits your needs or nothing at all by specifying 'divider' => '' in your options.
- prepend_home: For breadcrumbs only, specifying this option prepends your website's 'Homepage' title and URL as a breadcrumb as the topmost item in the navigation even if it isn't ancestrally part of the breadcrumb. This option is not applied by default. To use it specify 'prepend_home' => 1 in your options array.
- default_title: Controls whether to display Menu Builder saved menu item titles/labels versus displaying pages' actual/current titles. This is useful in scenarios where, for example, you need dynamic titles such as in a multilingual environment where you would want navigation labels to change depending on the current language. The default option is to display saved titles. To instead display actual titles, set option to 'default_title' => 1 in your options array.
- include_children: Controls whether to display viewable descendant ProcessWire pages (children, grandchildren, etc.) of Menu Builder ProcessWire pages' navigation items at runtime as opposed to editing and saving them in Menu Builder admin. This is useful in a number of cases. For instance, you may wish to limit the number of menu items appearing in a particular menu in the the Menu Builder admin, e.g. in cases where these are many. This option can be applied globally (i.e. at a menu-level/$options level) and locally (at a menu-item-level). Item-level settings mean that only children of the specified item will be included. The 'include_children' option is disabled by default. Please see below for further details on how to use the feature.
- m_max_level: This is a menu-only option related to the include children feature. It limits the depth from within which viewable descendant pages can be retrieved for display in a menu. The default is 1. This means that only fetch immediate children. A value of 2 means fetch both children and grandchildren, etc. The option can be applied globally and locally as explained previously.
- b_max_level: This is breadcrumb-only option related to the include children feature. It limits the depth from within which viewable descendant pages can be retrieved for display in a breadcrumb. The default is 1. This means that only fetch immediate children. A value of 2 means fetch both children and grandchildren, etc. The option can only be applied globally.
- current_class_level: Using this option, you can specify how high up the menu tree you want to apply the 'current_class' to the current item's ancestors. The default is 1, meaning (if specified) apply the 'current_class' to only the current item. A setting of 3 implies apply it to the current item, its parent and grandparent, etc. In short, the option can be used to show some or all 'active/current' menu items at various levels in your menu. This option only applies to menus and not breadcrumbs.
##How to Use
- Access Menu Builder in your ProcessWire admin and create a menu(s).
- Edit the menu and add items to it, dragging and dropping them in different positions as you wish.
- Once you've created a menu, you can view it in the frontend by loading it using MarkupMenuBuilder in a template file as follows.
####Rendering a Menu
//load the module
$menu = $modules->get('MarkupMenuBuilder');//Load the module. $menu is an example
/**you can render by menu Page object, name, title, id or properly formatted array of menu items**/
//render by name, title or id
echo $menu->render('Title of Your Menu');//render the menu by title
echo $menu->render('name-of-your-menu');//render the menu by name
echo $menu->render('1234');//render by ID
//render by passing a Page object
$m = $pages->get(1234);
echo $menu->render($m);//render by Page object
//render by passing an array
//get the Menu Builder field menu_items for this menu. That is where your menu items JSON string is stored
$json = $pages->get(1234)->menu_items;
//convert the JSON string to an array. Here we assume the JSON string is not empty
$array = json_decode($json, true);
echo $menu->render($array);//render by array
You can render additional menus as well, e.g.
echo $menu->render('sidenav');
You can additionally pass CSS class/id options to the method. See above for available options.
$options = array(
'has_children_class' => 'has_children',
'current_class' => 'current',
'menu_css_id' => 'main',
'menu_css_class' => 'nav',
);
echo $menu->render('sidenav', $options);
####Rendering Breadcrumbs
Rendering breadcrumbs is quite similar to the above, the only difference being the method you use and some of the options that can be used to configure the output.
//load the module
$menu = $modules->get('MarkupMenuBuilder');//Load the module. $menu is an example
/**you can render by menu Page object, name, title, id or properly formatted array of menu items**/
//render by name, title or id
echo $menu->renderBreadcrumbs('Title of Your Menu');//render the menu by title
echo $menu->renderBreadcrumbs('name-of-your-menu');//render the menu by name
echo $menu->renderBreadcrumbs('1234');//render by ID
//render by passing a Page object
$m = $pages->get(1234);
echo $menu->renderBreadcrumbs($m);//render by Page object
//render by passing an array
//get the Menu Builder field menu_items for this menu. That is where your menu items JSON string is stored
$json = $pages->get(1234)->menu_items;
//convert the JSON string to an array. Here we assume the JSON string is not empty
$array = json_decode($json, true);
echo $menu->renderBreadcrumbs($array);//render by array
Additionally, you can pass some options to the method. See above for available options.
$options = array(
'wrapper_list_type' => 'div',
'list_type' => 'span',
//'list_type' => '',//if empty, no tag will be applied + no CSS ID
'menu_css_id' => 'crumbs',
'menu_css_class' => 'trail',
'current_css_id' => 'current',
'divider' => '*',
'prepend_home' => 1
);
echo $menu->renderBreadcrumbs(1234, $options);
####Using the Include Children Feature
This is a flexible and powerful feature that you can leverage to produce all sorts of dynamic navigation for your ProcessWire website. With lots of power comes responsibility. The features should only be enabled (see relevant permission below) for users who know what they are doing. For example, a user could unknowingly use the feature to include descendant pages of a menu item that has hundreds of descendants, leading to undesirable effects.
#####Menu-Level Values
At the global/menu/API-level, the following values can be passed with 'include_children'. This is useful if you want to override some item-level settings. The priority order of the settings and in comparison to item-level settings are explained further below.
- 0: Overrides all settings, global and local and suppresses output of 'include_children'.
- 1: Include children of all navigation items but only in the menu unless specified otherwise in item-level setting.
- 2: Include children of all navigation items but only in the breadcrumbs unless specified otherwise in item-level setting.
- 3: Include children of all navigation items in both the menu and breadcrumbs unless specified otherwise in item-level setting. This assumes you are passing the same options to both render() and renderBreadcrumbs().
- 4: Do not include children of any navigation item unless specified otherwise in item-level setting. This is the default setting (automatically applied) and does not need to be specified in your $options.
#####Item-Level Values
At the local/admin/item-level, the following values can be saved with each navigation item either when creating or editing the menu item in the Menu Builder admin. This is useful when you want fine-grained control over 'include_children' output. The priority order of the settings and in comparison to menu-level settings are explained later below.
- No: This is the default value; It means do not include children of this menu item unless overridden by a menu-level setting.
- Menu: Include children of this navigation item but only in the menu unless overridden by a 0 menu-level setting.
- Breadcrumbs: Include children of this navigation item but only in the breadcrumbs unless overridden by a 0 menu-level setting.
- Both: Include children of this navigation item in both the menu and breadcrumbs overridden only by a 0 menu-level setting.
- Never: Never include children of this navigation item.
The combined (menu- and item-level) order of descending priority (i.e. a setting cannot override the setting above it) for these 'include_children' settings is as follows:
- 0 / Never
- Menu / Breadcrumbs / Both
- 1 / 2 / 3
- 4 / No
As a sanity check, in the Menu Builder admin, a navigation item made up of your ProcessWire 'homepage' cannot have its children included/excluded since it is the parent of all pages. In addition, you will only see 'include_children' inputs in the Menu Builder admin when both the feature is enabled and you have the permission 'menu-builder-include-children' (or are a superuser; more below under Permissions). Finally, note that if a user without this permission edits a menu that was previously edited by a user with the permission, any 'include_children' item-level settings will be lost.
####Other Usage Notes
Note that you are not limited to using MarkupMenuBuilder. You can also use your own PHP (or even JavaScript) recursive function to display your menu by decoding the saved JSON string containing menu items and passing the resulting array to your function for traversal.
The CSS is up to you, of course.
##Permissions
You can use the following permissions to control visibility and access to various advanced settings of Menu Builder by non-superusers. In ProcessWire, by default, Superusers inherit all permissions. Note that you will have to create and apply the permissions yourself using the normal ProcessWire way, i.e.
- Create a role, e.g. menu-editor.
- Create permissions and add assign them (when editing your role) to the role you created.
- Create a user and assign them the role with the Menu Builder permissions.
There are 8 permissions at your disposal for fine-grained access control of your Menu Builder admin.
-
menu-builder-lock This permission allows your non-superusers to lock and unlock menus using the 'Actions Panel' in Menu Builder's admin main page. Note two things: (i) This permission takes a whitelist approach. If this permission does not exist on your system (i.e. has not yet been created or is present but unpublished), by default, all users will be able to lock/unlock menus. (ii) The permission only kicks in if it is found. In that case, users without the permission will not be able to lock/unlock menus.
-
menu-builder-delete This permission allows your non-superusers to trash and delete menus using either the 'Actions Panel' in Menu Builder's admin main page or, when editing a single menu, via the 'Delete' tab. Note two things: (i) This permission takes a whitelist approach. If this permission does not exist on your system (i.e. has not yet been created or is present but unpublished), by default, all users will be able to trash and delete menus. (ii) The permission only kicks in if it is found. In that case, users without the permission will not be able to trash or delete menus.
-
menu-builder-selector This permission allows your non-superusers to add ProcessWire pages as menu items using a ProcessWire selector. This permission (and all the rest below) takes a blacklist approach. The permission does not need to exist in your system to kick in and by default all non-superusers are not able to add pages as menu items using selectors. This means that if you intend that only superusers will be allowed to use selectors in this manner, there is no need to create the permission. Only create and apply it if you wish to grant a particular non-superuser this feature. The same applies to all the following permissions.
-
menu-builder-selectable This permission allows non-superusers to specify ProcessWire pages that are selectable as menu items in either of the two configurable page fields in Menu Builder, i.e. AsmSelect and PageAutocomplete.
-
menu-builder-markup This permission allows your non-superusers to allow/disallow the use of markup/HTML in menu item titles/labels.
-
menu-builder-settings This permission allows non-superusers to edit nestedSortable settings, for instance set maxLevels (that controls number of nesting levels in a menu).
-
menu-builder-page-field This permission allows non-superusers to change the page field type used to select ProcessWire pages to add as menu items, i.e. toggle between AsmSelect [default] and PageAutocomplete.
-
menu-builder-include-children This permission allows non-superusers to set and use the include children feature.
##Uninstall
Uninstall like any other ProcessWire module. Note that All your menus will be deleted on uninstall!. The associated fields and template above will also be deleted.
##Resources
##License GPL2
##Changelog
#Version 0.1.0
- Cleaned-up HTML output by MarkupMenuBuilder.
#Version 0.0.9
- Fixed bug in 'current_class_level' when 'include_children' is active.
#Version 0.0.8
- Added new feature 'include children'. Allows global (menu-level) or item-level inclusion of a ProcessWire page menu item's 'natural' descendants (children, grandchildren, etc) in menus/breadcrumbs at runtime. This means such pages do not have to be added as part of the navigation during editing in Menu Builder.
- Added a 'current_class_level' to specify how far up menus the 'current_class' should be applied (i.e. current/active parents).
- Permission 'menu-builder-included-children' added to limit access to the new feature 'include children'.
#Version 0.0.7
- Added multilingual support for saving and displaying menus/breadcrumbs.
#Version 0.0.6
- Added method in MarkupMenuBuilder for rendering breadcrumbs - renderBreadcrumbs().
- Can now also pass Page object to method render() to render menu items/breadcrumbs
#Version 0.0.5
- Corrected some markup errors.
#Version 0.0.4
- Added various permissions to control visibility and editing of advanced settings
#Version 0.0.3
- Option to allow markup in menu item title (label) - superusers only
- Added more options to render() method, e.g. first, last classes
#Version 0.0.2
- First Beta version
- Menus saved as ProcessWire pages
- Menu items saved as JSON in a field in Menu pages
- Add menu items from ProcessWire pages using page fields (option to choose between PageAutocomplete and AsmSelect [default]) or a Selector (e.g. template=basic-page, limit-20).
- Specify a selector to return only those specified pages for selection in the page field (i.e. asm and autocomplete)
- For page fields, similar to custom menu items, add CSS classes and IDs as you add the items
- Menu settings for nestedSortable - e.g. maxLevels (limit nesting levels)
- Lock down menus for editing
- Highly configurable MarkupMenuBuilder
#Version 0.0.1 Initial alpha release