aurelia / router

A powerful client-side router.
MIT License
120 stars 115 forks source link

Feature Request: Better support for Layouts #233

Closed ghost closed 8 years ago

ghost commented 8 years ago

Using the default router schema with the default App view, but binding doesn't seem to work between the App template and the view controller when binding to a property on the router?

<template>
  <div>${router.title}</div>
  <router-view></router-view>
</template>
export class App {
  configureRouter(config, router) {
    config.map([
      {
        route: ['', 'home'],
        name: 'home',
        moduleId: './components/home/index',
        nav: false,
        title: "Hello World"
      },
    ]);
    this.router = router;
  }
}
ghost commented 8 years ago

Ok, figuring out stuff, but there does seem to be a hole in the router here somewhere. In the App controller, I am configuring my routes as per the docs, but in the template, I need to have conditional logic based on flags on the current route:

<template>
  <router-view if.bind="router.public" class="public-layout"></router-view>
  <router-view if.bind="router.secure class="secure-layout"></router-view>
</template>
export class App {
  configureRouter(config, router) {
    config.map([
      {
        route: ['', 'home'],
        name: 'home',
        moduleId: './components/home/index',
        nav: false,
        public: true
      },
    ]);
    this.router = router;
  }
}

The real scenario is not this straight-forward because the public and secure layouts require more markup than just the router-view but this is the gist of it.

I am just trying to setup what is a very normal app scenario. One set of public routes (with a layout) for account screens like login, register, and then another layout that has as sidebar, header, etc.

In an actual view, I can have the activate function which gives me the routeConfig that tells me the current route:

  activate(params, routeConfig, navigationInstructions) {
    this.router = routeConfig;
  }

This will give me the properties I put on the route, but this means I have to duplicate all my layout markup in every view. I am not sure what I am asking for, the fact that child routers have to be bound to a sub-view with another nested router-view element is very difficult to work with. With Angular's UI-Router there is a separation between states and url's and view templates that is much more flexible.

ghost commented 8 years ago

One more piece of information, in the activate callback on the App controller, the routeConfig parameter is undefined.

  activate(params, routeConfig, navigationInstructions) {
    this.route = routeConfig; //<= undefined
    console.log("App activated", );
  }

In a child router controller, the activate fires, and the routeConfig parameter is populated, but if you try to bind anything in the template to the route property is is ignored:

<template>
  <!-- doesn't get rendered even if this.route.public is true -->
  <router-view if.bind="route.public"></router-view>
</template>
  activate(params, routeConfig, navigationInstructions) {
    this.route = routeConfig; //<= works
    console.log("Child router activated", );
  }

From what I can see, it is impossible to have any dynamic rendering behavior in the root App template/controller, or a child router template/controller based on the current route.

EisenbergEffect commented 8 years ago

There are lots of ways to solve this issue. For example, you could use the compose element to dynamically render based on data. That's its main purpose. To find the exact solution, I would need more information on your scenario. The best place for questions like these is our gitter channel though. We use GitHub primarily for tracking bugs and feature enhancements.

ghost commented 8 years ago

I had read the docs on compose, but I guess I didn't really get how to use it. This article has helped - http://www.sitepoint.com/composition-aurelia-report-builder/. What I really want is something like what they have on the MeteorJS Iron Router - the ability to specify the layout as part of a route. This is what it would look like:

export class App {
  configureRouter(config, router) {
    config.map([
      {
        route: ['', 'home'], name: 'home',
        layout: './layouts/public',
        viewPorts: {
          default: { moduleId: './components/home/index' },
        }
      },
      {
        route: ['someFeature'], name: 'someFeature',
        layout: './layouts/app',
        viewPorts: {
          default: { moduleId: './components/someFeature/index' },
          sidebar: { moduleId: './common/sidebar' },
          header: { moduleId: './common/header' }
        }
      },
    ]);
    this.router = router;
  }
}

Basically, on a route, I want to be able to define different layout templates, and then fill the named views on the same route, without having to use a child router with a nested template. Based on the blog post I linked, it seems like I might be able to wire this up with layouts view controller and a compose block. I will work on it. Thanks for the suggestion.

ghost commented 8 years ago

FYI - Thx, I figured it out. I just needed to get my head around the potential for compose. I was able to create layout view-models with compose sections, and my custom version of 'viewPorts' on the route to bind to them achieving what I needed.