简体   繁体   中英

Aurelia: Easy way to create nested/multi level navigation menu

I need to create a multilevel navigation menu. The contents of the menu vary depending on the user. I plan on pulling the collection of navigation items that can contain an array of child items via a service that will return the data as JSON. Every example of navigation/routing I've seen uses static routes or single level menus. I have read a bit about child routing but that does not seem to be what I need. The only thing I can think of is to create a custom navigation element who's model will register the routes in a plain array using the data in the navigation items collection. I would then use this collection to render the HTML instead of using router as it contains the hierarchical information. Is there an easier way to do this. This is the first JS framework I have worked with so I'm trying to get up to speed.

This is fairly easy, actually. You'll just add a property to the route's settings object. You can name it whatever you want. It'll have a collection of menu items for the sub menu. Just use that to build the sub-menu.

Here's an example: https://gist.run?id=beb5ba9155fdb2ccefcf78676879b1ca

app.html

<template>
  <ul>
    <li repeat.for="row of router.navigation" class="${row.isActive ? 'active' : ''}">
      <a href.bind="row.href">${row.title}</a>
      <ul>
        <li repeat.for="sub of row.settings.subRoutes">
          <a route-href="route.bind: row.config.name; params.bind: sub.params">${sub.name}</a>
        </li>
      </ul>
    </li>
  </ul>

  <div class="page-host">
    <router-view></router-view>
  </div>
</template>

app.js

import {activationStrategy} from 'aurelia-router';

export class App {
  configureRouter(config, router) {
    config.title = 'Aurelia';
    config.map([
      { 
        route: ['', 'one'], 
        name: 'one',      
        moduleId: 'one',      
        nav: true, 
        title: 'Page 1',
        activationStrategy: activationStrategy.invokeLifecycle,
        settings: {
          subRoutes: [
            {
              name: 'Sub-choice 1',
              params: {
                'foo': 'bar'
              }
            },
            {
              name: 'Sub-choice 2',
              params: {
                'foo': 'two'
              }
            }
          ]
        }
      },
      { route: 'two',         name: 'two',        moduleId: 'two',        nav: true, title: 'Page 2' }
    ]);

    this.router = router;
  } 
}

one.html

<template>
  Page One<br />
  Params:<br />
  <pre><code>${params}</code></pre>
</template>

one.js

export class One {
  activate(params) {
    this.params = JSON.stringify(params);
  }
}

The parameters you pass can be anything you like. For example, they could be the information on what route the should be activated on a child router on the route you're going to. You could call router.navigate... in the activate method of the page ( one.js in the example below) to navigate to the correct route on the child router. You really can do whatever you want, since you can put any old thing you'd like on that settings object.

I have solved similar problem in following way:

  1. I have simple navigation tree retrieved from the server
  2. Then I walk the tree and convert all the navigation items to the aurelia routes, actually flatten tree to array, so every route will have settings property like: {id: 2, parentId: 1, level: 1} at that moment
  3. Then I pass this flat array to config.map() and create a new tree object using items from router.navigation (remember: I saved id and parentId in settings ) on router.ensureConfigured().then(_ => {createNavTree(router.navigation)})
  4. Use output from createNavTree(...) to render list items. ( level is used just for some styling)
  5. So in general I create flat router.navigation array and use its items to create own tree structure to render the navigation

Also I listen for changes in router.currentInstruction to track menu collapsing when subitem is navigated using injected ObserverLocator .

`import {ObserverLocator} from 'aurelia-framework';`
...
this.routerCurrentInstructionSub = this.observerLocator
  .getObserver(router, 'currentInstruction')
  .subscribe((newValue, oldValue) => { ... });

Note that subscription should be released when not needed to avoid memory leaks this.routerCurrentInstructionSub.dispose();

The main benefit of this solution - is that you get full-featured routes, so you can render tree subitems as active while navigating.

Also it doesn't restrict using sub routers for sub items.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM