Sei sulla pagina 1di 27

Aurelia For Real World Web Applications

Using the Aurelia Javascript framework to build real world


applications.
Dwayne Charrington
This book is for sale at http://leanpub.com/aurelia-for-real-world-applications
This version was published on 2016-06-13

This is a Leanpub book. Leanpub empowers authors and publishers with the Lean Publishing
process. Lean Publishing is the act of publishing an in-progress ebook using lightweight tools and
many iterations to get reader feedback, pivot until you have the right book and build traction once
you do.
2016 Dwayne Charrington

Tweet This Book!


Please help Dwayne Charrington by spreading the word about this book on Twitter!
The suggested tweet for this book is:
I just bought Aurelia For Real World Applications by @AbolitionOf
The suggested hashtag for this book is #rwaurelia.
Find out what other people are saying about the book by clicking on this link to search for this
hashtag on Twitter:
https://twitter.com/search?q=#rwaurelia

This book is dedicated to my beautiful wife Marie and our son Daedalus. Thank you for putting up
with my long hours and nights when I come to bed late. A special thank you to my employer
Fathom for allowing me to work with Aurelia on a daily basis.
Also, a shout out to various members of the Aurelia core team, Rob Eisenberg (the man behind
Aurelia), Jeremy Danyow, Patrick Walters and others who have helped answer questions and also
the community.

Contents
1. Crash Course In Aurelia . . . . . . . . . . . . . . . . . . .
Conventions . . . . . . . . . . . . . . . . . . . . . . . . . .
Views and Viewmodels . . . . . . . . . . . . . . . . . . .
Overriding Default Conventions . . . . . . . . . . . . . . .
A Viewmodel Without A View Template . . . . . . . . .
A View Template Without A Viewmodel (HTML Partials)
Singleton Classes aka Services . . . . . . . . . . . . . . .
The Basics & Fundamentals . . . . . . . . . . . . . . . . . .
Introduction . . . . . . . . . . . . . . . . . . . . . . . . .
Creating A Viewmodel . . . . . . . . . . . . . . . . . . .
Creating A View . . . . . . . . . . . . . . . . . . . . . .
The Containerless Attribute . . . . . . . . . . . . . . . .
Difference Between Element and DOM.Element . . . . .
Booting Up Aurelia . . . . . . . . . . . . . . . . . . . . . .
Multipage Applications With .setRoot . . . . . . . . . .
Progressive Enhancement Using Enhance . . . . . . . . .
Progressive Enhancement Examples . . . . . . . . . . . .
Browser Support And Polyfills . . . . . . . . . . . . . . .
Plugins . . . . . . . . . . . . . . . . . . . . . . . . . . .
Resources . . . . . . . . . . . . . . . . . . . . . . . . . .
Features . . . . . . . . . . . . . . . . . . . . . . . . . . .
Importing & Loading Components . . . . . . . . . . . . . .
Client-side Logging Using Log Appenders . . . . . . . . . .
Creating A Basic Custom Log Appender . . . . . . . . .
Creating A Server-side Log Appender . . . . . . . . . . .

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

1
1
1
2
2
3
4
6
6
6
7
7
8
8
9
11
12
14
15
16
17
18
19
19
21

1. Crash Course In Aurelia


In this chapter, we will go through the individual components of Aurelia. How views are constructed,
how you write a viewmodel, an understanding on how the binding system works, boot up Aurelia
and loading components.
We will also be detailing how to work with Aurelias console logic functionality to create your own
console loggers, monitoring client side exceptions and more.

Conventions
Aurelia is known as a conventions based framework because out-of-the-box it makes some decisions
for you without needing to be configured. Think of Aurelia like a car, it already knows how to drive
and through some modifications and driving habits, you can make it drive how you want it to drive.
While Aurelia is conventions based, if something does not work the way you want it too, you can
change it. Knowing how Aurelias conventions work is imperative to mastering the framework.
While the default conventions are there to meet about 80% of most needs, sometimes you need to
change how something works.
If you keep reading, you will soon discover that every assumption that Aurelia makes about how
you work can be changed mostly through the use of a decorator or class method, most of the time is
a decorator you import and specify above your viewmodel. If only everything in life was this easy.

Views and Viewmodels


When it comes to rendering UI, you will be writing views and viewmodels most of the time, unless
you work out an entirely new way and if that is the case, then good on you.
By default Aurelia assumes your custom elements, custom attributes and viewmodel classes all have
a paired view template of the same filename but with a .html file extension.
For example if you have a viewmodel which displays a homepage and you call it homepage.js
Aurelia assumes by default that you also have a template file named homepage.html which is the
view template, this is required by default. You can see a simplistic example of this convention below
as well as inside of the Aurelia skeleton applications.
src/homepage.js {lang=js} export class Homepage {
}
src/homepage.html {lang=html} <template> <h1>This template is automatically loaded from
homepage.js</h1> <p>Because default conventions for views are being used, this template
is loaded when homepage.js is loaded.</p> </template>

Crash Course In Aurelia

Overriding Default Conventions


What is the point of having conventions if you cant work around them? As we already discussed,
Aurelia allows you to change its defaults through the use of decorator and class methods. If these
concepts are new to you, dont worry, they are not as complicated as they sound.
A decorator is actually a function that can take the context of the class or function it is being used
with and modify it. So whenever you use a decorator on a viewmodel, you are actually modify the
class via a function.
Changing Which View Template A Viewmodel Loads
Using the getViewStrategy() method inside of our viewmodels, we can inform Aurelia of our
intention to load a template from somewhere else. This allows you to override the default view
and specify what should be loaded instead.
The getViewStrategy() method expects that you will return the full path and name of a view
template to load (including file extension, unless loading views dynamically from the server).
src/homepage.js {lang=js} export class Homepage { getViewStrategy() { return 'a-pagetemplate.html'; } }

Using our above example of a viewmodel called homepage.js we know that by default it will attempt
to look for a view template called homepage.html through the use of the getViewStrategy method
which returns a string, we can tell it to load a completely different template, in this case it is a-pagetemplate.html which is in the same location as homepage.js.
If polluting your class with methods is not your thing, there is also another way you can specify
which view a particular class should use by default in the form of the @useView() class decorator
function.
This has less flexibility and requires you know the view you want to supply upfront (which in some
cases, you might). If you just want to change the template to load from a different location or a
different named template, this decorator might actually be the preferred approach.
import {useView} from 'aurelia-framework';
@useView('templatename.html')
export class MyViewModel {
...
}

A Viewmodel Without A View Template


Sometimes you might want to use an external library like React or Polymer. By default Aurelia
handles a lot of the view binding stuff for you, but you can specify that you do not want Aurelia to
load a view template and that you will handle rendering yourself.

Crash Course In Aurelia

While Aurelia with its good intentions assumes you are happy for it to handle everything in your
application, sometimes you might want to use a library or jQuery plugin which renders some UI for
you or perhaps does not even need a UI, like the nprogress plugin which gives you a loading bar
and requires no view template.
Once again, Aurelia provides a great decorator called @noView() which allows you to specify that a
particular viewmodel has no view template that will be loaded.
Speaking of React, if the thought of using it within Aurelia excites you, later on I will show you how
to use React in Aurelia and we will be using the @noView() decorator to accomplish this.
src/homepage.js {lang=js} import {noView} from aurelia-framework;
@noView() export class Homepage { }
In previous examples Aurelia would attempt to look for homepage.html or whatever specified
template you provide. However, @noView means Aurelia will make no such attempts to load a
template.

A View Template Without A Viewmodel (HTML Partials)


This is one of my favourite things about Aurelia. You can specify view templates which act like
partials and custom elements without even needing a viewmodel. You can even create views
complete with support for bindable properties just in HTML without needing to create a viewmodel
and using @bindable. These are just static HTML files, with one exception being they need to have
their contents contained within opening and closing <template></template elements.
In the starter skeleton there is an example of a viewmodel-less view in the form of the nav-bar
component here. I wont paste the file contents here as it is a big file, but the meat on the bones is
the first line:
<template bindable="router">

The bindable attribute accepts a comma separated listed of bindable attributes. To use this HTML
element, simply require it using syntax like the following:
<require from="nav-bar.html"></require>
<nav-bar router.bind="router"></nav-bar>

Notice the router.bind line, it works just like a regular custom element? The name of the custom
element is the filename, minus the file extension. So our HTML element is nav-bar.html and we
can reference it using <nav-bar></nav-bar>, cool huh?
https://github.com/aurelia/skeleton-navigation/blob/master/skeleton-es2016/src/nav-bar.html

Crash Course In Aurelia

Multiple Properties
Another important thing to know about the bindable property is you can specify multiple
bindable properties using a comma as we briefly mentioned earlier on in this section.
Just to remind you, simply use the decorator and comma separate each bindable like so:
bindable="router,anotherbindable,testprop"

<template bindable="router, user">

<require from="nav-bar.html"></require>
<nav-bar router.bind="router" user.bind="user"></nav-bar>

Not only that but if you are not a fan of custom HTML element tags in your application, you can use
the containerless attribute to make the element just display its view contents minus the custom
element tags, like so:
<require from="nav-bar.html"></require>
<nav-bar router.bind="router" containerless></nav-bar>

Singleton Classes aka Services


In Aurelia every class that is not a custom attribute or custom element is treated as a singleton by
default. A singleton means that your application can load and use a class multiple times and it will
always get the same instance.
By default Aurelia treats all injected user created classes as singletons. If you have worked with
Angular or other frameworks, you might know singletons by another name: services, factories,
providers and whatever.
Even if you dont know what the word means, you most likely have encountered them in the wild
or even written one yourself. Singletons are great for keeping track of state, I use them to track the
currently logged in user.
A basic singleton example
src/user.js {lang=js} export class User { isLoggedIn = false;

Crash Course In Aurelia

userLoggedIn() {
return this.isLoggedIn;
}
login() {
this.isLoggedIn = true;
}
logout() {
this.isLoggedIn = false;
}

}
src/my-viewmodel.js {lang=js} import {inject} from aurelia-framework;
import {User} from ./user;
@inject(User) export class MyViewModel { constructor(user) { this.user = user; }
attached() {
if (this.user.userLoggedIn()) {
console.log('User is logged in');
} else {
console.log('User is not logged in!!');
}
}

}
This is great if you want to create classes that keep track of state and will be shared throughout your
application. However, there might be times when you want an injected class to get a new instance
every time: this is a transient class.
In our example which you can actually try yourself you can see we are creating a user class which
can be imported and then injected. We can then get the currently logged in status of the user and
know the value will be the same for all classes that import it. Remember a singleton is the same
instance that can be used multiple times?
With a really minor tweak and import of the @transient() decorator, we can make a class that gets
injected turn into a transient class that will return a new instance every time, akin to using the new
keyword.
src/user.js {lang=js} import {transient} from aurelia-framework;
@transient() export class User { isLoggedIn = false;

Crash Course In Aurelia

userLoggedIn() {
return this.isLoggedIn;
}
login() {
this.isLoggedIn = true;
}
logout() {
this.isLoggedIn = false;
}

}
src/my-viewmodel.js {lang=js} import {inject} from aurelia-framework;
import {User} from ./user;
@inject(User) export class MyViewModel { constructor(user) { this.user = user; } }
In the last example we made our user class a transient. This means whenever a class imports and
then injects it, a new instance will be created. If you wanted this class to perhaps be some kind of
object factory for creating new user objects, then this would be what you want. However, if you just
wanted to keep track of the current user, we would want a singleton.

The Basics & Fundamentals


Introduction
In this section we will run through the basics of Aurelia, while we will not delve into specifics
(those are for later chapters) we will touch upon generic aspects such as views, viewmodels, working
with the Element instance in a custom attribute or element and a few other things. If you do not
understand everything here, dont worry, later on we deep dive into many of the below mentioned
concepts.
This is a broad and high-level overview of Aurelia itself. The basic conventions that it employs and
what actually makes up an Aurelia app as well.

Creating A Viewmodel
A viewmodel is nothing more than a Javascript class. Through the use of decorator functions, you
can turn a standard viewmodel into a custom attribute, custom element, transient class and more.
This beautiful simplicity by design allows you a context-switching free workflow. The following
code is not Aurelia specific, classes were added into the 2015 version of the Javascript specification.

Crash Course In Aurelia

export class MyViewModel {


}

You should follow a CamelCased naming convention for your classes and your filename should
reflect your class name, with hyphens used to space out each CamelCased word. The above is what
a lot of your Aurelia applications on the viewmodel side will start out like. You would save the above
file as my-view-model.js for example.
The export keyword here is also a specification keyword. It makes the class that we have created
publicly accessible, by exporting it, we can then import and inject this later on.

Creating A View
In Aurelia a view is just a standard HTML file, with one small constraint. All of your views must have
their contents encapsulated by opening and closing HTML <template></template> elements. The
template element is for holding client-side content. It is not rendered by the browser, only parsed for
validity. This signals to the browser that the contents will be parsed and handled in your Javascript
application only and not in the browser.
<template>
My content goes in here.
</template>

Once again, the <template> element is not an Aurelia convention, it is part of the HTML
specification. You can read all about the <template> element here.

The Containerless Attribute


Any Aurelia custom element (including those that come with the framework) can be containerless.
By default if you create a custom element and inspect the page, you will see it in the page and its
contents inside. Similar to creating a DIV in the page and filling it with HTML.
However, there are times when the custom element can cause styling and markup issues. By using
the containerless attribute you can tell Aurelia to render the inner HTML of your custom element
and remove the outer custom HTML tags.
For example, if you are using the router, then you will have one or more <router-view></routerview> tags in your application. These tags signal to Aurelia this is where your loaded route views
will be injected. Navigating around changes the HTML of these elements, but the <router-view>
tags remain. By placing the containerless attribute on the opening tag you are telling Aurelia you
dont want these tags visible in your page.
https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template

Crash Course In Aurelia

The beautiful thing about this attribute is that in my experience, using it even on elements that will
be called or changed later breaks nothing (unless you try and access the element itself from within a
viwmodel). It is purely cosmetic, although there might be underlying issues I have not encountered
yet. You can even use it on in-built elements like the <compose> element if you wanted too.
<template>
<my-custom-element containerless></my-custom-element>
<router-view containerless></router-view>
</template>

Difference Between Element and DOM.Element


If you have prior experience with Aurelia, especially custom elements you might notice in many of
the examples for custom elements we are injecting @inject(DOM.Element) into our class to get the
current element. You probably want to know what the difference is and why you should use it. This
will be quick.
There is no difference between the two, they both do the same thing, however using the DOM.Element
approach covers you a little bit more. You see, the DOM.Element instance is preparation for Aurelia
supporting additional environments like Node.js which does not have globals like the browser
does. This means if you try to inject Element in a Node.js app, it wont work. Through the use
of DOM.Element you are covering yourself for future environments as it handles compatibility for
you.

Booting Up Aurelia
When you bootstrap an Aurelia application, you have a main entry point into your application
which is specified using the aurelia-app attribute inside of your main HTML file (in our case it is
index.html).
Placing this attribute on a HTML element (usually the body) tells the Aurelia bootstrapper where to
inject the application. If you do not specify a value for this attribute, it attempts to use app.js by
default to bootstrap your application.
The starter application is configured to use src/main.js file as the entry point that kicks everything
off. You can specify any named Javascript file as your main entry point.
Inside of the index.html file on the body attribute, you will notice an attribute called aureliaapp. This attribute takes the name of a Javascript file as its value. In our case the starter application
references main.js using: aurelia-app="main" if you wanted to have a file called start.js you
would specify the value of start to the aurelia-app attribute as: aurelia-app="start".
The file that the aurelia-app attribute references needs to export a single function called configure.
The configure method accepts an argument which is the Aurelia framework object. It allows us to

Crash Course In Aurelia

configure the framework, load plugins and custom resources using method chaining. Everything
important that takes place before anything is loaded happens in here.
Inside of the main.js file which bootstraps our application you can see it is quite simple:
export function configure(aurelia) {
aurelia.use
.standardConfiguration()
.developmentLogging()
.plugin('aurelia-animator-css');
aurelia.start().then(a => a.setRoot());
}

As you can see, the configure method is being exported which is called by the Aurelia bootstrapper.
Using aurelia.use we can call framework specific configuration methods to change logging modes
and load plugins.
The part in the above code that actually loads up our Aurelia application is the last line within the
configure method, aurelia.start() which returns a promise and returns an object with a setRoot
method.
aurelia.start().then(a => a.setRoot('app', document.body));

The setRoot method accepts two argument values. The first argument is a name of the viewmodel
Javascript class. In the starter application this is app.js and an associated View file called app.html.
This is where we can define application routes and other logic that is initially loaded and displayed.
This is the gateway to our entire application.
The second argument on the setRoot method is the location where the first supplied argument
viewmodel and view is attached. This is a DOM node and in the case of the starter application
we are attaching our application to the <body> tag within the index.html file.
Our Aurelia application loads up a few dependencies and configures some aspects of the framework
as well. Every single aspect of Aurelia is configurable from the binding language used in templates,
to the router and more.

Multipage Applications With .setRoot


We briefly explained what the setRoot method does above, but there is more than meets the eye.
For example if you have an application that has two parts, the first being the public facing aspect and
then an administration panel only for logged in users, you dont want your routes to be accessible
even accidentally until after the user logs in.
Because routes are defined inside of your app.js file, we can actually create two separate application
files each with their own routes. Your app.js can remain the public part of your application and
then we can creating a admin.js or whatever which contains your private routes.

Crash Course In Aurelia

10

This allows you to introduce the concept of one or more shells. You can keep your non-authenticated
application logic separate from the authenticated part and so on. This gives you security through
obscurity. Meaning it wont stop someone from trying to find out what your authenticated routes
are, but will make it harder.
If you wanted to make your application render a login view until the user has authenticated you
could do something like the below example.
src/main.js {lang=js} export function configure(aurelia) { aurelia.use .standardConfiguration()
.developmentLogging();
aurelia.start().then(app app.setRoot(login)); }
Providing value of login to the setRoot method tells Aurelia to load our login viewmodel first when
the application loads. This is just a standard viewmodel with a view rendering a login form.
src/login.js {lang=js} import { inject, Aurelia } from aurelia-framework;
@inject(Aurelia) export class Login { aurelia = null; username = ; password = ;
constructor(aurelia) {
this.aurelia = aurelia;
}
login(evt) {
if (this.username === username && this.password === password) {
this.aurelia.setRoot(app);
}
evt.preventDefault();
}

} }
src/login.html {lang=html} <template> <form action= method=POST submit.delegate=submit($event)>
<label for=username>Username</label>
<input type=text name=username id=username value.bind=username>
<label for=password>Password</label>
<input type=password name=password id=password value.bind=password>
</form>

</template>
Now when a user first visits our application, they can only access the login view and viewmodel.
Once they authenticated using the secret username and password of username:password we then
set the appRoot to be app which is src/app.js.

Crash Course In Aurelia

11

The beautiful thing about this is when the user first hits your application notice the router is not
even being used? This is another layer of security. The user cant navigate to routes that arent even
defined as they are handled inside of app.js, genius.
Not only can you use this approach to protect parts of your application, it allows you to create
multiple Aurelia applications in the one codebase that you can switch between by injecting the
Aurelia instance and using the setRoot method to switch root viewmodels.
In the example application chapter we will be building an authentication based application where
we switch between a public and non-public root viewmodel.

A note on security
While the approach of using setRoot to prevent users accessing certain routes does work, it
is security through obscurity. Please ensure that you also check via server calls and tokens
if the current user is allowed to access a certain route. Like client-side validation, trust
nothing the client sends to you.

Progressive Enhancement Using Enhance


By default using the setRoot method will dynamically generate your application contents, but what
about situations where you might want to enhance some existing HTML? This is where enhance
comes into the fray.
Because Aurelia currently does not support creating universal/isomorphic Javascript applications,
we can kind of use progressive enhancement to enhance existing HTML which gets us one tenth
of the way there. This will only work for the initial render, so if you have a homepage for example
and you want to make it a bit more SEO friendly by sending back some content that Aurelia then
enhances, this is where enhance comes into the equation.
One of my favourite things about the progressive enhancement functionality is you can supply
content from your server to your custom elements right in the page. This eliminates the need for an
AJAX request to load data from the server as it is supplied in the page itself. A fast initial render is
one of the reasons people love isomorphic web applications because you essentially render a page
using the server and then the client takes over after-the-fact.

A note on enhance usage with custom components


For the enhance functionality to work, you need to make sure your custom elements are
globalised using the globalResources functionality. You will see this in action below when
we run through a couple of examples. The reason is Aurelia needs to know about these
elements before they can be used with enhance.

Crash Course In Aurelia

12

Progressive Enhancement Examples


I probably did a pretty bad job at explaining what progressive enhancement actually is, so what
better than a couple of examples to clear things up? We will be working with the Aurelia starter
application we setup in chapter 2 titled, Getting Up and Running so if you skipped that step, you
might be a little confused if you are not familiar with Aurelia just yet.
Basic Example
In our index.html file we are going to make a couple of little additions. Youll notice that we have
a pretty standard index file, the only thing we have done is added in a custom element which will
get parsed by Aurelia when we instantiate everything. This custom element which we will create
shortly accepts two properties, one or product data and the other for a username.
<!doctype html>
<html>
<head>
<title>My App</title>
</head>
<body aurelia-app="main">
<products products.bind="productData" username="dwayne"></products>
<script src="jspm_packages/system.js"></script>
<script src="config.js"></script>
<script>
SystemJS.import('aurelia-bootstrapper')
</script>
</body>
</html>

Now, lets open up src/main.js and replace the entire file with the following:
export function configure(aurelia) {
aurelia.use
.standardConfiguration()
.developmentLogging()
.globalResources('products');
let viewModel = {
productData: [
{cost: '$22.95', name: 'Deluxe Cat Litter Tray'},
{cost: '$12.50', name: 'Large Squeaking Duck Toy'},
{cost: '$5.95', name: '12 Pack of Rabbit Stickers'},
{cost: '$50000', name: 'Microsoft Visual Studio Bulk Licence'}

Crash Course In Aurelia

13

]
};
aurelia.start().then(() => aurelia.enhance(viewModel, document.body));
}

The lines of interest here are .globalResources('products') which makes our custom element
(which doesnt exist yet) global. As mentioned, this is a requirement with the enhance functionality.
If an element is not globalised, Aurelia wont know how or where to render this, so youll get a blank
area in your app. We are then creating an object of data to mimick what a server might provide us
and passing it through to the enhance method as the first argument. The second argument is the
location of where Aurelia should enhance, most cases it is the body element, but you might have a
DIV you would prefer this happen with.
Now, lets finally create our custom element. Remember all of our files go into the src directory so
they get transpiled via Babel and put into the dist folder. Put the following two files into the src
folder following the below naming.
products.js
import {customElement, bindable} from 'aurelia-framework';
@customElement('products')
export class Products {
@bindable products = [];
@bindable username = '';
}

products.html
<template>
<h1>Hello ${username}! welcome to the shop of wonderful products</h1>
<ul>
<li repeat.for="product of products">${product.cost} - ${product.name}</li>
</ul>
</template>

As you can see our custom element just loops over the data we give it and displays it in a nice
unordered list for us. Now run gulp watch and see your newly added enhanced method come to
life. You will notice this is a lot faster than the usual approach of rendering DOM elements using
Aurelia, more-so depending on which browser you are using.

Crash Course In Aurelia

14

Server Content In Page


I have actually used the below example approach when passing through data from the server and
enhancing it. Using the above example, lets make some changes to allow the server to supply product
data in the page itself as an object. Aurelia can bind to objects defined in the HTML itself which is
an awesome and pretty unknown feature we can use to our advantage with enhancement.
Open src/main.js and replace its contents with the following:
export function configure(aurelia) {
aurelia.use
.standardConfiguration()
.developmentLogging()
.globalResources('products');
aurelia.start().then(() => aurelia.enhance());
}

Open index.html and replace the existing custom element with the following:
<products products.bind="[
{cost: '$22.95', name: 'Deluxe Cat Litter Tray'},
{cost: '$12.50', name: 'Large Squeaking Duck Toy'},
{cost: '$5.95', name: '12 Pack of Rabbit Stickers'},
{cost: '$50000', name: 'Microsoft Visual Studio Bulk Licence'}
]" username="dwayne"></products>

Youll notice we have wound back the enhance functionality to just enhance the body element and
we arent supplying any custom viewmodel data to it. But then you can see we are doing something
really cool, we are binding to an array of objects inside of the HTML itself. This data could be put
into the page via your server and could save an AJAX request or two. A great fictional example is a
list of products where the products data is in the page on page load.

Browser Support And Polyfills


While Aurelia is intended for web applications built for evergreen web browsers; Google Chrome,
Mozilla Firefox, Internet Explorer 11, Edge and Safari 8+ through the use of the MutationObservers
polyfill we can support Internet Explorer 9 and up.
If support for Internet Explorer 9 and 10 is important to you, as well as any other browser that might
not support some of the concepts utilised by Aurelia, you will need to install the MutationObservers
polyfill by running the following in your console (within the root directory of your application).

Crash Course In Aurelia

15

jspm install github:webcomponents/webcomponentsjs

Then updating your main index.html file with the following:


<script>
SystemJS.import('webcomponents/webcomponentsjs/MutationObserver').then(function() {
SystemJS.import('aurelia-bootstrapper');
});
</script>

Previously Aurelia shipped with and used the core-js polyfill extensively. Due to the uncertain
future of core-js, the Aurelia team decided to create their own set of minimal polyfills used for
various Aurelia dependencies. As such, there might be situations where you will need to implement
your own polyfill, if you need to support Symbols or other ES6/ES7 features. This can be achieved
easily and if you wanted to use a polyfill such as core-js or es6-shim, you can.
If you wanted to use core-js you could add it into Aurelia firstly, you need to install it: jspm install
core-js then wrap your Bootstrapper call like so:
<script>
SystemJS.import('core-js').then(function() {
SystemJS.import('webcomponents/webcomponentsjs/MutationObserver').then(function() {
SystemJS.import('aurelia-bootstrapper');
});
});
</script>

Plugins
The Aurelia framework has an extensible plugin system that allows you to use official Aurelia and
third party plugins within your application. There are a lot of third party plugins already to install
that the community have created and ported from existing frameworks.
A plugin is nothing more than a file which exports a configure method (similar to the method inside
of your main.js file). From within this main export we can include plugin files and interact with
Aurelias internals.
Later on we will go through the process of using the Aurelia plugin skeleton to create our own
plugin, but for now lets just show how you can download and configure a preexisting plugin.
During the bootstrapping phase of your application you can load plugins and make them accessible
to your entire application. The aurelia-validation plugin is a great example of a plugin you install
during the bootstrapping phase and then use in your viewmodels or anywhere else in your
application.

Crash Course In Aurelia

16

To load the aurelia-validation plugin all you need to do is make sure the plugin is installed by typing
into the console inside of the project directory: jspm install aurelia-validation and then after
it has finished downloading, configure Aurelia to load it using the aurelia.use.plugin method.

Important Note
The validation dependency is only used for example purposes. At the time of writing this,
this module is undergoing a massive rewrite and is currently not very functional. It is not
recommended you use the dependency, just follow along with this example on how to
install a plugin.

export function configure(aurelia) {


aurelia.use
.standardConfiguration()
.developmentLogging()
.plugin('aurelia-validation')
.plugin('aurelia-animator-css');
aurelia.start().then(a => a.setRoot());
}

As you can see, installing Aurelia plugins is quite easy to do.

Resources
When you create a viewmodel or view in Aurelia, it is encapsulated. This means that you must
import or require modules and views into your application. There might be situations where a lot
of your views are using the same component. Obviously importing the same component multiple
times would just be tiring. This is where global resources and features come into the equation.
Using the feature and globalResources methods, we can tell Aurelia we have one or more
components we want to be automatically imported. This allows us to use components we have
created without needing to import them first.
Global Resources
Inside of your main.js file, you can specify a component file to be made global. This could be a
custom element, a custom attribute or even a value converter.
Lets assume you have a custom element called markdown like we created earlier. This component
is located in the folder resources and has two files: markdown.js and markdown.html. When we
reference our custom element to be globalised, we reference the Javascript viewmodel and do not
specify a file extension as Aurelia internally will use the loader to add the file extension.
Opening up main.js we can use the globalResources method to specify our custom element should
be globalised.

Crash Course In Aurelia

17

export function configure(aurelia) {


aurelia.use
.standardConfiguration()
.developmentLogging()
.globalResources('resources/markdown-component');
aurelia.start().then(() => aurelia.setRoot());
}

As you will discover in the features section below, the globalResources method accepts multiple
parameters. This allows you to globalise multiple components at once.

Features
The features feature (what a tongue twister) in Aurelia allows you to specify folders which are
home to features. A feature might be a part of your app that is being worked on by another team or
a developer within your own team. It allows you to encapsulate and organise your code better as to
not interfere or disrupt what anyone else might be working on in the same application.
Lets take the above Markdown component example as a use-case. Imagine that we dont just have
a couple of files for our component, but 10 other files which handle working with Markdown
and different elements for displaying it in your application. Rather than specify inside of your
configuration logic, you can create a feature module file called index.js and place it into a folder
somewhere.
Keeping with the same theme, we are still working inside of the resources folder, but we are going
to create a new folder called markdown and move the existing files into it. Then we need to create
an index.js file.
Creating A Feature Module
The index.js file just exports a configure function and it gets an instance of the Aurelia configuration object as its first argument, the same one that aurelia.use is given in our main bootstrap
file.
export function configure(config) {
config.globalResources('./markdown', './disco-markdown-spinner', 'etc.');
}

As you can see, inside of our feature file we are using globalResources() to specify what parts of
our Markdown feature are accessible without needing to be imported. Please note that all resources
are relative to the index.js file.
Using our feature:

Crash Course In Aurelia

18

export function configure(aurelia) {


aurelia.use
.standardConfiguration()
.developmentLogging()
.feature('resources/markdown'); // This will reference resources/markdown/index.js
aurelia.start().then(() => aurelia.setRoot());
}

Importing & Loading Components


Once again, Aurelia uses standards based syntax for importing components inside of your application. The ES2015 module loader import functionality is what you will use to import third-party
plugins and other files. You will also use it to import Aurelia specific decorators and dependencies.
A custom element is a great example of using the syntax as you need to import one or more
exported functions from the Aurelia framework, especially if you have bindable data properties.
If a dependency has more than one export, you will need to use curly braces to dictate what you
want to import.
An example of imports for a custom element:
import {customElement, bindable} from 'aurelia-framework';

As you can see we are including two decorators from the main aurelia-framework dependency for
creating our custom element with bindable data. Later on we will go through the process of creating
custom elements and you will see this import syntax again.
Not all packages have multiple exports, sometimes a package has one export and it is a default export.
If a class has a default export it will be defined like this:
export default class MyClass {
}

And then to import it, you can omit the curly braces:
import MyClass from 'my-class-dependency';

A note for TypeScript users


In TypeScript we also have the ability to use a CommonJS style import using require
which you will be more than familiar with if you have ever worked with Node or created a
gulpfile. A simple example of this is: var mymodule = require('some-module-name');

Crash Course In Aurelia

19

In the context of Aurelia packages, you will be using curly braces to import various decorators and
exposed methods as there are no default exports and it pays to be a little more verbose so you can
see where everything is coming from.
We do not go into too much detail here as this book assumes you are familiar with ES2015 modules,
but if you would like to know more you can read about them in the official spec here.

Client-side Logging Using Log Appenders


In Aurelia there exists log appender functionality which allows you to capture; warnings, errors
and information at various points inside of your Aurelia applications. If you are using the default
configuration, you might notice your web browser development tools console displays a bit of
information when you load the page about what is being loaded and if there are any errors.
By default Aurelia ships with a log appender which outputs those errors to the console in the
browser, which during the development phase is what you want. However, when you go into
production you dont want users to see those potentially scary errors or warnings, instead you will
have infrastructure setup to silently capture and report those tidbits.

Creating A Basic Custom Log Appender


In this section we are going to be creating a custom and basic log appender. Then we are going to
create a custom appender you can use to log server-side exceptions more suited for a production
environment.
Inside of our src directory lets create a new file called my-log-appender.js and inside of it add the
following:
export class MyLogAppender {
debug(logger, message, ...rest){
console.debug(`DEBUG [${logger.id}] ${message}`, ...rest);
}
info(logger, message, ...rest){
console.info(`INFO [${logger.id}] ${message}`, ...rest);
}
warn(logger, message, ...rest){
console.warn(`WARN [${logger.id}] ${message}`, ...rest);
}
error(logger, message, ...rest){

http://wiki.ecmascript.org/doku.php?id=harmony:modules

Crash Course In Aurelia

20

console.error(`ERROR [${logger.id}] ${message}`, ...rest);


}
}

Please note
You must implement all of the above methods in your custom log appenders. Like we
created above, we must specify methods; debug, info, warn and error.

And now inside of our main.js file also in the same src directory lets add our custom log appender.
import {LogManager} from 'aurelia-framework';
import {MyLogAppender} from './my-log-appender';
// Add our custom created log appender to Aurelia
LogManager.addAppender(new MyLogAppender());
// We want to log all messages, so set the loglevel to debug.
LogManager.setLevel(LogManager.logLevel.debug);
export function configure(aurelia) {
aurelia.use
.standardConfiguration();
aurelia.start().then(a => a.setRoot());
}

Logging Levels
The Aurelia log appender accepts five different valid logging level values, none which
logs nothing, error which only logs errors, warn which only logs warning messages or
above, info logs additional messages or above and debug which logs every message type.
These are specified using LogManager.logLevel.levelvalue

Important
Make sure you remove the existing developmentLogging() line from your main.js file or
you will see double up of messages in your browser console.

We actually just duplicated what the existing Logging Console plugin does for us in Aurelia, but it
serves to show how easy it is to create your own custom logging appender and instead of outputting
to the console, we can send this off to the server or whereever.
https://github.com/aurelia/logging-console

Crash Course In Aurelia

21

Creating A Server-side Log Appender


We have learned how to create a custom log appender, now lets create one that sends info to the
server via HTTP using the HTTP Client dependency.
Inside of our src directory lets create a new file called server-log-appender.js and inside of it
add the following:
import { inject } from 'aurelia-framework';
import { HttpClient } from 'aurelia-http-client';
import { Logger } from 'aurelia-logging';
@inject(HttpClient)
export class ServerLogAppender {
constructor(http) {
this.http = http;
}
debug(logger, message, ...rest){
this.sendToServer(logger, message, ...rest);
}
info(logger, message, ...rest){
this.sendToServer(logger, message, ...rest);
}
warn(logger, message, ...rest){
this.sendToServer(logger, message, ...rest);
}
error(logger, message, ...rest){
this.sendToServer(logger, message, ...rest);
}
sendToServer(logger, message, ...rest) {
this.http.post('myerrorhandler', {
url: window.location.href,
source: logger.id,
message: message,
additional: rest.join('\r\n')
});
}
}

We now want to install the Aurelia HTTP Client by opening up a terminal window and typing: jspm
install aurelia-http-client which will download and configure the client for use in our custom

Crash Course In Aurelia

22

logger. We now have a custom log appender, lets open up main.js and configure our usage of this
new appender.
import {LogManager} from 'aurelia-framework';
import {ServerLogAppender} from './server-log-appender';
import { HttpClient } from 'aurelia-http-client';
// Add our custom created log appender to Aurelia and pass through the HTTP Client
LogManager.addAppender(new ServerLogAppender(new HttpClient()));
// We want to log all messages, so set the loglevel to debug.
LogManager.setLevel(LogManager.logLevel.debug);
export function configure(aurelia) {
aurelia.use
.standardConfiguration();
aurelia.start().then(a => a.setRoot());
}

Multiple Log Appenders


It is worth noting you can specify multiple logging appenders. So you can create a custom
appender and also specify that you want to use the standard console appender as well.
Simply add another LogManager.addAppender line before your custom appenders to do so.

Potrebbero piacerti anche