MEAN.js解决方案只提供了1级/2级菜单栏的service支持,最近项目中需要用到第3级菜单,所以需要进行一个小的功能扩展。一开始我以为可以很容易地做到无限级,真正做起来以后发现并没有那么简单,所以目前通过这个办法只能达到第3级。
初始的Menu Service中为使用者写了两个添加菜单项的方法:
this.addMenuItem = function (menuId, options)
|
以及
this.addSubMenuItem = function (menuId, parentItemState, options)
|
第一个方法很显然就是用来添加顶级菜单了,第二个在没有看代码以前我曾经天真地以为它可以无限嵌套,然而并没有,它做的事情仅限于添加第2级菜单。所以现在我需要自己写第三个方法来完成添加第三级菜单。考虑到三级循环的效率问题,虽然一般来说菜单项不会有太多,但看起来就是非常不爽,所以我给每个Menu项都添加了一个哈希表来储存其下面所有菜单项的引用,这样多花费一点点内存就可以不用写循环嵌套了。由于使用了哈希表,对原2级菜单做了一些修改:
this.addSubMenuItem = function (menuId, parentItemState, options) { options = options || {};
this.validateMenuExistance(menuId);
for (var itemIndex in this.menus[menuId].items) { if (this.menus[menuId].items[itemIndex].state === parentItemState) { var newSubmenuItem = { title: options.title || '', state: options.state || '', disabled: options.disabled || false, roles: ((options.roles === null || typeof options.roles === 'undefined') ? this.menus[menuId].items[itemIndex].roles : options.roles), position: options.position || 0, shouldRender: shouldRender, items: [] }; this.menus[menuId].items[itemIndex].items.push(newSubmenuItem); this.menus[menuId].menuHash[newSubmenuItem.state] = newSubmenuItem;
if (options.items) { for (var i in options.items) { this.addSubMenuItemToSubMenu(menuId, options.state, options.items[i]); } } } }
return this.menus[menuId]; };
|
然后是新的添加3级菜单的方法:
this.addSubMenuItemToSubMenu = function (menuId, parentItemState, options) { options = options || {}; this.validateMenuExistance(menuId); for (var itemIndex in this.menus[menuId].menuHash) { if (this.menus[menuId].menuHash[itemIndex].state === parentItemState) { var newSubMenuItem = { title: options.title || '', state: options.state || '', disabled: options.disabled || false, roles: ((options.roles === null || typeof options.roles === 'undefined') ? this.menus[menuId].menuHash[itemIndex].roles : options.roles), position: options.position || 0, shouldRender: shouldRender, items: [] }; this.menus[menuId].menuHash[itemIndex].items.push(newSubMenuItem); this.menus[menuId].menuHash[newSubMenuItem.state] = newSubMenuItem; } } return this.menus[menuId]; };
|
原Header模板中嵌套了两层Angular循环来遍历菜单项,我们给它加一层就好了,改完以后就像这样:
<nav class="collapse navbar-collapse" uib-collapse="!isCollapsed" role="navigation"> <ul class="nav navbar-nav" ng-if="menu.shouldRender(authentication.user);"> <li ng-repeat="item in menu.items | orderBy: 'position'" ng-if="item.shouldRender(authentication.user);" ng-switch="item.type" ng-class="{ active: $state.includes(item.state), dropdown: item.type === 'dropdown',disabled:item.disabled }" class="{{item.class}}" uib-dropdown="item.type === 'dropdown'"> <a ng-switch-when="dropdown" class="dropdown-toggle" uib-dropdown-toggle role="button">{{::item.title}} <span class="caret"></span></a> <ul ng-switch-when="dropdown" class="dropdown-menu"> <li ng-repeat="subitem in item.items | orderBy: 'position'" ng-if="subitem.shouldRender(authentication.user);" ui-sref-active="active" ng-class="{'dropdown-submenu':subitem.items.length>0,disabled:subitem.disabled}"> <a ui-sref="{{subitem.state}}" ng-bind="subitem.title" ng-if="subitem.items.length===0"></a> <a href="javascript:;" ng-bind="subitem.title" ng-if="subitem.items.length>0"></a> <ul class="dropdown-menu" ng-if="subitem.items.length>0"> <li ng-repeat="i in subitem.items | orderBy: 'position'" ng-if="i.shouldRender(authentication.user);" ui-sref-active="active" ng-class="{disabled:i.disabled}"> <a ui-sref="{{i.state}}" ng-bind="i.title"></a> </li> </ul> </li> </ul> <a ng-switch-default ui-sref="{{item.state}}" ng-bind="item.title"></a> </li> </ul> <ul class="nav navbar-nav navbar-right" ng-hide="authentication.user"> <li ui-sref-active="active"> <a ui-sref="authentication.signup">Sign Up</a> </li> <li class="divider-vertical"></li> <li ui-sref-active="active"> <a ui-sref="authentication.signin">Sign In</a> </li> </ul> <ul class="nav navbar-nav navbar-right" ng-show="authentication.user"> <li class="dropdown" uib-dropdown> <a class="dropdown-toggle user-header-dropdown-toggle" uib-dropdown-toggle role="button"> <img ng-src="{{authentication.user.profileImageURL}}" alt="{{authentication.user.displayName}}" class="header-profile-image"/> <span ng-bind="authentication.user.displayName"></span> <b class="caret"></b> </a> <ul class="dropdown-menu" role="menu"> <li ui-sref-active="active"> <a ui-sref="settings.profile">Edit Profile</a> </li> <li ui-sref-active="active"> <a ui-sref="settings.picture">Change Profile Picture</a> </li> <li ui-sref-active="active" ng-show="authentication.user.provider === 'local'"> <a ui-sref="settings.password">Change Password</a> </li>
<li class="divider"></li> <li> <a href="/api/auth/signout" target="_self">Signout</a> </li> </ul> </li> </ul> </nav>
|
修改CSS
为了让菜单看起来更自然些,这里修改的是 core.css
,添加以下内容:
.dropdown-submenu { position: relative; }
.dropdown-menu { -webkit-border-radius: 0; -moz-border-radius: 0; border-radius: 0; }
.dropdown-submenu > .dropdown-menu { top: 0; left: 100%; margin-top: -6px; margin-left: -1px; }
.dropdown-submenu:hover > .dropdown-menu { display: block; }
.dropdown-submenu > a:after { display: block; content: " "; float: right; width: 0; height: 0; border-color: transparent; border-style: solid; border-width: 5px 0 5px 5px; border-left-color: #333; margin: 5px -10px 0; }
.dropdown-submenu:hover > a:after { border-left-color: #333; }
.dropdown-submenu.pull-left { float: none; }
.dropdown-submenu.pull-left > .dropdown-menu { left: -100%; margin-left: 10px; -webkit-border-radius: 6px 0 6px 6px; -moz-border-radius: 6px 0 6px 6px; border-radius: 6px 0 6px 6px; }
|
使用方法
3级菜单的定义方法与2级菜单一模一样,除了直接调用 addSubMenuItemToSubMenu
以外,还可以通过在2级菜单内定义 items
来实现添加子菜单,示例如下,高亮部分则为3级菜单:
Menus.addMenuItem('topbar', { title: '...', state: '...', type: 'dropdown', position: 0, roles: ['*'], items: [{ title: '...', state: '...', roles: ['*'] }, { title: '...', state: '...', roles: ['*'], items: [{ title: '...', state: '...', roles: ['*'] }, { title: '...', state: '...', roles: ['*'] }] }] });
|
实现无限级
目前看来菜单层级的限制不是在于Service代码,而在于模板。如何在模板中让Angular做一个DFS搜索才是重点。Angular貌似没有提供类似的API,要做的话比较好的办法应该是自己写一个指令。以后有时间再来实现。