How to define and render submenu-items, using Aurelia's Router

一世执手 提交于 2019-12-04 09:48:44
Charleh

The problem you have is that sub routes tend to use child routers.

This enables some pretty powerful scenarios in Aurelia but presents the challenge that your route configuration might not be present until you have navigated to a child route.

I've handled this scenario before by proving a routing service that surfaces a routing tree as a single object with some helper methods to transform parts of this into a route config object which Aurelia can consume.

This is then injected into the sub modules and they query it to configure the router

The nav menu component can then look at this tree to build the menu structure ahead of any child modules being loaded.

Mike Graham also does a similar thing but he just sets all the route config up front (using a "level" variable on the route config to determine the menu heirarchy):

Aurelia: child router routes display in "main" nav-bar and child view in app.html <router-view> element?

The disadvantage to that approach is that you need to know about submodules ahead of time in order to configure the router. (part of the power of child routers is that they can just register at runtime and can be "dropped in" without any config anywhere else in the hosting app - this negates that advantage)

The disadvantage of the aforementioned approach is that you can't really generate route hrefs using the router easily (since it uses the parent to figure out what the relative href is) and you end up having to build the navmodel yourself.

I solved this problem with a ValueConverter. This is only for two levels, but with a little change it could support more.

Routes - settings.parentMenu defines under which menu it will appear.

export class App {
  configureRouter(config, router) {
    config.title = 'Aurelia';
    config.map([
      { route: ['', 'welcome'],   name: 'welcome',          moduleId: 'welcome',    nav: true, title: 'Welcome',   settings: { icon : 'fa-th-large'} },
      { route: '#',               name: 'admin',            moduleId: 'admin',      nav: true, title: 'Admin',     settings: { icon : 'fa-user' } },
      { route: 'admin/templates', name: 'admin-templates',  moduleId: 'users/users',  nav: true, title: 'Templates', settings: { parentMenu: 'Admin'} }
    ]);
    this.router = router;
  }
}

subMenu.js - Groups the submenus under the parent

export class SubMenuValueConverter {
    toView(routerMenuItems) {
        var menuItems = [];
        routerMenuItems.forEach(function (menutItem) {
            if (menutItem.settings.parentMenu) {
                // Submenu children
                var parent = menuItems.find(x => x.title == menutItem.settings.parentMenu);
                // If it doesn't exist, then something went wrong, so not checking 
                parent.children.push(menutItem);                   
            } else {
                 // Just insert.  It should not be there multiple times or it's a bad route
                menuItems[menutItem] = menuItems[menutItem] || [];
                // Create empty children
                menutItem.children = [];
                menuItems.push(menutItem);
            }
        });

        return menuItems;
    }
}

nav-bar.html - pipe the router.navigation into the subMenu value converter and then check for children when binding the submenu.

<template bindable="router">
  <require from="./subMenu"></require>
  <nav role="navigation">
    <li repeat.for="row of router.navigation | subMenu" class="${row.isActive ? 'active' : ''}">
        <a href="${row.children.length == 0 ? row.href : 'javascript:void(0);'}">
        <i class="fa ${row.settings.icon}"></i>
        <span class="nav-label">${row.title}</span>
        </a>

        <ul if.bind="row.children.length > 0" class="nav nav-second-level">
            <li repeat.for="sub of row.children" class="${sub.isActive ? 'active' : ''}">
                <a href.bind="sub.href">
                    <i class="fa ${subrow.settings.icon}"></i>
                    <span class="nav-label">${sub.title}</span>
                </a>
            </li>
        </ul>
    </li>
  </nav>
</template>

Whether this is the correct way to do it I don't know, but it's the least "hacky" way I found. If you are using child routes then this won't work unless you inject the child routers into your app.js (or wherever you define your routes) and calling configureRouter and passing in the main router's config. I found that this registered all the routes on the main router, although it seems really bad to me.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!