How to define and render submenu-items, using Aure

2019-04-10 16:25发布

In an Aurelia app, I've defined a simple route like this:

configureRouter(config: RouterConfiguration, router: Router) {
    config.title = 'Marino';
    config.map([
        { route: ['', 'home'], name: 'home', moduleId: './pages/home/home', nav: true, title: 'Home' },
        { route: 'colors', name: 'colors', moduleId: './pages/colors/overview', nav: true, title: 'Colors' }
    ]);

    this.router = router;
}

This works perfectly as all the examples mention, by implementing repeat.for and href.bind like this:

<ul class="main-navigation">
    <li repeat.for="row of router.navigation" class="${row.isActive ? 'active' : ''}">
        <a class="btn btn-primary" href.bind="row.href">${row.title}</a> 
    </li>
</ul>

The challenge in my scenario is that I want to dynamically render routes with submenu-items to this menu as well. Something like this:

<ul class="main-navigation">

    <!-- the regular 'regular' menu and works just fine -->
    <li repeat.for="row of router.navigation" class="${row.isActive ? 'active' : ''}">
        <a class="btn btn-primary" href.bind="row.href">${row.title}</a> 
    </li>

    <!-- 
        below is the pickle; a different kind of element (non-clickable),
        but with child elements
    -->

    <li class="main-navigation-dropdown">
        <a class="btn btn-primary">Menu with Submenu-items</a> 
        <div class="horizontal-dropdown-menu">
            <a class="btn btn-primary sideline">Submenu 1</a> 
            <a class="btn btn-primary sideline">Submenu 2</a> 
            <a class="btn btn-primary sideline">Submenu 3</a> 
        </div>
    </li>                            
</ul>

What puzzles me are two things:

  1. How do I properly define the submenu-items in the route config?
  2. How can I conditionally render each route as either a regular (clickable) route, or as a non-clickable item with submenu's?

I've looked in the RouteConfig docs but can't seem to find any info on 'nested' subroutes. The Aurelia Getting Started does provide info about child routes, but all the samples seem to me to be related to displaying "other", or second menu's on another component.

I'm sure it's quite trivial, but I just can't seem to get a fix on it.

2条回答
甜甜的少女心
2楼-- · 2019-04-10 16:41

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.

查看更多
ゆ 、 Hurt°
3楼-- · 2019-04-10 17:04

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.

查看更多
登录 后发表回答