Skip to content

Commit

Permalink
Merge branch 'master' into explicit-navigation-flags
Browse files Browse the repository at this point in the history
  • Loading branch information
EisenbergEffect authored Feb 1, 2017
2 parents 95df138 + 164ec3f commit f9cf4ea
Show file tree
Hide file tree
Showing 3 changed files with 219 additions and 38 deletions.
234 changes: 198 additions & 36 deletions doc/article/en-US/router-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -566,16 +566,20 @@ A pipeline step must be an object that contains a `run(navigationInstruction, ne

## [Rendering View Ports](aurelia-doc://section/9/version/1.0.0)

Every instance of a `router-view` custom element essentially defines a "view port". When you give a `router-view` a name, you can refer to it in the `viewPorts` property of the route configuration in your javascript. The value of a `viewPorts` property is an object where each property name is the name of a view port (ie, `router-view`) and each value is the `moduleId` destination of the route. Thus you can specify any number of view ports on a single route configuration.

> Info
> If you don't name a router-view, it will be available under the name 'default'.
> If you don't name a `router-view`, it will be available under the name 'default'.
Following is an example of the use of view ports:

<code-listing heading="app.html">
<source-code lang="HTML">
<template>
<div class="page-host">
<div>
<router-view name="left"></router-view>
</div>
<div class="page-host">
<div>
<router-view name="right"></router-view>
</div>
</template>
Expand Down Expand Up @@ -607,97 +611,244 @@ A pipeline step must be an object that contains a `run(navigationInstruction, ne
</source-code>
</code-listing>

> Info
> In addition to the `moduleId`, you can also specify a "layout" in the configuration of a view port. See the discussion of Layouts elsewhere in this document.
## [Layouts](aurelia-doc://section/10/version/1.0.0)

Similar to MVC-style master/layout pages, Aurelia allows you to use a "layout" view like an MVC "master template" for a set of views.

The set of views subject to being part of a layout is defined in Aurelia as a set of views referenced by one or more routes in a router configuration. There are two ways to associate a layout with routes. The first is via HTML, the second is via view model code.

> Info
> Specifying layout on the `<router-view>` element will set the default layout for all routes.
> We're going to be a little sloppy here in terminology. Technically, routes refer to "moduleIds", not
"views". Since the router resolves a moduleId to a view, indirectly the router does reference a view. It is easy to picture a view visually contained within a layout, so in this topic to we'll refer to views referenced by a route, not modules.

Similar to MVC-style master/layout pages, Aurelia allows configuration of multiple layouts. Here are the properties for creating layouts:
We'll look at using HTML first. We know that the `router-view` custom HTML element is always associated with a set of one or more views referenced in a router configuration given in its parent view's view model. By associating a layout with a `router-view` one can thus associate a layout with the same set of views with which the `router-view` is associated.

* `layoutView` property on a route object - specifies the layout view to use for the route.
* `layoutViewModel` property on a route object - specifies the view model to use with the layout view.
* `layoutModel` property on a route object - specifies the model parameter to pass to the layout view-model's activate function.
To specify a layout on the `router-view` custom element, we use the following attributes:

* `layout-view` - specifies the file name (with path) of the layout view to use.
* `layout-view-model` - specifies the moduleId of the view model to use with the layout view.
* `layout-model` - specifies the model parameter to pass to the layout view model's `activate` function.

> Info
> All of these layout attributes are bindable.
Following is an example of HTML in which we specify that we want all destination views reachable under the `router-view` to be laid-out inside a view with file name `layout.html`, located in the same directory as the view contianing the `router-view`:

<code-listing heading="app.html">
<source-code lang="HTML">
<template>
<div class="page-host">
<router-view layout-view="views/layout-default.html"></router-view>
<div>
<router-view layout-view="layout.html"></router-view>
</div>
</template>
</source-code>
</code-listing>

Here is the layout view itself:

<code-listing heading="layout.html">
<source-code lang="HTML">
<template>
<div class="left-content">
<slot name="aside-content"></slot>
<slot name="left-content"></slot>
</div>
<div class="right-content">
<slot name="main-content"></slot>
<slot name="right-content"></slot>
</div>
</template>
</source-code>
</code-listing>

<code-listing heading="module.html">
And here we define a view that we want to appear within the layout:

<code-listing heading="home.html">
<source-code lang="HTML">
<template>
<div slot="main-content">
<p>I'm content that will show up on the right.</p>
<div slot="left-content">
<p>${leftMessage}.</p>
</div>
<div slot="aside-content">
<p>I'm content that will show up on the left.</p>
<div slot="right-content">
<p>${rightMessage}.</p>
</div>
<div>This will not be displayed in the layout because it is not contained in any named slot referenced by the layout.</div>
</template>
</source-code>
</code-listing>

<code-listing heading="home${context.language.fileExtension}">
<source-code lang="ES 2015/2016">
export class Home {
constructor() {
this.leftMessage = "I'm content that will show up on the left";
this.rightMessage = "I'm content that will show up on the right";
}
}
</source-code>
<source-code lang="TypeScript">
export class Home {
constructor() {
this.leftMessage = "I'm content that will show up on the left";
this.rightMessage = "I'm content that will show up on the right";
}
}
</source-code>
</code-listing>

Observe how we use the `slot` mechanism for associating parts of the layout to parts of the views that are to be contained within the layout. (Happy for developers, this is conveniently the same mechanism and syntax we use in Aurelia when providing content to custom elements.)

Now we just have to define the route configuration that will be associated with the `router-view`:

<code-listing heading="app${context.language.fileExtension}">
<source-code lang="ES 2015/2016">
export class App {
configureRouter(config, router) {
config.title = 'Aurelia';
var model = {
id: 1
};
configureRouter(config, router){
config.map([
{ route: 'home', name: 'home', moduleId: 'home/index' },
{ route: 'login', name: 'login', moduleId: 'login/index', layoutView: 'views/layout-login.html' },
{ route: 'users', name: 'users', moduleId: 'users/index', layoutViewModel: 'views/model', layoutModel: model }
{ route: '', name: 'home', moduleId: 'home' }
]);

this.router = router;
}
}
</source-code>
<source-code lang="TypeScript">
import {Redirect, NavigationInstruction, RouterConfiguration, Router} from 'aurelia-router';
import {RouterConfiguration, Router} from 'aurelia-router';

export class App {
configureRouter(config: RouterConfiguration, router: Router): void {
config.title = 'Aurelia';
var model = {
id: 1
};
configureRouter(config: RouterConfiguration, router: Router): void {){
config.map([
{ route: 'home', name: 'home', moduleId: 'home/index' },
{ route: 'login', name: 'login', moduleId: 'login/index', layoutView: 'views/layout-login.html' },
{ route: 'users', name: 'users', moduleId: 'users/index', layoutViewModel: 'views/model', layoutModel: model }
{ route: '', name: 'home', moduleId: 'home' }
]);

this.router = router;
}
}
</source-code>
</code-listing>

## [Internationalizing Titles](aurelia-doc://section/11/version/1.0.0)
Thus when we navigate to the module "home" we find that it is laid-out as desired inside the layout view.

Note there is nothing different about the above route configuration with or without the layout. It may reference any number of views that would all be included by default in the layout.

So that is how we use HTML to associate a layout view with a set of views referenced in a router configuration.

We can also associate layouts with route configurations using code in our view model. Suppose we like what we've done above, but we have a couple views that we would like to associate with a different layout and would thus like to partially override the configuration given in the HTML. The following code is an example of how we can do that:

<code-listing heading="app${context.language.fileExtension}">
<source-code lang="ES 2015/2016">
export class App {
configureRouter(config, router){
config.map([
{ route: '', name: 'home', moduleId: 'home' },
{ route: 'login', name: 'login', moduleId: 'login/index', layoutView: 'layout-login.html' },
{ route: 'users', name: 'users', moduleId: 'users/index', layoutModel: 'layout-users', layoutViewModel: { access: "admin" } }
]);

this.router = router;
}
}
</source-code>
<source-code lang="TypeScript">
import {RouterConfiguration, Router} from 'aurelia-router';

export class App {
configureRouter(config: RouterConfiguration, router: Router): void {){
config.map([
{ route: '', name: 'home', moduleId: 'home' },
{ route: 'login', name: 'login', moduleId: 'login/index', layoutView: 'layout-login.html' },
{ route: 'users', name: 'users', moduleId: 'users/index', layoutModel: 'layout-users', layoutViewModel: { access: "admin" } }
]);

this.router = router;
}
}
</source-code>
</code-listing>

The above example will assign different layouts to the "login" and "users" views, overriding the HTML while leaving "home" to remain as configured in the HTML. Noticing we're using camel-cased property names here, unlike in the HTML.

You can also specify a layout in the `viewPorts` configuration of a route. See a simple example, below:

<code-listing heading="app.html">
<source-code lang="HTML">
<template>
<div>
<router-view name="myRouterView"></router-view>
</div>
</template>
</source-code>
</code-listing>

<code-listing heading="app${context.language.fileExtension}">
<source-code lang="ES 2015/2016">
export class App {
configureRouter(config, router){
config.map([
{ route: '', name: 'home', viewPorts: { myRouterView: { moduleId: 'home', layoutView: 'default.html' } } }
]);

this.router = router;
}
}
</source-code>
<source-code lang="TypeScript">
import {RouterConfiguration, Router} from 'aurelia-router';

export class App {
configureRouter(config: RouterConfiguration, router: Router): void {){
config.map([
{ route: '', name: 'home', viewPorts: { myRouterView: { moduleId: 'home', layoutView: 'default.html' } } }
]);

this.router = router;
}
}
</source-code>
</code-listing>

## [View Swapping and Animation](aurelia-doc://section/11/version/1.0.0)

When the Aurelia router navigates from one view to another, we refer to this as "swapping" one view for another. Aurelia gives us an optional set of strategies dictating how a swap proceeds, or more specifically, how animation plays out during the swap. We refer to these strategies more precisely as the "swap order".

> Info
> If there is no animation defined, then swap-order has no visible impact.
You can apply a swap strategy to one or more routes by applying the `swap-order` attribute to a `router-view` custom HTML element. The strategy will then be applied in any transition between two views accessible under the `router-view`.

> Info
> `swap-order` is bindable.
The following swap order strategies are available:

* before - animate the next view in before removing the current view
* with - animate the next view at the same time the current view is removed
* after - animate the next view in after the current view has been removed (the default)

Here is an example of setting the swap order strategy on a `router-view`:

<code-listing heading="swap-order">
<source-code lang="HTML">
<template>
<div>
<router-view swap-order="before"></router-view>
</div>
</template>
</source-code>
</code-listing>


## [Internationalizing Titles](aurelia-doc://section/12/version/1.0.0)

If your application targets multiple cultures or languages, you probably want to translate your route titles. The `Router` class has a `transformTitle` property that can be used for this. It is expected to be assigned a function that takes the active route's title as a parameter and then returns the translated title. For example, if your app uses `aurelia-i18n`, its routes' titles would typically be set to some translation keys
and the `AppRouter`'s `transformTitle` would be configured in such a way that the active route's title is translated using the `I18N`'s `tr` method:
and the `AppRouter`'s `transformTitle` would be configured in such a way that the active route's title is translated using the `I18N`'s `tr` method. Additionally you can listen to a custom event published by the I18N service to react on locale changes using the EventAggregator:

<code-listing heading="src/main${context.language.fileExtension}">
<source-code lang="ES 2015/2016">
import Backend from 'i18next-xhr-backend';
import {AppRouter} from 'aurelia-router';
import {EventAggregator} from 'aurelia-event-aggregator';

export function configure(aurelia) {
aurelia.use
Expand All @@ -714,6 +865,11 @@ and the `AppRouter`'s `transformTitle` would be configured in such a way that th
}).then(() => {
const router = aurelia.container.get(AppRouter);
router.transformTitle = title => i18n.tr(title);
const eventAggregator = aurelia.container.get(EventAggregator);
eventAggregator.subscribe('i18n:locale:changed', () => {
router.updateTitle();
});
});
});

Expand All @@ -723,6 +879,7 @@ and the `AppRouter`'s `transformTitle` would be configured in such a way that th
<source-code lang="TypeScript">
import Backend from 'i18next-xhr-backend';
import {AppRouter} from 'aurelia-router';
import {EventAggregator} from 'aurelia-event-aggregator';

export function configure(aurelia) {
aurelia.use
Expand All @@ -739,6 +896,11 @@ and the `AppRouter`'s `transformTitle` would be configured in such a way that th
}).then(() => {
const router = aurelia.container.get(AppRouter);
router.transformTitle = title => i18n.tr(title);
const eventAggregator = aurelia.container.get(EventAggregator);
eventAggregator.subscribe('i18n:locale:changed', () => {
router.updateTitle();
});
});
});

Expand Down
5 changes: 4 additions & 1 deletion src/app-router.js
Original file line number Diff line number Diff line change
Expand Up @@ -199,11 +199,12 @@ function processResult(instruction, result, instructionCount, router) {
function resolveInstruction(instruction, result, isInnerInstruction, router) {
instruction.resolve(result);

let eventArgs = { instruction, result };
if (!isInnerInstruction) {
router.isNavigating = false;
router.isExplicitNavigation = false;
router.isExplicitNavigationBack = false;
let eventArgs = { instruction, result };

let eventName;

if (result.output instanceof Error) {
Expand All @@ -218,6 +219,8 @@ function resolveInstruction(instruction, result, isInnerInstruction, router) {

router.events.publish(`router:navigation:${eventName}`, eventArgs);
router.events.publish('router:navigation:complete', eventArgs);
} else {
router.events.publish('router:navigation:child:complete', eventArgs);
}

return result;
Expand Down
18 changes: 17 additions & 1 deletion src/interfaces.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ interface RouteConfig {
* The view ports to target when activating this route. If unspecified, the target moduleId is loaded
* into the default viewPort (the viewPort with name 'default'). The viewPorts object should have keys
* whose property names correspond to names used by <router-view> elements. The values should be objects
* specifying the moduleId to load into that viewPort.
* specifying the moduleId to load into that viewPort. The values may optionally include properties related to layout:
* `layoutView`, `layoutViewModel` and `layoutModel`.
*/
viewPorts?: any;

Expand Down Expand Up @@ -84,6 +85,21 @@ interface RouteConfig {
*/
activationStrategy?: string;

/**
* specifies the file name of a layout view to use.
*/
layoutView?: string;

/**
* specifies the moduleId of the view model to use with the layout view.
*/
layoutViewModel?: string;

/**
* specifies the model parameter to pass to the layout view model's `activate` function.
*/
layoutModel?: string;

[x: string]: any;
}

Expand Down

0 comments on commit f9cf4ea

Please sign in to comment.