menubuilder

UI for managing (CRUD, arrange nestable) menus/items online
29 followers

Use this extension if you don't want to use hardcoded menus/items only in your application. Let the admin/sitemaster design the menus online.

The menubuilder module comes with (hope so) all necessary features to manage your menus and items. The extension supports all menus inherited from CMenu.

Features

  • A single adminform to manage menus and menuitems

    • CRUD operations
    • Assign the items to a menu
    • Arrange the items, nestable for submenu items
    • Preview the menu as mbmenu, superfish, bootstrap-navbar,bootstrap-menu, dropdownlist and lists
    • Simulate scenarios, userroles and languages on preview
    • Fine granular access-permissions (adminroles): Who is allowed (user/role) to update/append/delete menus and menuitems
    • 2 themes: blueprint/bootstrap
  • Menus and items

    • visibility by userroles (rbac)
    • visibility by different scenarios, for example 'backend', 'frontend', 'dashboard' ...
    • support for icons
    • support for ajax
    • generate CMenu items, dropdownlists, (un-)ordered lists
    • Fine granular admin-permissions(adminroles): Who is allowed (user/role) to update/append/delete menus and menuitems
    • Support for all properties of the CMenu items
  • Merge different menus into one

  • Multilanguage support
  • Storage: file, mysql, mongodb
  • Export/import to transfer menus/items from one site to another
  • Caching menu items
  • Themes for bluegrid and bootstrap
  • Callbacks onVisible, onActive, onUrl, onLabel when rendering a menuitem
  • Customize the adminform view and all components

Requirements

Developed with Yii 1.1.12

Installation

The menubuilder is designed as an extension that holds the core components and a module as 'working copy' for you, where you can customize the components and views.

You can download the code from menubuilder on github too.

  1. Extract the menubuilder.zip to protected/extensions
  2. Copy the directory protected/extensions/menubuilder/modules/menubuilder to your modules directory (protected/modules)

    This is your working copy, where you can modify the code without touching the core components.

    Ensure, the directory protected/modules/menubuilder/data is writeable.

  3. Add the menubuilder module to the modules in config/main.php

'modules'=>array(
  'menubuilder',
  ...
)

Browse to the menubuilder module (yourapp/index.php/menubuilder) and you should be able to play around with the menubuilder for testing.

On the first run the menus from data/installmenus.php and data/installmenuitems.php will be installed. You can modify these arrays and call 'reinstall' from the utilities tab in the menubuilder form. Set 'checkInstall'=>false after the first run (see below).

How it works

The implementation is built upon the Nestable jQuery Pluging by David Bushell. The nestable items are serialized to a JSON string like: [{"id":13},{"id":14},{"id":15,"children":[{"id":16},{"id":17},{"id":18}]}]

So there is a (unordered) list of menu available items at the left side of the manage form. The menu model holds this JSON string as the 'nestedConfig' property with the structure of the items at the right side. You build the hierachical list by drag & drop the items between the two sides.

The default configuration shows the menupreview as a superfish menu and as a unordered list with the 'blueprint' theme, no multilanguage support. Support for the userroles 'Guest' and 'Autenticated' and two scenarios: 'Backend' and 'Frontend'

Usage

Extended configuration

'modules'=>array(
 
'menubuilder'=>array(
            'theme'=>'bootstrap', //comment for bluegrid theme (=default)
            //'checkInstall'=>false, //uncomment after first usage
            //'cacheDuration'=> -1, //uncomment for disabling the menucaching
            'languages'=>array('de','en_us'),
            'supportedScenarios'=>array('backend' => 'Backend', 'frontend' => 'Frontend', 'dashboard' => 'Dashboard'),
 
            //set EMBDbAdapter to switch to mysql (checkInstall=>true on first run)
            //'dataAdapterClass'=> 'EMBDbAdapter', //'EMBMongoDbAdapter',
 
            //the available menus/lists for the preview
            'previewMenus'=>array(
               // 'superfish'=>'Superfish',
               // 'mbmenu'=>'MbMenu',
                'bootstrapnavbar'=>'Bootstrap Navbar',
                'bootstrapmenu'=>'Bootstrap Menu',
              // 'dropdownlist'=>'Dropdownlist',
                'unorderedlist'=>'Unordered list'
            )
        ),
)

Permission settings

  1. When using in your application you have to set the accessRules in the AdminController to check the access to the actions of the menubuilder (index, saveAsDefault, restoreDefault, reinstall, export, import, flushCache) for (admin/sitemaster) users. See: protected/modules/menubuilder/controllers/Admincontroller

  2. The menubuilder is designed to delegate managing menus to a sitemaster or other users. By default every user can configure the menus with all features. So you have to configure the permissions of the admin form. This is done by the method formPermissionRules() of the Admincontroller. This is implemented analogous to the accessRules, but 'actions' are replaced by following items:

public function formPermissionRules()
    {
        return array(
            array('allow', // The same as accessRules
                'users' => array('*'),
                //'roles'=>array('admin'),
                //'ips' .....
 
 
                //instead of actions:
                'menu'=>array('create','update','delete','simulate','preview'),
                'menuitem'=>array('create','update','delete','arrange','advanced'),
                'util'=>array('flushcache','restoredefault','saveasdefault','reinstall','import','export'),
                'menuFields'=>array('menuid','visible','locked','sortposition','maxdepth','icon','titles','descriptions','scenarios','userroles','adminroles','createdinfo'),
                'menuitemFields'=>array('visible','active','labels','descriptions','url','target','ajaxOptions','linkOptions','ajaxOptions','itemOptions','submenuOptions','template','icon','scenarios','userroles','createdinfo'),
                'simulateFields'=>array('scenarios','userroles','languages'),
            ),
            array('deny'),
        );
    }
 )

Set menu, menuitem or util => false to hide this section/tab in the menubuilder form.

Example:

public function formPermissionRules()
    {
        return array(
            array('allow',
                'users' => array('admin'), //allow all to admin
            ),
            array('allow',
                  'users' => array('sitemaster'),
 
                    'menu'=>array('update','simulate','preview'), //no create, delete
                    'menuFields'=>array('visible','sortposition','maxdepth','icon','titles','descriptions','scenarios','userroles','createdinfo'), //no menuid, locked, adminroles
                    'menuitem'=>array('create','update','delete','arrange'), //no advanced
                    'util'=>false, // no import/export ...
                    //all menuItemFiels, simulate if not listed
                ),
            array('allow',
                  'users' => array('editor'), //allow only to arrange the menu items
                    'menu'=>false, //don't show the menu model properties, simulate and preview
                    'menuitem'=>array('arrange'), //no advanced
                    'util'=>false, 
                ),
            array('deny'),
        );
    }
 )

Rendering the menus

Run the EMBMenu widget in your layout view and set the attribute menuClass to a menu widget. The class must be a CMenu (no support for subitems), a CMenu descendant of your choice or one the integrated menus. Assign the options of the menu to the property menuOptions, as you would do it without the menubuilder. You can use the items property in the menuOptions too. These hardcoded items will be merged with the items from the menubuilder (see below).

The simplest usage

$this->widget('ext.menubuilder.widgets.EMBMenu', array(
              'menuClass'=>'ext.menubuilder.extensions.superfish.RSuperfish', //The integrated superfish menu
              //'menuOptions'=> array( ....), //the CMenu/Superfish properties
                'menuIds' => 'main', //display the menu with the id 'main'
               ));

This will render the RSuperFish menu the items of the menu with the id 'main'. If the user is a guest, all items with no userroles assigned or items with the userrole 'guest' will be visible. By assigning items in the menuOptions property, you can add fixed, hardcoded items as you would do in CMenu.

Merging menus

  • Merge the menubuilder items with the hardcoded items like using the CMenu. Set the property menuBuilderItemsBefore=>false (default=true)
  • Assign multiple menuIds as array ('main','main_frontend' ...)
  • Assign one (as string) or more (as array) scenarios

Set the rootMenuIds property, if you want to merge the menu's with a single root label (the menu title or fixed) with the items from the menubuilder as subitems.

Example for a 'dashboard' view:

$this->widget('ext.menubuilder.widgets.EMBMenu', array(
              'menuClass'=>'ext.menubuilder.extensions.superfish.RSuperfish', //The integrated superfish menu
              'menuOptions'=> array( //fixed items
                    'items'=>array(
                        array('label'=>'Login', 'url'=>array('/site/login'), 'visible'=>Yii::app()->user->isGuest),
                        array('label'=>'Logout ('.Yii::app()->user->name.')', 'url'=>array('/site/logout'), 'visible'=>!Yii::app()->user->isGuest)
                    )
                ),
                'menuIds' => array('main','dashboard'), 
               ));

The main menu will be visible at the frontend and the backend (if no userroles are assigned). Assign the userrole 'authenticated' to the 'dashboard' menu, so this items will only be visible if the user is logged in.

For more flexible, dynamic menus/items you should use 'scenarios' instead of the menuIds.

$this->widget('ext.menubuilder.widgets.EMBMenu', array(
              'menuClass'=>'ext.menubuilder.extensions.superfish.RSuperfish', //The integrated superfish menu
              //'menuOptions'=> array(...),
               'scenarios' => 'frontend', // array('frontend','backend','admin'), 
               'rootMenuIds' => 'backend_admin', //array('backend_admin'=>array('label'=>'Adminmenu')),
               ));

This will display all menus and items configured with scenario 'frontend'. If a menu with the id 'backend_admin' has the scenario 'frontend' too, there will be added a label as root with the menutitle as text (or fixed to 'Adminmenu') and the menuitems as subitems of this label. Of course the visibility of the menus/items will be checked by userroles too.

Bootstrap navbar

You first have to get the items first and assign the result to the TbMenu.

$items=$this->createWidget('ext.menubuilder.widgets.EMBMenu', array(
                'menuIds'=>'navbar',
                'menuBuilderItemsBefore' => false,
                'menuOptions'=> array(
                    'items'=>array(
                        array('url'=>array('/menubuilder'),'label'=>'Menubuilder'),
                    )
                ),
               )
              )->getItems();
 
 
        $this->widget('bootstrap.widgets.TbNavbar', array(
            'collapse' => true,
            'items' => array(
                array(
                    'class' => 'bootstrap.widgets.TbMenu',
                    'encodeLabel'=>false,
                    'items' => $items,
                )
            )
        ));

Callbacks

For more complex menuitems you can register callback functions: onVisible, onActive, onLabel, onUrl. You can change visible, active, the label or the url before rendering the output.

If assigned, a evaluate expression will be executed on rendering the menu items. You can register an expression string or a function. The function will be called with the parameters: currentvalue (visible,active,label or url), model (the menuitem model), itemProvider (the itemProvider instance).

//Add a method to your controller like this, if the default activate handler of the CMenu doesn't work
public function checkMenuItemActive($active,$model,$itemProvider) 
{
   //if(strpos($model->url, ...) && $this->action->id == ....)) ....
   return ($this->action->id == ....) ? true : false;
}
 
 
//in your view
$this->widget('ext.menubuilder.widgets.EMBMenu', array(
              'menuClass'=>'ext.menubuilder.extensions.superfish.RSuperfish', 
              'onActive'=>array($this,'checkMenuItemActive'),
               ));

Advanced

Customizing

The menubuilder is designed to override the components and install the customized components. Example EMBDataFilter: This component - installed as default - implements methods to get the current user roles and the supported roles. If you have an authmanager installed, you can configure the EMBRbacDataFilter dataFilter in config/main.php instead.

'modules'=>array(
 
'menubuilder'=>array(
            ...
            'dataFilterClass'=>'EMBRbacDataFilter',
 
        ),
)

Example for a custom dataFilter: Create the file MyDataFilter.php in the components directory of the menubuilder module and configure MyDataFilter as dataFilterClass in config/main.php

class MyDataFilter extends EMBDataFilter
        {
            public static function getCurrentUserRoles()
            {
                switch(Yii::app()->user->id)
                {
                    case 'admin':
                    $roles = array('authenticated'=>'Authenticated user','admin'=>'Admin');
                    break;
 
                    case 'sitemaster':
                    $roles = array('authenticated'=>'Authenticated user','sitemaster'=>'Sitemaster');
                    break;
                    ...
                    default:
                    $roles = parent::getCurrentUserRoles(); //authenticated or guest or configured in config/main.php
                }
 
                return $roles;
            }
 
            public static function getSupportedUserRoles()
            {
                return array_merge(parent::getSupportedUserRoles(),array('admin'=>'Admin','sitemaster'=>'Sitemaster'));
            }
        }
)

Other installable classes: iconProviderClass, itemsProviderClass, dataAdapterClass, formPermissionsClass

Note: If you want to use a menu that is not a descencant from CMenu, you can override the EMBItemsProvider add a method 'getMyMenuItems' and install this class in the menubuilder. Override the EMBMenu with the getItems methods.

Output as list

Use the EMBList widget to render menus/items as list (used in a portlet, linklist ...). Change the tags and tagHtmlOptions for your needs.

$this->widget('ext.menubuilder.widgets.EMBList', array(
        //'titleTag'=>'h4', //=default
        //'titleHtmlOptions'=>array(...),   
        //'listTag'=>'ul', //=default
         //'itemTag'=>'li', //=default
         ...
        'menuIds'=>....
        'scenarios' => ...
 
    )
);

Ajax

Fill the 'ajaxOptions' field in the advanced tab of the menuitem form with the update property: update=#ajaxContent This menu item will be rendered as an ajax call on click. The div with the specified id will be updated on success.

If you want to render all items of a menu as ajax items, you don't have to set the ajaxOptions for every item. You can use the property ajaxOptions of the EMBMenu widget instead. With the useAjaxOnClick attribute of the EMBMenu widget (default=false) you can decide, wether to add the ajax-call as the onclick-attribute of the item or bind it as jQuery event on document-ready.

Other menupreviews

If you want to use another menu (example: XYMenu) in the preview:

  • Register the menu in the menuconfig
'modules'=>array(
 
'menubuilder'=>array(
            ...
              'previewMenus'=>array(
                'xymenu'=>'My menu', //subdir => name
 
            )            
        ),
);
  • Create a viewfile '_xymenu.php' in the directory views/admin/menupreviews with the menu widget like _mbmenu.php or _superfish.php

Caching

If your application has a cache installed, this one will be used to cache the menuitems. This will increase the performance of the menu output. Extra keys will be used for different menu configurations, depending on userroles, scenarios, ... In the menu adminMode, the cache will be flushed after every CRUD operation of a menu or menuitem. Set the modules property autoFlushCache to false if you don't want this behavior. With this configuration the admin user can modify the menus, the changes will be visible to online users after flushing the cache. You can flush the cache by executing the action flushCache in the utilities tab.

Important note: Flushing the cache will flush the applications global cache. To avoid this, you should use the cacheOptions property of the menubuilder. You can assign a different storage path for the menu cache (depending on cache type) to separate the menucache from the application cache.

Set the modules property cacheDuration to -1 to disable the cache.

An example configuration could be:

'modules'=>array(
 
'menubuilder'=>array(
            ...
            //'cacheDuration'=> 0, // =default: never expires 
            'autoFlushCache' => false, //default: true
            'cacheOptions' => array(
                                    'cacheTableName' => 'menubuildercache', // for CDbCache
                                    //'cachePath' => 'pathToMenuBuilderCacheDir' //for CFileCache  
 
                                   ),           
        ),
)

You can set the EMBMenu/EMBList widgets attribute cached=>false if you want to exclude a specific menu from caching.

Utilities

The util action links of the AdminController are available in the utilities tab of the menubuilder.

  • Use the import/export link in the utilities tab to transfer menus/items from you developer workstation to your production enviroment.

  • You can save a snapshot of the menus as default and restore this by restorefromdefault.

  • You can reinstall the menus too.

Note:

If you want to prepare a installation for your production enviroment, you can design the menu and save it as default. This will generate the files x_defaultmenuitems.php, x_defaultmenus.php (prefix x depends on the installed dataAdapter) in the data directory of the menubuilder. Rename these files to installmenus.php and installmenuitems.php and publish these files.

What's next

The menubuilder includes the component EMBPageBehavior. You can't use this, it's unfinished and not tested.

If finished in the next release, you should be able to integrate the menubuilder with your cms. You can integrate a embedded form in your page model form, where the user can choose to create a menuitem before or after an existing item.

The menubuilder contains the german translation file messages/de/. I someone uses this extension in another language, please send me the translated messages.php.

Resources

Integrated jQuery plugins / extensions

Optional Yii extensions that must be installed manually

Changelog

  • v1.01 Bugfix:
    • Path to select2 component 'ext.menubuilder.extensions.select2.ESelect2' instead of 'ext.select2.ESelect2' in the bluegrid-views
    • data/mbmenus.php, data/mbmenuitems.php should not be included in the released code.

Total 14 comments

#16082 report it
Trejder at 2014/01/17 04:38am
First impression

After reading (broadly) text and looking at screenshot, I thought, that this will be a great extension. And it is. It certainly is a good piece of code! However, I run into some issues after only five minutes of using it:

  1. DB table prefix MUST be variable and user must be able to set it in application configuration. Forcing user to use emb_ prefix for DB tables, because it is currently hardcoded is very, very wrong and makes using your great extension in many Yii applications simply not possible. It isn't the good idea, that user takes your code and starts with rewritng it or at least changing hardcoded emb_ DB's prefixes to something, that suits him or her.

  2. Yii application MUST be in English (must have 'language'=>'en_us') when installing / first running your menu, or else, menubuilder won't install default menu structure (currently it is available for de and en_us only). The other approach is to re-edit menu structure and fix it to current application's language. Nothing about this is anywhere in "Install" section of your extension's documentation or I did miss this.

  3. If there isn't any menu structure loaded (menu is completely empty), menubuilder does not display title field (which is required) and thus makes itself completely unusable. User can't create first menu, because title field is required, which he or she can't fill-in, because it is hidden (not rendered) in the form.

  4. Menubuilder (your extension) is a module and thus, should be initially rendered in current user theme / layout, not introduce its own one. It isn't a good idea, that user takes your extensions's code and must start with rewriting views and controllers settings to make it displayed on app's default layout / theme.

  5. You extensions uses ESelect2 extension and has all necessary code bundled with, but does not register it properly. So, first run of an application, after installing your extension, ends with critical Yii error, that ext.select2.ESelect2 alias is invalid. To fix this, user must either install ESelect2 extension to /protected/extensions/ folder or edit your extension's code and fix alias / ESelect2 registration.

Fixing above may take some time, so I must consider, if I have it or if I should go to something else, that will require less work to adapt it to a working application.

#15983 report it
bandpay at 2014/01/07 06:10am
Different problem

Dear Joblo, My problem was about missing rangeFieldRow and that was why I upgraded to newer YiiBooster. But since the MenuBuilder began to malfunction, I reverted to the older YiiBooster and copied the needed functions from the new version into the older version and got the rangeFieldRow to work.

I Believe that your solution is excellent. Looking forward to use the new version and setup. Cheers. Sincerely Babak

#15981 report it
Joblo at 2014/01/07 03:37am
bootstrap

How did you solve the problem? My idea is to add a method 'renderSelect2' to the EMBAdminController like below. And use this in all layout views (blueprint, bootstrap, yiistrap ...)

public function renderSelect2($form,$label,$model,$attribute,$data)
    {
        echo CHtml::tag('span', array(), Yii::t('MenubuilderModule.messages', $label) . ' ');
        $this->widget('menubuilder.extensions.select2.ESelect2', array(
            'model' => $model,
            'attribute' => $attribute,
            'data' => $data,
            'options' => array(
                'width' => '250px',
            ),
            'htmlOptions' => array(
                'multiple' => 'multiple',
            ),
        ));
 
        echo $form->error($model, $attribute);
    }

Example usage in the views:

$this->renderSelect2($form,'Userroles',$model,'userroles',$viewParams['supportedUserRoles']);

The bootstrap story never ends ... there exists another extension for bootstrap3 (4,5..).

#15979 report it
bandpay at 2014/01/07 02:52am
Thanks for the reply

Dear Joblo, I have coded my way out of the problem, even I am a Yii noob. I'm sure that your product will be operational again and I think that it is kind of weird that they have removed some properties from the Select2 class and have introduced so many changes in the code that it is not recognizable anymore. I believe that this is a defining moment in the future of all the Bootsrap related Yii packages. Maybe now we find out if we shoud keep to Wheel or Strap or Booster once and for all. Have a nice day dear Joblo Sincerely Babak Bandpey

#15975 report it
Joblo at 2014/01/06 10:40am
select2

Detected this problem too a few days before: I have tried to add yiistrap layout with yiiwheels select2. The support for assoziative array data/tags for select2 went away :-(

I didn't found a solution, but the standard select2 - used in blueprint layout - works (delivered with the menubuilder). So I will switch to this in bootstrap and yiistrap layout.

In the meanwhile - before the next release - you can do the same: use the select2 code from blueprint layout and add bootstrap control divs ...

#15974 report it
bandpay at 2014/01/06 10:23am
YiiBooster > 2.0.0 Fails

Hello. This is a great extension.

I have just experienced some errors when upgrading to newer version of YiiBooster. Select2 won't select multiple items and some other things go wrong as well.

I hope that you have time to fix these issues because your extension is seriously nice.

Have a nice time.

#14804 report it
Joblo at 2013/09/11 03:22am
bootstrap

Sorry, no Yiistrap support is planned yet. In my projects I'm using yiibooster only.

But it should not be a lot of work:

  • Copy the modules/views/bootstrap folder to modules/views/yiistrap

  • Search for 'bootstrap' in the views: Replace the bootstrap.widgets.X (TbButton, TbActiveForm,TbTabView,TbMenu...) with yiistrap components.

  • set the theme property of the menubuilder to 'yiistrap' in config/main.php

#14799 report it
PaulD at 2013/09/10 11:35am
Yiistrap support

Is this extension going to be updated to support Yiistrap anytime soon. It fails for any boostrap setting in config using the new Yiistrap extensions.

#14017 report it
leo4all at 2013/07/12 03:20am
demo

if there a demo page or screenshots, I think I did install it correctly but am no sure yet how to use it.

#13809 report it
Argenis Bolivar at 2013/06/27 03:08pm
Nice

Excellent, Thanks, you have github? you can add Yii::app()->clientScript->coreScriptPosition to registerScriptFile($assets.'/js/.js',Yii::app()->clientScript->coreScriptPosition)

#13807 report it
Joblo at 2013/06/27 12:24pm
usort: php bug?

Google the error message:

for example here

Maybe this helps: modify usort to @usort in line 89 of the EMBFileAdapter.php

Or comment this line - but then the menus will not be sorted by sortposition/title.

Or maybe a simplier sort function (without the title as second criteria) works:

protected function sortMenus($a, $b)
    {
        if ($a->sortposition == $b->sortposition)
             return 0;
        else
            return ($a->sortposition < $b->sortposition) ? -1 : 1;
    }

Please let me know

#13804 report it
Argenis Bolivar at 2013/06/27 11:34am
Error

I have this error try access index.php/menubuilder with the basic configuration "usort(): Array was modified by the user comparison function" in menubuilder/components/EMBFileAdapter.php

Any idea?

#13760 report it
trond at 2013/06/23 01:19pm
Very impressive extension

I need to do something similar, and will download and study it. Thanks a lot for sharing!

#13757 report it
samilo at 2013/06/23 10:05am
Very Good

Thank you so much , I need this extension very much .

Leave a comment

Please to leave your comment.

Create extension