Skip to content

Instantly share code, notes, and snippets.

Created May 12, 2016 13:29
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save anonymous/677f5ca6a20b315a10d77d224be2cebb to your computer and use it in GitHub Desktop.
Save anonymous/677f5ca6a20b315a10d77d224be2cebb to your computer and use it in GitHub Desktop.
Aurelia router with layouts
<template>
<section class="au-animate">
<div class="well">
<content select="#navigation"></content>
</div>
<div>
<content select="#content"></content>
</div>
</section>
</template>
<template>
<require from="nav-bar.html"></require>
<require from="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css"></require>
<nav-bar router.bind="router"></nav-bar>
<div class="page-host" style="margin-top:50px">
<layout-router-view layout-view="main-layout.html"></layout-router-view>
</div>
</template>
export class App {
configureRouter(config, router) {
config.title = 'Aurelia';
config.map([
{ layoutView: 'main-layout.html', route: ['', 'welcome'], name: 'welcome', moduleId: 'welcome', nav: true, title: 'Welcome' },
{ layoutView: 'alt-layout.html', route: 'welcome2', name: 'welcome2', moduleId: 'welcome', nav: true, title: 'Welcome Alt Layout' },
]);
this.router = router;
}
}
<!doctype html>
<html>
<head>
<title>Aurelia</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css"></link>
<link rel="stylesheet" href="styles.css"></link>
</head>
<body aurelia-app="main">
<h1>Loading...</h1>
<script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/0.19.6/system.js"></script>
<script src="https://rawgit.com/aurelia-ui-toolkits/aurelia-kendoui-bundles/0.3.15/config2.js"></script>
<script>
System.import('aurelia-bootstrapper');
</script>
</body>
</html>
import {Container, inject} from 'aurelia-dependency-injection';
import {ViewSlot, ViewLocator, customElement, noView, BehaviorInstruction, bindable, CompositionTransaction, CompositionEngine} from 'aurelia-framework';
import {Router} from 'aurelia-router';
import {Origin} from 'aurelia-framework';
import {DOM} from 'aurelia-framework';
import {_ContentSelector} from 'aurelia-framework';
class SwapStrategies {
// animate the next view in before removing the current view;
before(viewSlot, previousView, callback) {
let promise = Promise.resolve(callback());
if (previousView !== undefined) {
return promise.then(() => viewSlot.remove(previousView, true));
}
return promise;
}
// animate the next view at the same time the current view is removed
with(viewSlot, previousView, callback) {
let promise = Promise.resolve(callback());
if (previousView !== undefined) {
return Promise.all([viewSlot.remove(previousView, true), promise]);
}
return promise;
}
// animate the next view in after the current view has been removed
after(viewSlot, previousView, callback) {
return Promise.resolve(viewSlot.removeAll(true)).then(callback);
}
}
const swapStrategies = new SwapStrategies();
@customElement('layout-router-view')
@noView
@inject(DOM.Element, Container, ViewSlot, Router, ViewLocator, CompositionTransaction, CompositionEngine)
export class LayoutRouterView {
@bindable swapOrder;
@bindable layoutView;
@bindable layoutViewModel;
@bindable layoutModel;
constructor(element, container, viewSlot, router, viewLocator, compositionTransaction, compositionEngine) {
this.element = element;
this.container = container;
this.viewSlot = viewSlot;
this.router = router;
this.viewLocator = viewLocator;
this.compositionTransaction = compositionTransaction;
this.compositionEngine = compositionEngine;
this.router.registerViewPort(this, this.element.getAttribute('name'));
if (!('initialComposition' in compositionTransaction)) {
compositionTransaction.initialComposition = true;
this.compositionTransactionNotifier = compositionTransaction.enlist();
}
}
created(owningView) {
this.owningView = owningView;
}
bind(bindingContext, overrideContext) {
this.container.viewModel = bindingContext;
this.overrideContext = overrideContext;
}
process(viewPortInstruction, waitToSwap) {
let component = viewPortInstruction.component;
let childContainer = component.childContainer;
let viewModel = component.viewModel;
let viewModelResource = component.viewModelResource;
let metadata = viewModelResource.metadata;
let config = component.router.currentInstruction.config;
// layoutInstruction is our layout viewModel
let layoutInstruction = {
viewModel: config.layoutViewModel || this.layoutViewModel,
view: config.layoutView || this.layoutView,
model: config.layoutModel || this.layoutModel,
router: viewPortInstruction.component.router,
childContainer: childContainer,
viewSlot: this.viewSlot
};
return Promise.resolve(this.composeLayout(layoutInstruction))
.then(layoutView => {
let viewStrategy = this.viewLocator.getViewStrategy(component.view || viewModel);
if (viewStrategy) {
viewStrategy.makeRelativeTo(Origin.get(component.router.container.viewModel.constructor).moduleId);
}
return metadata.load(childContainer, viewModelResource.value, null, viewStrategy, true)
.then(viewFactory => {
if (!this.compositionTransactionNotifier) {
this.compositionTransactionOwnershipToken = this.compositionTransaction.tryCapture();
}
viewPortInstruction.layout = layoutView ? layoutView.view || layoutView : undefined;
viewPortInstruction.controller = metadata.create(childContainer,
BehaviorInstruction.dynamic(
this.element,
viewModel,
viewFactory
)
);
if (waitToSwap) {
return;
}
this.swap(viewPortInstruction);
});
});
}
composeLayout(instruction) {
if (instruction.viewModel || instruction.view) {
return this.compositionEngine.compose(instruction);
}
}
swap(viewPortInstruction) {
let work = viewPortInstruction.layout ?
() => { return this.swapWithLayout(viewPortInstruction); } :
() => { return this.swapWithoutLayout(viewPortInstruction); };
viewPortInstruction.controller.automate(this.overrideContext, this.owningView);
if (this.compositionTransactionOwnershipToken) {
return this.compositionTransactionOwnershipToken.waitForCompositionComplete().then(() => {
this.compositionTransactionOwnershipToken = null;
work();
});
}
work();
}
swapWithoutLayout(viewPortInstruction) {
let previousView = this.view;
let swapStrategy;
let viewSlot = this.viewSlot;
swapStrategy = this.swapOrder in swapStrategies
? swapStrategies[this.swapOrder]
: swapStrategies.after;
swapStrategy(viewSlot, previousView, () => {
return Promise.resolve(viewSlot.add(viewPortInstruction.controller.view))
.then(() => {
if (this.compositionTransactionNotifier) {
this.compositionTransactionNotifier.done();
this.compositionTransactionNotifier = null;
}
});
});
this.view = viewPortInstruction.controller.view;
}
swapWithLayout(viewPortInstruction) {
let previousView = this.view;
let swapStrategy;
let layout = viewPortInstruction.layout;
if (layout.contentSelectors.length === 0) throw new Error('No content selector present in layout ' + layout.resources.viewUrl);
let viewSlot = new ViewSlot(layout.contentSelectors[0].anchor, false);
viewSlot.attached();
_ContentSelector.applySelectors(
viewPortInstruction.controller.view,
layout.contentSelectors,
(contentSelector, group) => contentSelector.add(group)
);
swapStrategy = this.swapOrder in swapStrategies
? swapStrategies[this.swapOrder]
: swapStrategies.after;
swapStrategy(viewSlot, previousView, () => {
return Promise.resolve(viewSlot.add(viewPortInstruction.controller.view))
.then(() => {
if (this.compositionTransactionNotifier) {
this.compositionTransactionNotifier.done();
this.compositionTransactionNotifier = null;
}
});
});
this.view = viewPortInstruction.controller.view;
}
}
<template>
<section class="au-animate">
<div class="container-fluid">
<div class="row">
<div class="col-xs-3">
<content select="#navigation"></content>
</div>
<div class="col-xs-9">
<content select="#content"></content>
</div>
</div>
</div>
</section>
</template>
//import 'bootstrap';
export function configure(aurelia) {
aurelia.use
.standardConfiguration()
.developmentLogging();
aurelia.use.globalResources('layout-router-view');
aurelia.use.plugin('aurelia-animator-css');
aurelia.start().then(() => aurelia.setRoot());
}
<template bindable="router">
<nav class="navbar navbar-default navbar-fixed-top" role="navigation">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#skeleton-navigation-navbar-collapse">
<span class="sr-only">Toggle Navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">
<i class="fa fa-home"></i>
<span>${router.title}</span>
</a>
</div>
<div class="collapse navbar-collapse" id="skeleton-navigation-navbar-collapse">
<ul class="nav navbar-nav">
<li repeat.for="row of router.navigation" class="${row.isActive ? 'active' : ''}">
<a data-toggle="collapse" data-target="#skeleton-navigation-navbar-collapse.in" href.bind="row.href">${row.title}</a>
</li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li class="loader" if.bind="router.isNavigating">
<i class="fa fa-spinner fa-spin fa-2x"></i>
</li>
</ul>
</div>
</nav>
</template>
body {
margin: 0;
}
.splash {
text-align: center;
margin: 10% 0 0 0;
box-sizing: border-box;
}
.splash .message {
font-size: 72px;
line-height: 72px;
text-shadow: rgba(0, 0, 0, 0.5) 0 0 15px;
text-transform: uppercase;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
}
.splash .fa-spinner {
text-align: center;
display: inline-block;
font-size: 72px;
margin-top: 50px;
}
.page-host {
position: absolute;
left: 0;
right: 0;
top: 50px;
bottom: 0;
overflow-x: hidden;
overflow-y: auto;
}
@media print {
.page-host {
position: absolute;
left: 10px;
right: 0;
top: 50px;
bottom: 0;
overflow-y: inherit;
overflow-x: inherit;
}
}
section {
margin: 0 20px;
}
.navbar-nav li.loader {
margin: 12px 24px 0 6px;
}
.pictureDetail {
max-width: 425px;
}
/* animate page transitions */
section.au-enter-active {
-webkit-animation: fadeInRight 1s;
animation: fadeInRight 1s;
}
div.au-stagger {
/* 50ms will be applied between each successive enter operation */
-webkit-animation-delay: 50ms;
animation-delay: 50ms;
}
.card-container.au-enter {
opacity: 0;
}
.card-container.au-enter-active {
-webkit-animation: fadeIn 2s;
animation: fadeIn 2s;
}
.card {
overflow: hidden;
position: relative;
border: 1px solid #CCC;
border-radius: 8px;
text-align: center;
padding: 0;
background-color: #337ab7;
color: rgb(136, 172, 217);
margin-bottom: 32px;
box-shadow: 0 0 5px rgba(0, 0, 0, .5);
}
.card .content {
margin-top: 10px;
}
.card .content .name {
color: white;
text-shadow: 0 0 6px rgba(0, 0, 0, .5);
font-size: 18px;
}
.card .header-bg {
/* This stretches the canvas across the entire hero unit */
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 70px;
border-bottom: 1px #FFF solid;
border-radius: 6px 6px 0 0;
}
.card .avatar {
position: relative;
margin-top: 15px;
z-index: 100;
}
.card .avatar img {
width: 100px;
height: 100px;
-webkit-border-radius: 50%;
-moz-border-radius: 50%;
border-radius: 50%;
border: 2px #FFF solid;
}
/* animation definitions */
@-webkit-keyframes fadeInRight {
0% {
opacity: 0;
-webkit-transform: translate3d(100%, 0, 0);
transform: translate3d(100%, 0, 0)
}
100% {
opacity: 1;
-webkit-transform: none;
transform: none
}
}
@keyframes fadeInRight {
0% {
opacity: 0;
-webkit-transform: translate3d(100%, 0, 0);
-ms-transform: translate3d(100%, 0, 0);
transform: translate3d(100%, 0, 0)
}
100% {
opacity: 1;
-webkit-transform: none;
-ms-transform: none;
transform: none
}
}
@-webkit-keyframes fadeIn {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
@keyframes fadeIn {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
<template>
<section id="navigation">
<h2>Nav</h2>
<ul class="nav nav-stacked nav-pills">
<li class="active"><a href="#">Home</a></li>
<li><a href="#">Home</a></li>
<li><a href="#">Home</a></li>
<li><a href="#">Home</a></li>
<li><a href="#">Home</a></li>
</ul>
</section>
<section id="content">
<h2>${heading}</h2>
<form role="form" submit.delegate="submit()">
<div class="form-group">
<label for="fn">First Name</label>
<input type="text" value.bind="firstName" class="form-control" id="fn" placeholder="first name">
</div>
<div class="form-group">
<label for="ln">Last Name</label>
<input type="text" value.bind="lastName" class="form-control" id="ln" placeholder="last name">
</div>
<div class="form-group">
<label>Full Name</label>
<p class="help-block">${fullName | upper}</p>
</div>
<button type="submit" class="btn btn-default">Submit</button>
</form>
</section>
</template>
//import {computedFrom} from 'aurelia-framework';
import {activationStrategy} from 'aurelia-router';
export class Welcome {
heading = 'Welcome to the Aurelia Navigation App!';
firstName = 'John';
lastName = 'Doe';
previousValue = this.fullName;
//Getters can't be directly observed, so they must be dirty checked.
//However, if you tell Aurelia the dependencies, it no longer needs to dirty check the property.
//To optimize by declaring the properties that this getter is computed from, uncomment the line below
//as well as the corresponding import above.
//@computedFrom('firstName', 'lastName')
get fullName() {
return `${this.firstName} ${this.lastName}`;
}
submit() {
this.previousValue = this.fullName;
alert(`Welcome, ${this.fullName}!`);
}
canDeactivate() {
if (this.fullName !== this.previousValue) {
return confirm('Are you sure you want to leave?');
}
}
determineActivationStrategy() {
return activationStrategy.replace;
}
}
export class UpperValueConverter {
toView(value) {
return value && value.toUpperCase();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment