Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 57 additions & 8 deletions MarkupMenuBuilder.module
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class MarkupMenuBuilder extends WireData implements Module {
'title' => 'Menu Builder: Markup',
'summary' => 'Render menus created by Process Menu Builder',
'author' => 'Francis Otieno (Kongondo)',
'version' => '0.2.7',
'version' => '0.2.82',
'href' => 'http://processwire.com/talk/topic/4451-module-menu-builder/',
'singular' => true,
'autoload' => false,
Expand Down Expand Up @@ -77,6 +77,32 @@ class MarkupMenuBuilder extends WireData implements Module {
// required
}

/**
* Get nested array of given menu identity's menu_items
*
* @param int|string $menuID
* @return array $nested
*
*/
public function nestedArray(int|string $menuID) {
$m = wire('pages')->get('template=menus, name|id|title='.$menuID);
if (!$m->id) return false;

// Get flat items array:
$json = $m->menu_items;
$array = json_decode($json, true);
// Init nested array:
$nested = [];

// Populate nested array:
foreach ($array as $k => $v) {
if (array_key_exists('parent_id', $v)) $nested[$v['parent_id']][$k] = $v;
else $nested[$k] = $v;
}

return $nested;
}

/**
* Pass on menu items for processing and rendering.
*
Expand Down Expand Up @@ -275,6 +301,8 @@ class MarkupMenuBuilder extends WireData implements Module {

// NEW TAB
$m->newtab = isset($item['newtab']) ? 1 : '';
// TARGET
$m->target = isset($item['target']) ? $item['target'] : '';

// CSS
$m->cssID = isset($item['css_itemid']) ? $item['css_itemid'] : '';
Expand Down Expand Up @@ -614,7 +642,9 @@ class MarkupMenuBuilder extends WireData implements Module {
foreach ($menu as $m) {

// set properties
$newtab = $m->newtab ? " target='_blank'" : '';
$newtab = $m->newtab && empty($m->target) ? " target='_blank'" : '';
$target = $m->target ? " target='".$m->target."'" : '';
// if (!empty($target)) $newtab = ''; // Override newtab target attribute

// if this menu item is a parent; create the sub-items/child-menu-items
if ($m->parentID == $parent) {
Expand Down Expand Up @@ -658,13 +688,21 @@ class MarkupMenuBuilder extends WireData implements Module {
// apply current item class to current page + ancestors if specified for both native and included menu items
$itemCurrent = $m->isCurrent == 1 ? $o->currentClass . ' ' : '';

// a tag:
$aTopLevelClass = $o['aTopLevelClass'] ? $o['aTopLevelClass'].' ' : '';
$aChildrenClass = $o['aChildrenClass'] ? $o['aChildrenClass'].' ' : '';
$aParentClass = $o['aParentClass'] ? $o['aParentClass'].' ' : '';
$aTagClass = $parent == 0 ? $aTopLevelClass : $aChildrenClass;
if ($m->isParent) $aTagClass .= $aParentClass;
$aTagClass = trim($aTagClass);

$classes = $itemCSSClass . $itemHasChildren . $itemLast . $itemCurrent . $itemFirst;
$classes = trim(preg_replace('/\s+/', ' ', $classes));
$class = strlen($classes) ? ' class="' . $classes . '"' : '';

// if $iTag is empty, apply css id and classes to <a> instead
if (!$iTag) $out .= "\n\t<a{$itemCSSID}{$class}{$newtab} href='{$m->url}'>{$m->title}</a>";
else $out .= "\n\t<{$iTag}{$itemCSSID}{$class}>\n\t\t<a{$newtab} href='{$m->url}'>{$m->title}</a>";
if (!$iTag) $out .= "\n\t<a{$itemCSSID}{$class}{$newtab}{$target} href='{$m->url}'>{$m->title}</a>";
else $out .= "\n\t<{$iTag}{$itemCSSID}{$class}>\n\t\t<a{$newtab}{$target} class=\"{$aTagClass}\" href=\"{$m->url}\">{$m->title}</a>";

// build nested/sub-elements
$out .= str_replace("\n", "\n\t\t", $this->buildMenu($m->id));
Expand Down Expand Up @@ -717,6 +755,7 @@ class MarkupMenuBuilder extends WireData implements Module {

// set properties
$newtab = $m->newtab ? " target='_blank'" : '';
$target = $m->target ? " target='".$m->target."'" : '';

// if this menu item is a parent; create the sub-items/child-menu-items
if ($m->parent_id == $parent) { // @note: here and throughout, property names == original array index!
Expand Down Expand Up @@ -758,8 +797,8 @@ class MarkupMenuBuilder extends WireData implements Module {
$class = strlen($classes) ? ' class="' . $classes . '"' : '';

// if $iTag is empty, apply css id and classes to <a> instead
if (!$iTag) $out .= "\n\t<a{$itemCSSID}{$class}{$newtab} href='{$m->url}'>{$m->title}</a>";
else $out .= "\n\t<{$iTag}{$itemCSSID}{$class}>\n\t\t<a{$newtab} href='{$m->url}'>{$m->title}</a>";
if (!$iTag) $out .= "\n\t<a{$itemCSSID}{$class}{$newtab}{$target} href='{$m->url}'>{$m->title}</a>";
else $out .= "\n\t<{$iTag}{$itemCSSID}{$class}>\n\t\t<a{$newtab}{$target} href='{$m->url}'>{$m->title}</a>";

// build nested/sub-elements
$out .= str_replace("\n", "\n\t\t", $this->buildMenuFromCache($id));
Expand Down Expand Up @@ -940,21 +979,23 @@ class MarkupMenuBuilder extends WireData implements Module {

$url = $item->url;
$newtab = '';
$target = '';
} else {

// grab the menu item in the WireArray with the id=$item
$m = $menu->get('id=' . $item);
$title = $m->title;
$url = $m->url;
$newtab = $m->newtab ? "target='_blank'" : '';
$target = $m->target ? "target='".$m->target."'" : '';
}

// ancestor items
if ($total - $i != 1) {
$divider = " {$o->divider} "; // note the spaces before and after!
// if $iTag is empty, default to <a> instead
if (!$iTag) $out .= "<a {$newtab} href='{$url}'>{$title}</a>{$divider}";
else $out .= "<$iTag><a {$newtab} href='{$url}'>{$title}</a>{$divider}</$iTag>";
if (!$iTag) $out .= "<a {$newtab}{$target} href='{$url}'>{$title}</a>{$divider}";
else $out .= "<$iTag><a {$newtab}{$target} href='{$url}'>{$title}</a>{$divider}</$iTag>";
}

// the last breadcrumb item, i.e., the current page
Expand Down Expand Up @@ -1413,6 +1454,7 @@ class MarkupMenuBuilder extends WireData implements Module {
'title' => $m->title, // default_title: 0=show saved mb titles;1=show actual/current pw titles
'url' => $m->url, // menu items URL
'newtab' => $m->newtab, // open link in new tab?
'target' => $m->target, // open link in new tab?
'css_itemid' => $m->cssID, // the items own CSS ID
'css_itemclass' => $m->cssClass, // the items own CSS class(es)
'include_children' => $m->includeChildren, // include this menu items natural children
Expand Down Expand Up @@ -2131,6 +2173,9 @@ class MenuOptions extends WireData {
current_class_level// NOT for breadcrumbs - 0=unlimited (+ include ancestors of current page that are not menu items);1=current item only;2=current+parent;3=current+parent+grandparent
current_css_id// only for breadcrumbs
default_class// a default css class to be applied to all menu items
$this->set('aTopLevelClass', isset($options['a_toplevel_class']) ? $options['a_toplevel_class'] : ''); // a tags
$this->set('aChildrenClass', isset($options['a_children_class']) ? $options['a_children_class'] : ''); // a tags
$this->set('aParentClass', isset($options['a_parent_class']) ? $options['a_parent_class'] : ''); // a tags
divider// only for breadcrumbs
prepend home page at the as topmost item even if it isn't part of the breadcrumb
prepend_home// only for breadcrumbs => 0=no;1=yes
Expand Down Expand Up @@ -2165,6 +2210,9 @@ class MenuOptions extends WireData {
$this->set('currentClassLevel', isset($options['current_class_level']) ? $options['current_class_level'] : 1);
$this->set('currentCSSID', isset($options['current_css_id']) ? $options['current_css_id'] : '');
$this->set('defaultClass', isset($options['default_class']) ? $options['default_class'] : '');
$this->set('aTopLevelClass', isset($options['a_toplevel_class']) ? $options['a_toplevel_class'] : '');
$this->set('aChildrenClass', isset($options['a_children_class']) ? $options['a_children_class'] : '');
$this->set('aParentClass', isset($options['a_parent_class']) ? $options['a_parent_class'] : '');
$this->set('divider', isset($options['divider']) ? $options['divider'] : '&raquo;');
$this->set('prependHome', isset($options['prepend_home']) ? $options['prepend_home'] : 0);
$this->set('defaultTitle', isset($options['default_title']) ? $options['default_title'] : 0);
Expand Down Expand Up @@ -2201,6 +2249,7 @@ class Menu extends WireData {
$this->set('title', '');
$this->set('url', '');
$this->set('newtab', '');
$this->set('target', '');
$this->set('parentID', ''); // mb menu item parent [not pw!]
$this->set('pagesID', ''); // for pw pages
$this->set('cssID', '');
Expand Down
49 changes: 45 additions & 4 deletions ProcessMenuBuilder.module
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class ProcessMenuBuilder extends Process implements Module {
'title' => 'Menu Builder: Process',
'summary' => 'Easy, drag and drop menu builder',
'author' => 'Francis Otieno (Kongondo)',
'version' => '0.2.7',
'version' => '0.2.82',
'href' => 'http:// processwire.com/talk/topic/4451-module-menu-builder/',
'singular' => true,
'autoload' => false,
Expand Down Expand Up @@ -430,6 +430,7 @@ class ProcessMenuBuilder extends Process implements Module {
$this->_('CSS ID'),
$this->_('CSS Class'),
$this->_('New Tab'),
$this->_('Target'),
));

$n = $modules->get('InputfieldName');
Expand All @@ -452,12 +453,17 @@ class ProcessMenuBuilder extends Process implements Module {
$itemCustomNewTab = "<input type='checkbox' name='new_newtab[]' value='0' class='newtab'>";
$itemCustomNewTabHidden = "<input type='hidden' name='new_newtab_hidden[]' value='0' class='newtabhidden'>";// force send a value for new tabs

$nt = $modules->get('InputfieldName');
$nt->attr('name', 'new_item_custom_target[]');
$nt->attr('class', 'new_custom');

$t->row(array(
$n->render(),
$u->render(),
$n2->render(),
$n3->render(),
$itemCustomNewTab . $itemCustomNewTabHidden,
$nt->render(),
'<a href="#" class="remove_row"><i class="fa fa-trash"></i></a>',
));

Expand Down Expand Up @@ -552,6 +558,7 @@ class ProcessMenuBuilder extends Process implements Module {
$this->_('CSS ID'),
$this->_('CSS Class'),
$this->_('New Tab'),
$this->_('Target'),
$this->_('Type'),// custom or PW page

));
Expand Down Expand Up @@ -580,6 +587,8 @@ class ProcessMenuBuilder extends Process implements Module {
// does this menu item link open in a new window or not (i.e. target='_blank') - for custom menu items only
$itemNewTab = isset($menuItem['newtab']) ? $this->_('Yes') : $this->_('No');

$itemTarget = isset($menuItem['target']) ? $menuItem['target'] : '';

$itemCSSID = isset($menuItem['css_itemid']) ? $menuItem['css_itemid'] :'';
$itemCSSClass = isset($menuItem['css_itemclass']) ? $menuItem['css_itemclass'] : '';

Expand All @@ -590,6 +599,7 @@ class ProcessMenuBuilder extends Process implements Module {
$itemCSSID,
$itemCSSClass,
$itemNewTab,
$itemTarget,
$itemType)
);

Expand Down Expand Up @@ -773,7 +783,7 @@ class ProcessMenuBuilder extends Process implements Module {
$r = new InputfieldRadios();
$r->attr('id+name', 'menu_item_disable_items');
$r->label = $this->_('Use enable/disable menu items feature');
$r->notes = $this->_('Allows you to set some menu items as disabled. If an item is disabled, the item together will all of its descendants will be set as disabled after you save the menu settings. Disabled items will not be output when the menu is viewed in the frontend.');
$r->notes = $this->_('Allows you to set some menu items as disabled. If an item is disabled, the item together with all of its descendants will be set as disabled after you save the menu settings. Disabled items will not be output when the menu is viewed in the frontend.');

$radioOptions = array (
1 => $this->_('Yes'),
Expand Down Expand Up @@ -927,7 +937,7 @@ class ProcessMenuBuilder extends Process implements Module {
- css_itemclass: this menu items's CSS Class (optional)
- pages_id: for PW pages items = $page->id; for custom menu items = 0 (note: this is different from id!)
- optional include children feature
- opitional disable menu items feature
- optional disable menu items feature

*/

Expand Down Expand Up @@ -959,6 +969,7 @@ class ProcessMenuBuilder extends Process implements Module {
$this->cssItemClass = isset($item['css_itemclass']) ? $item['css_itemclass'] : '';
$this->itemPagesID = isset($item['pages_id']) ? $item['pages_id'] : 0;// only PW pages will have a pages_id > 0 (equal to their PW page->id)
$this->newTab = isset($item['newtab']) ? $item['newtab'] : 0;
$this->target = isset($item['target']) ? $item['target'] : '';// bd($this->target); // 210x bd() for 14 menu items
$this->itemIncludeChildren = isset($item['include_children']) ? $item['include_children'] : '';
$this->itemMenuMaxLevel = isset($item['m_max_level']) ? $item['m_max_level'] : '';
$this->disabledItem = isset($item['disabled_item']) ? $item['disabled_item'] : '';
Expand Down Expand Up @@ -1332,6 +1343,8 @@ class ProcessMenuBuilder extends Process implements Module {
if($this->disableItems == 1) $out .= $this->buildMenuItemDisabledMarkup();
######################### custom menu item new tab markup #########################
if(0 == $this->itemPagesID) $out .= $this->buildMenuItemNewTabMarkup();
######################### custom menu item target markup #########################
$out .= $this->buildMenuItemTargetMarkup();
######################### item hidden inputs markup #########################
$out .= $this->buildMenuItemHiddenInputs();

Expand Down Expand Up @@ -1534,7 +1547,24 @@ class ProcessMenuBuilder extends Process implements Module {
}

/**
* Builds hiden inputs for tracking menu items settings.
* Builds input for specifying menu item target attribute.
*
* Used in a menu item's settings.
*
* @access private
* @return string $out Markup of input.
*
*/
private function buildMenuItemTargetMarkup() {
$id = $this->itemID;
$value = isset($this->menuItems[$this->itemID]['target']) ? $this->menuItems[$this->itemID]['target'] : '';
$out = '<label for="target' . $id . '">' . $this->_('Target') . '</label>
<input type="text" name="target[' . $id . ']" value="' . $value . '" class="menu_settings" id="target' . $id . '"/>';
return $out;
}

/**
* Builds hidden inputs for tracking menu items settings.
*
* @access private
* @return string $out Markup of hidden inputs.
Expand Down Expand Up @@ -2425,6 +2455,8 @@ class ProcessMenuBuilder extends Process implements Module {
$itemCSSClass = $sanitizer->text($post->css_itemclass[$itemID]);// sanitizer->text to accept multiple classes
$itemNewTab = isset($post->newtab[$itemID]) ? 1 : '';// only save for custom menu items with target='_blank'

$itemTarget = isset($post->target[$itemID]) ? $sanitizer->name($post->target[$itemID]) : '';// save for any custom/page(s)

// if current user can edit include children values + change include children setting
$itemIncludeChildren = '';
if( $this->includeChildren && isset($post->include_children[$itemID]) ) {
Expand All @@ -2451,6 +2483,7 @@ class ProcessMenuBuilder extends Process implements Module {
'include_children' => $itemIncludeChildren,
'm_max_level' => $itemMMaxLevel,
'disabled_item' => $itemDisabled,
'target' => $itemTarget,
);

// add disabled item to array to check if to apply same status to descendants
Expand Down Expand Up @@ -2553,6 +2586,7 @@ class ProcessMenuBuilder extends Process implements Module {
$itemCSSClass = $sanitizer->name($post->new_css_itemclass[$i]);
//$itemNewTab = (!isset($post->new_newtab[$i])) ? '' : 1;// using checkbox unreliable; use hidden input instead (below)
$itemNewTab = (int) $post->new_newtab_hidden[$i] ? 1 : '';// hidden input to resolve above
$itemTarget = isset($post->new_item_custom_target[$i]) ? $post->new_item_custom_target[$i] : '';

// add custom (external) menu items to our menu
$menuItems[$menuItemID] = array(
Expand All @@ -2563,7 +2597,10 @@ class ProcessMenuBuilder extends Process implements Module {
'css_itemclass' => $itemCSSClass,
'pages_id' => '',
'newtab' => $itemNewTab,
'target' => $itemTarget,
);
// Auto-disable new items:
if ($this->disableItems) $menuItems[$menuItemID]['disabled_item'] = 1;

$menuItemID++;

Expand Down Expand Up @@ -2637,6 +2674,8 @@ class ProcessMenuBuilder extends Process implements Module {
'm_max_level' => $itemMMaxLevel,
// 'b_max_level' => $itemBMaxLevel,// @todo - not setting individually for now
);
// Auto-disable new items:
if ($this->disableItems) $menuItems[$menuItemID]['disabled_item'] = 1;

$menuItemID++;

Expand Down Expand Up @@ -2688,6 +2727,8 @@ class ProcessMenuBuilder extends Process implements Module {
'pages_id' => $item->id,// the PW page ID
// 'newtab' => ''// NOT necessary for PW pages
);
// Auto-disable new items:
if ($this->disableItems) $menuItems[$menuItemID]['disabled_item'] = 1;

$menuItemID++;

Expand Down
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# 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](https://github.com/mjsarfatti/nestedSortable) jQueryUI plugin by Manuele J Sarfatti.

_(Alterations v0.2.8 - 0.2.82 by chimmel)_


## Features
* Visual menu builder
Expand Down Expand Up @@ -78,6 +80,10 @@ $defaultOptions = array(
'm_max_level' => 1,// how deep to fetch 'include_children'
'current_class_level' => 1,// how high up the ancestral tree to apply 'current_class'
'default_class' => '',// a CSS class to apply to all menu items
// inner a tags:
'a_toplevel_class' => '',
'a_children_class' => '',
'a_parent_class' => '',

);
````
Expand Down Expand Up @@ -1026,6 +1032,16 @@ GPL2

## Changelog

### Version 0.2.82
1. Add nestedArray method to MarkupMenuBuilder.

### Version 0.2.81
1. Add options a_toplevel_class, a_children_class, a_parent_class.

### Version 0.2.8
1. If disable_items menu setting is enabled: automatically disable newly added menu items until positioned in the menu, so as to allow for positioning post-save/addition in a published menu in use, prior to the item being added to its output, e.g., if menu is already in use on the frontend. Once saved/added/disabled, and then positioned, 'Disable' can be unchecked, save, and proper placement attained with no disruption.
2. Add text input for target attribute that overrides 'New Tab', and is applicable on any menu item (custom/page).

### Version 0.2.7
1. In multi-lingual environments, menus and breadcrumbs can retrieved using their titles or names in any language irrespective of the current user's language.
2. For getMenuItems() usage only, added option extra_fields to return values of some specific fields on the menu item pages. See documentation for compatible Fieldtypes.
Expand Down