Practical Aurelia

You might also like

Download as pdf or txt
Download as pdf or txt
You are on page 1of 118

Practical Aurelia

Real-world examples of using Aurelia in multiple scenarios


and platforms

Behzad Abbasi
This book is for sale at http://leanpub.com/practical-aurelia

This version was published on 2017-07-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 - 2017 Behzad Abbasi


Tweet This Book!
Please help Behzad Abbasi by spreading the word about this book on Twitter!
The suggested hashtag for this book is #PracticalAurelia.
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=#PracticalAurelia
Contents

Five Practical Examples For Learning Aurelia . . . . . . . . . . . . . . . . . . . . . . . . . 1


1. Navigation Menu . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
2. Inline Editor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
3. Order Form . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
4. Instant Search . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
5. Switchable Grid . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10

Firebase . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
Creating a Firebase plugin for Aurelia . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
Firebase password authentication . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
Firebase ReactiveCollection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
Enabling Offline Capabilities . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43

Loopback . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
What is Loopback? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
Creating server as loopback backend . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
Using generated REST API in Aurelia . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66

Working with Apache Cordova . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71


What is Cordova? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
Installing Cordova . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
Create a project . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
Add a platform . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
Run your app . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
Add Plugins . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
Updating Cordova and Your Project . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
Using Aurelia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
Taking photos with a Cordova application . . . . . . . . . . . . . . . . . . . . . . . . . . 79
Installing Plugins . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
Adding Crosswalk . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
Configuring the App . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
Deploying the App . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
Using Aurelia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88
CONTENTS

Planning an Aurelia Application . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106


Project Management . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106
Accessibility, i18n and environments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
Development Process Methodology . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
Tooling and Development . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108
Testing Methodologies . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110
Codebase Distribution Strategies . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111
Backend API . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112
Performance Strategies . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113
Five Practical Examples For Learning
Aurelia
In this chapter you will find 5 practical examples that have been built with Aurelia, and which will
give you a head start with the framework.
In this chapter, we will use many JavaScript class and Html files for run the application. We can see
these in below.

Listing-1: index.html file

1 <!-- index.html -->


2 <!DOCTYPE html>
3 <html>
4 <head>
5 <title>Practical Aurelia</title>
6 <!--The FontAwesome version is locked at 4.6.3 in the package.json file to k\
7 eep this from breaking.-->
8 <link rel="stylesheet" href="jspm_packages/npm/font-awesome@4.6.3/css/font-a\
9 wesome.min.css">
10 <link rel="stylesheet" href="styles/styles.css">
11 <meta name="viewport" content="width=device-width, initial-scale=1">
12 </head>
13
14 <body aurelia-app="main">
15
16 <div class="splash">
17 <div class="message">Practical Aurelia</div>
18 <i class="fa fa-spinner fa-spin"></i>
19 </div>
20
21 <script src="jspm_packages/system.js"></script>
22 <script src="config.js"></script>
23 <script>
24 SystemJS.import('aurelia-bootstrapper');
25 </script>
26 </body>
27 </html>
Five Practical Examples For Learning Aurelia 2

At first, create index.html file in the project root directory. Then creating app directory and main.js
file for implement Aurelia configuration.

Listing-2: main.js as aurelia-app

1 // main.js
2 export function configure(aurelia) {
3 aurelia.use
4
5 //You can use custom configuration
6 .standardConfiguration()
7
8 //Show logging
9 .developmentLogging();
10
11 //You can use aurelia.setRoot('your-directory/your-class.js') instead app.js
12 aurelia.start().then(() => aurelia.setRoot());
13 }

1. Navigation Menu
As a first example, we will build a navigation menu that to routes the selected entry. To use Aurelia’s
router, your component view must have a <router-view></router-view> element. In order to
configure the router, the component’s view-model requires a configureRouter() function.

Listing-3: app.js contains aurelia route config

1 // app.js
2 export class App {
3 configureRouter(config, router) {
4 config.title = 'Navigation Menu';
5 // Define routes
6 config.map([
7 { route: ['', 'home'], name: 'home', moduleId: 'navigation-menu/pages\
8 /home', nav: true, title: 'Home' },
9 { route: 'about', name: 'about', moduleId: 'navigation-menu/pages\
10 /about', nav: true, title: 'About' },
11 { route: 'contact', name: 'contact', moduleId: 'navigation-menu/pages\
12 /contact', nav: true, title: 'Contact' }
13 ]);
14
15 this.router = router;
Five Practical Examples For Learning Aurelia 3

16 }
17 }

Listing-4: app.html as router view

1 <!-- app.html -->


2 <template>
3 <!-- require menu file -->
4 <require from="nav-bar.html"></require>
5 <!-- require bootstrap css file -->
6 <require from="bootstrap/css/bootstrap.css"></require>
7
8 <!-- using custom element -->
9 <nav-bar router.bind="router"></nav-bar>
10
11 <div class="page-host">
12 <!-- show routes -->
13 <router-view></router-view>
14 </div>
15 </template>

At the top, you can see require element for import HTML and CSS file by Url that define in from
attribute. By doing so, you can use ViewModels and Models as minor at page.

Listing-5: nav-bar.html as navigation bar

1 <!-- nav-bar.html -->


2 <template bindable="router">
3 <nav class="navbar navbar-default navbar-fixed-top" role="navigation">
4 <div class="navbar-header">
5 <button type="button" class="navbar-toggle" data-toggle="collapse" data-ta\
6 rget="#skeleton-navigation-navbar-collapse">
7 <span class="sr-only">Toggle Navigation</span>
8 <span class="icon-bar"></span>
9 <span class="icon-bar"></span>
10 <span class="icon-bar"></span>
11 </button>
12 <a class="navbar-brand" href="#">
13 <i class="fa fa-home"></i>
14 <span>${router.title}</span>
15 </a>
16 </div>
Five Practical Examples For Learning Aurelia 4

17 <div class="collapse navbar-collapse" id="skeleton-navigation-navbar-collaps\


18 e">
19 <ul class="nav navbar-nav">
20 <!-- repeat li tag to display route title -->
21 <li repeat.for="row of router.navigation" class="${row.isActive ? 'activ\
22 e' : ''}">
23 <a data-toggle="collapse" data-target="#skeleton-navigation-navbar-col\
24 lapse.in" href.bind="row.href">${row.title}</a>
25 </li>
26 </ul>
27 <ul class="nav navbar-nav navbar-right">
28 <!-- show loading -->
29 <li class="loader" if.bind="router.isNavigating">
30 <i class="fa fa-spinner fa-spin fa-2x"></i>
31 </li>
32 </ul>
33 </div>
34 </nav>
35 </template>

In the code above, we are using custom element to set and read the route list. When it changes,
it causes change the route and the HTML that uses it to be updated automatically. In Aurelia’s
terminology, this HTML is called View Model that contains a Model by same name. If you have used
JavaScript templates or Model before, you are familiar with the ${row.title} syntax. When the
framework sees such a string, it replaces it with the contents of the variable.

2. Inline Editor
For the second example, we will create a simple inline editor clicking a paragraph will show a tooltip
with a text field.We will use a controller that will initialize the models and declare two methods for
toggling the visibility of the tooltip.
Five Practical Examples For Learning Aurelia 5

Listing-6: app.js

1 export class App {


2 constructor() {
3 //Here we set some default values:
4 this.showtooltip = false;
5 this.title = 'Edit me.';
6 }
7 hideTooltip() {
8 // In this case it will hide the tooltip.
9 this.showtooltip = false;
10 }
11 toggleTooltip(e) {
12 e.stopPropagation();
13 this.showtooltip = !this.showtooltip;
14 }
15 }

Adding properties or functions to it makes them available to the view. Using the value binding on
the text field tells Aurelia to update that variable when the value of the field changes (this in turn
re-renders the paragraph with the value).

Listing-7: app.html

1 <template>
2 <require from="./app.css"></require>
3 <div id="main" click.trigger="hideTooltip()">
4 <!-- This is the tooltip. It is shown only when the showtooltip variable\
5 is truthful -->
6 <div class="tooltip" click.trigger="$event.stopPropagation()" show.bind=\
7 "showtooltip">
8 <!--value.bind binds the contents of the text field with the "title"\
9 model.
10 Any changes to the text field will automatically update the title, and
11 all other bindings on the page that depend on it. -->
12 <input type="text" value.bind="title" />
13 </div>
14 <!-- Call a method defined in the InlineEditorController that toggles
15 the showtooltip variable -->
16 <p click.trigger="toggleTooltip($event)">${title}</p>
17 </div>
18 </template>
Five Practical Examples For Learning Aurelia 6

3. Order Form
In this example, we will code an order form with a total price updated in real time, using another
one of Aurelia’s useful features as ValueConverter.
A value converter is a class whose responsibility is to convert view-model values into values that
are appropriate to display in the view and visa-versa. In the example below, I am using the currency
format, to turn a number into a properly formatted price, complete with a dollar sign and cents.

Listing-8: app.js

1 import {computedFrom} from 'aurelia-framework';


2 export class App {
3 constructor() {
4 //Here we set some default values:
5 this.services = [
6 {
7 name: 'Web Development',
8 price: 300,
9 active: true
10 }, {
11 name: 'Design',
12 price: 400,
13 active: false
14 }, {
15 name: 'Integration',
16 price: 250,
17 active: false
18 }, {
19 name: 'Training',
20 price: 220,
21 active: false
22 }
23 ];
24 // using for computed observe
25 this.lastServiceName = '';
26 }
27 toggleActive(service) {
28 // In this case it will hide the tooltip.
29 service.active = !service.active;
30 this.lastServiceName = service.name;
31 }
32 @computedFrom('lastServiceName')
Five Practical Examples For Learning Aurelia 7

33 get total() {
34 // "total" will be observed.
35 var total = 0;
36 this.services.forEach((service) => {
37 if (service.active) {
38 total += service.price;
39 }
40 });
41 return total;
42 }
43 }

Aurelia polls your property for changes because it has no way of knowing when your property-getter
will return a different value. If it were a simple property (without a getter), Aurelia could observe the
property directly, no polling would be needed. To avoid the polling you could tell Aurelia’s binding
system what to observe.
Aurelia’s binding system has a method for observing computed properties- the @computedFrom dec-
orator. Simply decorate any property with @computedFrom(propertyName1[, propertyName2...,
propertyNameN]) and Aurelia’s binding system will observe the specified properties and re-evaluate
bindings when any of the properties change.

Listing-9: currency-format.js

1 import numeral from 'numeral';


2 export class CurrencyFormatValueConverter {
3 toView(value) {
4 return numeral(value).format('($0,0.00)');
5 }
6 }

We created a value converter CurrencyFormatValueConverter. It has a toView method that the


Aurelia framework will apply to model values before displaying that in the view. Our converter use
the NumeralJS library to format the data.
Five Practical Examples For Learning Aurelia 8

Listing-10: app.html

1 <template>
2 <require from="./app.css"></require>
3 <require from="./currency-format"></require>
4 <form role="form">
5 <h1>Services</h1>
6 <ul>
7 <!-- Loop through the services array, assign a click handler, and se\
8 t or
9 remove the "active" css class if needed -->
10 <li repeat.for="service of services" click.trigger="toggleActive(ser\
11 vice)" class.bind="service.active?'active':''">
12 <!-- Notice the use of the currency filter, it will format the p\
13 rice -->
14 ${service.name} <span>${service.price | currencyFormat}</span>
15 </li>
16 </ul>
17 <div class="total">
18 <!-- Calculate the total price of all chosen services. Format it as \
19 currency. -->
20 Total: <span>${total | currencyFormat}</span>
21 </div>
22 </form>
23 </template>

The repeat.for binding is another useful feature of the framework. It lets you loop through an
array of items and generate markup for them. It is intelligently updated when an item is changed or
deleted.
Finally we applied the converter in the binding using the pipe | syntax.
${total | currencyFormat}

4. Instant Search
This example will allow users to filter a list of items by typing into a text field. This is another place
where Aurelia shines, and is the perfect use case for writing a value converter.
Five Practical Examples For Learning Aurelia 9

Listing-11: app.js

1 import {inject} from 'aurelia-framework';


2 import {HttpClient} from 'aurelia-fetch-client';
3 import 'fetch';
4 @inject(HttpClient)
5 export class App {
6 constructor(http) {
7 //Here we set some default values:
8 http.configure(config => {
9 config
10 .useStandardConfiguration()
11 .withBaseUrl('https://api.github.com/');
12 });
13 this.http = http;
14 }
15 activate() {
16 return this.http.fetch('users')
17 .then(response => response.json())
18 .then(users => this.users = users);
19 }
20 }

The HttpClient is another useful feature of the framework. It is merely a thin wrapper around the
native Fetch specification. So anything detailed in the Fetch specification can be achieved using the
Aurelia Fetch client albeit the way the wrapper expects them to be done.

Listing-12: seacrh-for.js

1 export class SearchForValueConverter {


2 toView(array, searchString) {
3 if (!searchString) {
4 return array;
5 }
6 var result = [];
7 searchString = searchString.toLowerCase();
8 // Using the forEach helper method to loop through the array
9 array.forEach(function (item) {
10 if (item.login.toLowerCase().indexOf(searchString) !== -1) {
11 result.push(item);
12 }
13 });
14 return result;
Five Practical Examples For Learning Aurelia 10

15 }
16 }

The converters in the previous example worked great but what if we needed to display numbers.
It would be quite repetitive to define a converter for each value we needed to display. A better
approach would be to modify the converters to accept a searchString parameter. Then we’d be
able to specify the value in the binding and get filter users out of our value converter.

Listing-13: app.html
1 <template>
2 <require from="./app.css"></require>
3 <require from="./search-for"></require>
4 <div id="main">
5 <div class="bar">
6 <!-- Create a binding between the searchString model and the text fi\
7 eld -->
8 <input type="text" value.bind="searchString" placeholder="Enter your\
9 search terms" />
10 </div>
11 <ul>
12 <!-- Render a li element for every entry in the items array. Notice
13 the custom search value converter "searchFor". It takes the value of the
14 searchString model as an argument.
15 -->
16 <li repeat.for="user of users | searchFor:searchString">
17 <a href.bind="user.html_url"><img src.bind="user.avatar_url" /><\
18 /a>
19 <p>${user.login}</p>
20 </li>
21 </ul>
22 </div>
23 </template>

With the searchString parameter added to the toView methods we are able to specify the value in
the binding using the [expression] | [converterName]:[parameterExpression] syntax:
user of users | searchFor:searchString

5. Switchable Grid
Another popular UI interaction is switching between different layout modes (grid or list) with a
click of a button. This is very easy to do in Aurelia. In addition, I will introduce another important
Five Practical Examples For Learning Aurelia 11

concept – Services. They are objects that can be used by your application to communicate with a
server, an API, or another data source. In our case, we will write a service that communicates with
Instagram’s API and returns an array with the most popular photos at the moment.

Listing-14: app.js

1 // app.js
2 import {inject} from 'aurelia-framework';
3 import InstagramService from './instagram-service';
4 @inject(InstagramService)
5 export class App {
6 constructor(instagramService) {
7 //Here we set some default values:
8 this.layout = 'grid';
9 this.pics = [];
10 this.instagramService = instagramService;
11 }
12 activate() {
13 let that = this;
14 this.instagramService.fetchPopular(function (data) {
15 // Assigning the pics array will cause the view
16 // to be automatically redrawn by Aurelia.
17 that.pics = data;
18 })
19 }
20 }

If you create a service called InstagramService and inject it throughout different parts of your
app, provided you’re injecting it without telling Aurelia to do anything non-standard, it will be
a singleton. A class by default in Aurelia is a singleton and unless you specify otherwise using a
decorator like @transient() then that is guaranteed.

Listing-15: app.js

1 //instagram-service.js
2 import {inject} from 'aurelia-framework';
3 import {HttpClient} from 'aurelia-http-client';
4 import 'fetch';
5 @inject(HttpClient)
6 export default class InstagramService {
7 constructor(httpClient) {
8 let clientId = '3872137809.b91ba91.11de5ddc30274a349dd7d09141677115';
9 this.http = httpClient;
Five Practical Examples For Learning Aurelia 12

10 this.url = 'https://api.instagram.com/v1/tags/puppy/media/recent?code=19\
11 b6dee5c5f24950b026a8cd591a99bb&access_token=' +
12 clientId + '&callback=JSON_CALLBACK'
13 }
14 fetchPopular(callback) {
15 return this.http.jsonp(this.url, 'callback').then(response => {
16 callback(response.content.data);
17 });
18 }
19 }

Instagram API expects callback GET parameter to be present in request URL. This parameter should
be a function name to wrap response object into (Aurelia will add something like &callback=JSON_-
CALLBACK). It adds callback parameter automatically behind the scene.

Warning
Due to changes in the Instagram API, this demo is no longer working. However, the rest
of the code, including the AJAX request, is still correct.

Listing-16: app.html

1 <template>
2 <require from="./app.css"></require>
3 <section>
4 <div class="bar">
5 <!-- These two buttons switch the layout variable,
6 which causes the correct UL to be shown. -->
7 <a class="list-icon" class.bind="layout == 'list'? 'active' : ''" cl\
8 ick.trigger="layout = 'list'"></a>
9 <a class="grid-icon" class.bind="layout == 'grid'? 'active' : ''" cl\
10 ick.trigger="layout = 'grid'"></a>
11 </div>
12 <!-- We have two layouts. We choose which one to show depending on the "\
13 layout" binding -->
14 <ul show.bind="layout == 'grid'" class="grid">
15 <!-- A view with big photos and no text -->
16 <li repeat.for="item of pics">
17 <a href.bind="item.link" target="_blank"><img src.bind="item.ima\
18 ges.low_resolution.url" /></a>
19 </li>
20 </ul>
Five Practical Examples For Learning Aurelia 13

21 <ul show.bind="layout == 'list'" class="list">


22 <!-- A compact view smaller photos and titles -->
23 <li repeat.for="item of pics">
24 <a href.bind="item.link" target="_blank"><img src.bind="item.ima\
25 ges.thumbnail.url" /></a>
26 <p>${item.caption.text}</p>
27 </li>
28 </ul>
29 </section>
30 </template>
Firebase
(Firebase is a Real-Time Database, owned by Google. http://www.firebase.com1 )
Firebase is a backend service that provides data storage, authentication, and static website hosting
for your Aurelia app.
This section has the following chapters: - Creating Aurelia plugin for Firebase - Firebase password
authentication - Firebase ReactiveCollection

Creating a Firebase plugin for Aurelia


A Firebase plugin for Aurelia that supports Authentication, Reactive data collections (auto-sync)
and other Firebase features.
For our purpose let’s call our file index.js. Our index.js file goes in our resources directory
and should always export a configure method. It doesn’t have to export a class, just the configure
function.

Listing-17: index.js

1 export function configure(aurelia, configCallback) {


2
3 }

The reason we need to export a configure function is so that Aurelia can call the function by
convention and allow it to configure itself without having to make the plugin consumer copy a
bunch of boilerplate code in to their app.
We are going to create an configuration.js file that holds our firebase configurations. For that,
creating a file in resources directory and importing to index.js.

1
http://www.firebase.com
Firebase 15

Listing-18: configuration.js

1 export class ConfigurationDefaults {


2
3 }
4
5 ConfigurationDefaults._defaults = {
6 name: 'practicalaurelia',
7 config: {
8 apiKey: 'AIzaSyAx02B4a0nbMvQxsvliyMmIjlWKqfIm58M',
9 authDomain: "practicalaurelia.firebaseapp.com",
10 databaseURL: "https://practicalaurelia.firebaseio.com"
11 }
12 };
13
14 ConfigurationDefaults.defaults = function () {
15 let defaults = {};
16 Object.assign(defaults, ConfigurationDefaults._defaults);
17 return defaults;
18 };
19
20 export class Configuration {
21
22 constructor(innerConfig) {
23 this.innerConfig = innerConfig;
24 this.values = this.innerConfig ? {} : ConfigurationDefaults.defaults();
25 }
26
27 getValue(identifier) {
28 if (this.values.hasOwnProperty(identifier) !== null && this.values[ident\
29 ifier] !== undefined) {
30 return this.values[identifier];
31 }
32 if (this.innerConfig !== null) {
33 return this.innerConfig.getValue(identifier);
34 }
35 throw new Error('Config not found: ' + identifier);
36 }
37
38 setValue(identifier, value) {
39 this.values[identifier] = value;
40 return this; // fluent API
41 }
Firebase 16

42 }

You can see here we’ve ConfigurationDefaults class for defaults config and Configuration class
to set up Firebase settings in the main.js.
ConfigurationDefaults class contains a name property for Firebase App also included a property
called config that contains initialization information to configure the Firebase JavaScript SDK to use
Authentication, Storage and the Realtime Database. You can reduce the amount of code your app
uses by just including the features you need. The individually installable components are:

• firebase-app - The core firebase client (required).


• firebase-auth - Firebase Authentication (optional).
• firebase-database - The Firebase Realtime Database (optional).
• firebase-storage - Firebase Storage (optional).
• firebase-messaging - Firebase Cloud Messaging (optional).

Configuration class used by the plugin. we use the constructor for initializes a new instance of
the Configuration class that contains an object params that the optional initial configuration values.
If not provided will initialize using the defaults.
For gets the value of a configuration option by its identifier we use getValue(identifier) that
contains a param for the configuration option identifier. This function returns the value of the
configuration option. If configuration option is not found, throws new error.
By setValue we can sets the value of a configuration option. This function returns the current
configuration instance (Fluent API)

Listing-19: index.js

1 import { Configuration } from './configuration';


2
3 export { Configuration } from './configuration';
4 export function configure(aurelia, configCallback) {
5 let config = new Configuration(Configuration.defaults);
6 if (configCallback !== undefined && typeof configCallback === 'function') {
7 configCallback(config);
8 }
9 aurelia.instance(Configuration, config);
10 }

Now we can import and export configurations in index.js. As you can see here, new instance from
configuration has been made and set to aurelia configCallback, therefore it can be used in main
configuration.
Firebase 17

Listing-20: main.js

1 export function configure(aurelia) {


2 aurelia.use
3 .standardConfiguration()
4 .plugin('resources/index', config => {
5 config.setValue('name', 'myName')
6 })
7 aurelia.start().then(() => aurelia.setRoot());
8 }

Now that we have our configurations, we are going to create User model that represents a Firebase
User.

Listing-21: user.js

1 export class User {


2 uid = null;
3 provider = null;
4 token = null;
5 auth = null;
6 expires = 0;
7 email = null;
8 isTemporaryPassword = null;
9 profileImageUrl = null;
10 get isAuthenticated() {
11 return (this.token && this.auth && this.expires > 0) || false;
12 }
13 constructor(userData = null) {
14 this.update(userData);
15 }
16 update(userData) {
17 userData = userData || {};
18 this.uid = userData.uid || null;
19 this.provider = userData.provider || null;
20 this.token = userData.token || null;
21 this.auth = userData.auth || null;
22 this.expires = userData.expires || 0;
23 userData.password = userData.password || {};
24 this.isTemporaryPassword = userData.password.isTemporaryPassword || fals\
25 e;
26 this.profileImageUrl = userData.password.profileImageURL || null;
27 this.email = userData.password.email || null;
Firebase 18

28 }
29 reset() {
30 this.update({});
31 }
32 }

At this model we have many property that contains:

• uid - A unique user ID, intented as the user’s unique key accross all providers
• provider - The authentication method used
• token - The Firebase authentication token for this session
• auth - The contents of the authentication token
• expires - A timestamp, in seconds since UNIX epoch, indicated when the authentication
token expires
• email - The user’s email address
• isTemporaryPassword - Whether or not the user authenticated using a temporary password,
as used in password reset flows
• profileImageUrl - The URL to the user’s Gravatar profile image

Also, we’ve many method for manage users that contains:

• isAuthenticated - Whether or not the user is authenticated


• update - Update the current user instance with the provided data
• reser - Reinitializes the current user instance.

We also have an constructor for initializes a new instance of the user. what remains is just exporting
in index.js.

Listing-22: index.js

1 import { Configuration } from './configuration';


2
3 export {User} from './user';
4 export { Configuration } from './configuration';
5 export function configure(aurelia, configCallback) {
6 let config = new Configuration(Configuration.defaults);
7 if (configCallback !== undefined && typeof configCallback === 'function') {
8 configCallback(config);
9 }
10 aurelia.instance(Configuration, config);
11 }
Firebase 19

Firebase password authentication


You can use Firebase Authentication to let your users authenticate with Firebase using their email
addresses and passwords, and to manage your app’s password-based accounts.
Let’s start by installing the required dependencies:
jspm install firebase

Warning
Remember if you get any errors while using jspm you might need to register your github
with it.

Next, install bluebird:


jspm install npm:bluebird

Bluebird is a fully featured promise library with focus on innovative features and performance.

Why bluebird?
There are many third party promise libraries available for JavaScript and even the standard
library contains a promise implementation in newer versions of browsers and node/io.js.
This page will explore why one might use bluebird promises over other third party or the
standard library implementations. For reasons why to use promises in general, see the Why
Promises?2 article.

Now that we have our dependencies installed, we are going to create a new file as auth.js in
resources directory.

First we inject our dependencies for initialize Firebase App and create a new user in the construc-
tor.Attention, this class created for users manager, so we use auth() method.

Listing-23: auth.js

1 import { inject } from 'aurelia-framework';


2 import Promise from 'bluebird';
3 import Firebase from 'firebase';
4 import { Configuration } from './configuration';
5 import { User } from './user';
6
7 @inject(Configuration, Firebase)
8 export class Auth {
2
http://bluebirdjs.com/docs/why-promises.html
Firebase 20

9 constructor(configuration, firebase) {
10 var app = firebase.initializeApp(configuration.getValue('config'), confi\
11 guration.getValue('name'));
12 this.firebase = app.auth();
13 this.currentUser = new User();
14 }
15 }

Let’s start by looking at what we’d like to achieve. To create a new user accounts with a password
we must passing the new user’s email address and password to createUserWithEmailAndPassword
method.

Listing-23: auth.js
1 import { inject } from 'aurelia-framework';
2 import Promise from 'bluebird';
3 import Firebase from 'firebase';
4 import { Configuration } from './configuration';
5 import { User } from './user';
6
7 @inject(Configuration, Firebase)
8 export class Auth {
9 constructor(configuration, firebase) {
10 var app = firebase.initializeApp(configuration.getValue('config'), confi\
11 guration.getValue('name'));
12 this.firebase = app.auth();
13 this.currentUser = new User();
14 }
15 createUser(email, password) {
16 return new Promise((resolve, reject) => {
17 this.firebase.createUserWithEmailAndPassword(email, password).then((\
18 result) => {
19 let user = new User(result);
20 user.email = user.email || email; // Because firebase result doe\
21 sn't provide the email
22 resolve(user);
23 });
24 });
25 }
26 }

On successful creation of the user account, this user will also be signed. User account creation can
fail if the account already exists or the password is invalid.
Firebase 21

Note
The email address acts as a unique identifier for the user and enables an email-based
password reset. This function will create a new user account and set the initial user
password.

If the new account was created, the user is signed in automatically. Have a look at below to get the
signed in user details the similar to creating a new account. To
user signs in to app, pass the user’s email address and password to signInWithEmailAndPassword
method.

Listing-24: auth.js
1 import { inject } from 'aurelia-framework';
2 import Promise from 'bluebird';
3 import Firebase from 'firebase';
4 import { Configuration } from './configuration';
5 import { User } from './user';
6
7 @inject(Configuration, Firebase)
8 export class Auth {
9 constructor(configuration, firebase) {
10 var app = firebase.initializeApp(configuration.getValue('config'), confi\
11 guration.getValue('name'));
12 this.firebase = app.auth();
13 this.currentUser = new User();
14 }
15 // ...
16 signIn(email, password) {
17 return new Promise((resolve, reject) => {
18 this.firebase.signInWithEmailAndPassword(email, password).then(resul\
19 t => {
20 let user = new User(result);
21 this.currentUser = user;
22 resolve(user);
23 });
24 });
25 }
26 }

After a user signs in for the first time, a new user account is created and linked to the credentials—
that is, the user name and password, or auth provider information—the user signed in with. This
new account is stored as part of Firebase project, and can be used to identify a user across every app
in project, regardless of how the user signs in.
Firebase 22

• In application, the recommended way to know the auth status of your user is to set an observer
on the Auth object. You can then get the user’s basic profile information from the User object.
• In Firebase Realtime Database and Firebase Storage Security Rules, you can get the signed-in
user’s unique user ID from the auth variable, and use it to control what data a user can access.

To sign out a user, call signOut.

Listing-25: auth.js

1 import { inject } from 'aurelia-framework';


2 import Promise from 'bluebird';
3 import Firebase from 'firebase';
4 import { Configuration } from './configuration';
5 import { User } from './user';
6
7 @inject(Configuration, Firebase)
8 export class Auth {
9 constructor(configuration, firebase) {
10 var app = firebase.initializeApp(configuration.getValue('config'), confi\
11 guration.getValue('name'));
12 this.firebase = app.auth();
13 this.currentUser = new User();
14 }
15 // ...
16 signOut() {
17 return new Promise((resolve, reject) => {
18 this.firebase.signOut().then(result => {
19 this.currentUser.reset();
20 resolve();
21 });
22 });
23 }
24 }

Other methods are similar to creating a new account as you can see below:
Firebase 23

Listing-26: auth.js

1 import { inject } from 'aurelia-framework';


2 import Promise from 'bluebird';
3 import Firebase from 'firebase';
4 import { Configuration } from './configuration';
5 import { User } from './user';
6
7 @inject(Configuration, Firebase)
8 export class Auth {
9 constructor(configuration, firebase) {
10 var app = firebase.initializeApp(configuration.getValue('config'), confi\
11 guration.getValue('name'));
12 this.firebase = app.auth();
13 this.currentUser = new User();
14 }
15 createUser(email, password) {
16 return new Promise((resolve, reject) => {
17 this.firebase.createUserWithEmailAndPassword(email, password).then((\
18 result) => {
19 let user = new User(result);
20 user.email = user.email || email; // Because firebase result doe\
21 sn't provide the email
22 resolve(user);
23 });
24 });
25 }
26 createUserAndSignIn(email, password) {
27 return this.createUser(email, password).then(() => {
28 return this.signIn(email, password);
29 });
30 }
31 signIn(email, password) {
32 return new Promise((resolve, reject) => {
33 this.firebase.signInWithEmailAndPassword(email, password).then(resul\
34 t => {
35 let user = new User(result);
36 this.currentUser = user;
37 resolve(user);
38 });
39 });
40 }
41 getCurrentUser() {
Firebase 24

42 this.firebase.currentUser;
43 }
44 changeEmail(oldEmail, newEmail) {
45 return new Promise((resolve, reject) => {
46 var user = this.getCurrentUser();
47 user.updateEmail(newEmail).then(() => {
48 this.currentUser.email = newEmail;
49 let result = { oldEmail, newEmail };
50 resolve(result);
51 });
52 });
53 }
54 changePassword(oldPassword, newPassword) {
55 return new Promise((resolve, reject) => {
56 var user = this.getCurrentUser();
57 user.updatePassword(newPassword).then(() => {
58 let result = { email: email };
59 resolve(result);
60 });
61 });
62 }
63 deleteUser(email: string, password: string): Promise {
64 return new Promise((resolve, reject) => {
65 var user = this.getCurrentUser();
66 user.delete().then(() => {
67 resolve();
68 });
69 });
70 }
71 }

Let’s see how it works on Aurelia.


I am going to start by defining the needed routes, which are the mappings between specific URLs
and the views that should be displayed when the browser navigates to that URL. The first two will
map the index and signup URLs to the app.html and signup.html views, respectively.
Firebase 25

Listing-27: app.js

1 import 'bootstrap/css/bootstrap.min.css!';
2 export class App {
3 configureRouter(config, router) {
4 config.title = 'Aurelia on Fire';
5 config.addPipelineStep('authorize', AuthorizeStep);
6 config.map([
7 { route: ['', 'dashboard'], name: 'dashboard', moduleId: 'dashboard/\
8 index', nav: true, title: 'Dashboard' },
9 { route: ['account/signup'], name: 'accountSignup', moduleId: 'accou\
10 nt/signup', title: 'Sign up' },
11 { route: ['account', 'account/index'], name: 'accountIndex', moduleI\
12 d: 'account/index', title: 'Account', auth: true }
13 ]);
14 this.router = router;
15 }
16 }

In the router config function, you can specifify an auth property in the routing map indicating wether
or not the user needs to be authenticated in order to access the route. In the above the accountIndex
route is only available for authenticated users.
Let’s start at the top and setup the navigation bar.

Listing-28: navbar identity nav-bar.html

1 <template>
2 <ul if.bind="!user.isAuthenticated" class="nav navbar-nav navbar-right">
3 <li>
4 <p class="navbar-text">Welcome, Anonymous !</p>
5 </li>
6 <li><a route-href="route: accountSignin">Sign in</a></li>
7 <li><a route-href="route: accountSignup">Register</a></li>
8 </ul>
9 <ul if.bind="user.isAuthenticated" class="nav navbar-nav navbar-right">
10 <li class="dropdown">
11 <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" a
12 -haspopup="true" aria-expanded="false">${user.email} <span class="caret"></span>\
13 </a>
14 <ul class="dropdown-menu">
15 <li><a route-href="route: accountIndex">Account</a></li>
16 <li role="separator" class="divider"></li>
Firebase 26

17 <li><a click.delegate="signOut()" href="#">Sign out</a></li>


18 </ul>
19 </li>
20 </ul>
21 </template>

Notice here that we’re running a filter on the repeated navigation items with authFilter:
isAuthenticated. This allows us to hide any nav menu items that are to be protected if the user
isn’t authenticated, and this is how we will hide the super-secret-quote menu item when the user
isn’t logged in. We’re also conditionally showing the Signup, Login, and Logout links.

SignUp
If you guessed that we need to create a signup.js and signup.html file, you are correct. Here’s the
source:

Listing-29: signup view model


1 import { inject } from 'aurelia-framework';
2 import { Router } from 'aurelia-router';
3 import { Auth } from 'resources/index';
4 @inject(Auth, Router)
5 export class SignUp {
6 email = null;
7 password = null;
8 message = null;
9 constructor(authManager, router) {
10 this.aureliaFirebase = authManager;
11 this.router = router;
12 }
13 signUp() {
14 this.aureliaFirebase.createUserAndSignIn(this.email, this.password)
15 .then(() => {
16 this.router.navigateToRoute('accountIndex');
17 })
18 .catch((e) => {
19 this.message = e.message;
20 });
21 }
22 }

The signup view model will speak directly with the auth plugin service, which is made available
via constructor injection. The signup() method uses Auth to send a POST request to the API, which
either creates a new user or returns an error if there was a problem.
Firebase 27

Listing-30: signup view

1 <template>
2 <section class="au-enter-active">
3 <div class="page-header">
4 <h1>Sign up</h1>
5 </div>
6 <form role="form" submit.trigger="signUp()">
7 <div class="form-group">
8 <label for="email">Email address</label>
9 <input type="email" class="form-control" id="email" placeholder="E
10 e.bind="email">
11 </div>
12 <div class="form-group">
13 <label for="password">Password</label>
14 <input type="password" class="form-control" id="password" placehol
15 ord" value.bind="password">
16 </div>
17 <div if.bind="message" class="alert alert-danger">${message}</div>
18 <input type="submit" value="Sign up" class="btn btn-default" />
19 </form>
20 </section>
21 </template>

In this view, we’re providing two <input>s that take the user’s email and password. We’ve also got
an alert box at the bottom to show the user any errors that are returned.
Next, let’s set up the signin, signout and resetPassword routes.

SignIn
The signin route is pretty similar. You’ll just need to swap out submit.delegate="signup()" for
submit.delegate="signin()" and adjust the other pieces of markup appropriately.

The JavaScript for signin looks similar as well.


Firebase 28

Listing-31: signin view model

1 import { inject } from 'aurelia-framework';


2 import { Router } from 'aurelia-router';
3 import { Auth } from 'resources/index';
4 @inject(Auth, Router)
5 export class SignIn {
6 email = null;
7 password = null;
8 message = null;
9 constructor(auth, router) {
10 this.aureliaFirebase = auth;
11 this.router = router;
12 }
13 signIn() {
14 this.message = null;
15 this.aureliaFirebase.signIn(this.email, this.password)
16 .then(() => {
17 this.router.navigateToRoute('accountIndex');
18 })
19 .catch((e) => {
20 this.message = e.message;
21 });
22 }
23 }

In the above code, after signing user, change the route to accountIndex. But we should check the
user is authenticated in the route config. Let’s see how that works:

Listing-32: app.js

1 //...
2 import { Auth, Configuration } from 'resources/index';
3
4 //...
5
6 @inject(Auth, Configuration)
7 class AuthorizeStep {
8 constructor(authManager, config) {
9 this.authManager = authManager;
10 this.fbConfig = config;
11 }
12 run(navigationInstruction, next) {
Firebase 29

13 // Check if the route has an "auth" key


14 // Then check if the current authenticated user is valid
15 if (navigationInstruction.getAllInstructions().some(i => i.config.auth))\
16 {
17 if (!this.authManager || !this.authManager.currentUser || !this.auth\
18 Manager.currentUser.isAuthenticated) {
19 return next.cancel(new Redirect(this.fbConfig.getLoginRoute()));
20 }
21 }
22 return next();
23 }
24 }

SignOut
The signout route essentially follows the same pattern using .signout()

‘Listing-33SignOutinnav-bar.html’

1 signOut() {
2 this.aureliaFirebase.signOut().then(() => {
3 this.router.navigateToRoute('dashboard');
4 });
5 }

Change Password and Change Email


The changePassword and the changeEmail has the same pattern using updatePassword and
updateEmail.

For another methods you can see Firebase docs3

Firebase ReactiveCollection
The ReactiveCollection class handles firebase data synchronization. To use it, first you must
create a file in resource directory as collection.js therefore you just need to create a new
ReactiveCollection passing its constructor the firebase data location relative to the Firebase Url
you provided at the plugin initialization.

3
https://firebase.google.com/docs/auth/
Firebase 30

Listing-34: collection.js

1 import Promise from 'bluebird';


2 import Firebase from 'firebase';
3 import {Configuration} from './configuration';
4
5 export class ReactiveCollection {
6 constructor(){
7
8 }
9 }

For initial ReactiveCollection class you need to instance of Configuration because for extend
this class you must have a new instance for implementing methods. First you must doing import
Container from aurelia-dependency-injection, then use it to get instance of Configuration.

Listing-35: collection.js

1 import Promise from 'bluebird';


2 import Firebase from 'firebase';
3 import {Container} from 'aurelia-dependency-injection';
4 import {Configuration} from './configuration';
5
6 export class ReactiveCollection {
7 constructor(){
8 if (!Container || !Container.instance) throw Error('Container has not be\
9 en made global');
10 let config = Container.instance.get(Configuration);
11 if (!config) throw Error('Configuration has not been set');
12 }
13 }

Now you need to make a name for table to communicate with the database that you can use
constructor. By doing this you will be able to using Firebase storage API.

‘Listing-36:collection.js’

1 constructor(path: Array<string>){ ... }

To get an instance of Firebase you should receive it from Aurelia Container and use the database()
the database service.
Firebase 31

‘Listing-37:collection.js’

1 constructor(path: Array<string>) {
2 //...
3 var firebase = Container.instance.get(Firebase);
4 // You can use application name instead '[0]' to manage many applications
5 this.database = firebase.apps[0].database()
6 this._query = this.database.ref(path);
7 }

Realtime Database
You’re ready to start using the Firebase Realtime Database!

Firebase data is retrieved by attaching an asynchronous listener to a firebase.database.Reference.


The listener is triggered once for the initial state of the data and again anytime the data changes.
Now that you have access to reference by path you can use ChildEventListener insterface. Classes
implementing this interface can be used to receive events about changes in the child locations of a
given Firebase ref. Attach the listener to a location using Query.addChildEventListener(ChildEventListener)
and the appropriate method will be triggered when changes occur.

‘Listing-38’

1 constructor(){
2 //...
3 this._listenToQuery(this._query);
4 }

Listing-39: collection.js

1 //...
2 _listenToQuery(query) {
3 let that = this;
4 query.on('child_added', (snapshot, previousKey) => {
5 that._onItemAdded(snapshot, previousKey);
6 });
7 query.on('child_removed', (snapshot) => {
8 that._onItemRemoved(snapshot);
9 });
10 query.on('child_changed', (snapshot, previousKey) => {
11 that._onItemChanged(snapshot, previousKey);
Firebase 32

12 });
13 query.on('child_moved', (snapshot, previousKey) => {
14 that._onItemMoved(snapshot, previousKey);
15 });
16 }
17 //...

• Child Added: This method is triggered when a new child is added to the location to which
this listener was added.
• Child Removed: This method is triggered when a child is removed from the location to which
this listener was added.
• Child Changed: This method is triggered when the data at a child location has changed.
• Child Moved: This method is triggered when a child location’s priority changes.

All of the above have snapshot parameter, that is an immutable snapshot of the data. The
previousKey is the key name of sibling location ordered before the child, the new child or the child
location. Note that in child_added and child_changed, previousKey will be null for the first child
node of a location and into child_moved it will be null if this location is ordered first.
Let’s get started to create methods for working with data. First, to create Add method as below:

Listing-40: collection.js
1 //...
2 add(item): Promise {
3 return new Promise((resolve, reject) => {
4 let query = this._query.push();
5 query.set(item, (error) => {
6 if (error) {
7 reject(error);
8 return;
9 }
10 resolve(item);
11 });
12 });
13 }
14 //...

Note
In add method you must use the push() function. if you don’t use it, your item will be
updated.

When item was created or was updated, child_added or child_changed runs. so:
Firebase 33

Listing-41: collection.js

1 //...
2 _onItemAdded(snapshot, previousKey) {
3 let value = this._valueFromSnapshot(snapshot);
4 let index = previousKey !== null ?
5 this.items.indexOf(this._valueMap.get(previousKey)) + 1 : 0;
6 this._valueMap.set(value.__firebaseKey__, value);
7 this.items.splice(index, 0, value);
8 }
9 //...

The saved value in _onItemAdded will be get with the help of _valueFromSnapshot and after its
index check, it will be held in items list in _valueFromSnapshot function key or the id assigned to
firebase will be added to item.

Listing-42: collection.js

1 _valueFromSnapshot(snapshot) {
2 let value = snapshot.val();
3 if (!(value instanceof Object)) {
4 value = {
5 value: value,
6 __firebasePrimitive__: true
7 };
8 }
9 value.__firebaseKey__ = snapshot.key;
10 return value;
11 }

The next step is to create the Remove method as below:

Listing-43: collection.js

1 remove(item): Promise {
2 if (item === null || item.__firebaseKey__ === null) {
3 return Promise.reject({ message: 'Unknown item' });
4 }
5 return this.removeByKey(item.__firebaseKey__);
6 }
7
8 removeByKey(key) {
9 return new Promise((resolve, reject) => {
Firebase 34

10 this._query.ref().child(key).remove((error) => {
11 if (error) {
12 reject(error);
13 return;
14 }
15 resolve(key);
16 });
17 });
18 }

For stop listen to query you can use this method:

Listing-44: collection.js

1 _stopListeningToQuery(query) {
2 query.off();
3 }

See the complete class below:

Listing-45: collection.js

1 import Promise from 'bluebird';


2 import Firebase from 'firebase';
3 import { Container } from 'aurelia-dependency-injection';
4 import { Configuration } from './configuration';
5
6 export class ReactiveCollection {
7
8 _query = null;
9 _valueMap = new Map();
10 items = [];
11
12 constructor(path: Array<string>) {
13 if (!Container || !Container.instance) throw Error('Container has not be\
14 en made global');
15 let config = Container.instance.get(Configuration);
16 if (!config) throw Error('Configuration has not been set');
17 var firebase = Container.instance.get(Firebase);
18 // You can use application name instead '[0]' to manage many applications
19 this.database = firebase.apps[0].database();
20 this._query = this.database.ref(path);
21 this._listenToQuery(this._query);
Firebase 35

22 }
23
24 add(item: any): Promise {
25 return new Promise((resolve, reject) => {
26 let query = this._query.push();
27 query.set(item, (error) => {
28 if (error) {
29 reject(error);
30 return;
31 }
32 resolve(item);
33 });
34 });
35 }
36
37 remove(item: any): Promise {
38 if (item === null || item.__firebaseKey__ === null) {
39 return Promise.reject({ message: 'Unknown item' });
40 }
41 return this.removeByKey(item.__firebaseKey__);
42 }
43
44 getByKey(key): any {
45 return this._valueMap.get(key);
46 }
47
48 removeByKey(key) {
49 return new Promise((resolve, reject) => {
50 this._query.ref().child(key).remove((error) => {
51 if (error) {
52 reject(error);
53 return;
54 }
55 resolve(key);
56 });
57 });
58 }
59
60 clear() {
61 return new Promise((resolve, reject) => {
62 let query = this._query.ref();
63 query.remove((error) => {
Firebase 36

64 if (error) {
65 reject(error);
66 return;
67 }
68 resolve();
69 });
70 });
71 }
72
73 _listenToQuery(query) {
74 let that = this;
75 query.on('child_added', (snapshot, previousKey) => {
76 that._onItemAdded(snapshot, previousKey);
77 });
78 query.on('child_removed', (snapshot) => {
79 that._onItemRemoved(snapshot);
80 });
81 query.on('child_changed', (snapshot, previousKey) => {
82 that._onItemChanged(snapshot, previousKey);
83 });
84 query.on('child_moved', (snapshot, previousKey) => {
85 that._onItemMoved(snapshot, previousKey);
86 });
87 }
88
89 _stopListeningToQuery(query) {
90 query.off();
91 }
92
93 _onItemAdded(snapshot, previousKey) {
94 let value = this._valueFromSnapshot(snapshot);
95 let index = previousKey !== null ?
96 this.items.indexOf(this._valueMap.get(previousKey)) + 1 : 0;
97 this._valueMap.set(value.__firebaseKey__, value);
98 this.items.splice(index, 0, value);
99 }
100
101 _onItemRemoved(oldSnapshot) {
102 let key = oldSnapshot.key;
103 let value = this._valueMap.get(key);
104 if (!value) {
105 return;
Firebase 37

106 }
107 let index = this.items.indexOf(value);
108 this._valueMap.delete(key);
109 if (index !== -1) {
110 this.items.splice(index, 1);
111 }
112 }
113
114 _onItemChanged(snapshot, previousKey) {
115 let value = this._valueFromSnapshot(snapshot);
116 let oldValue = this._valueMap.get(value.__firebaseKey__);
117
118 if (!oldValue) {
119 return;
120 }
121
122 this._valueMap.delete(oldValue.__firebaseKey__);
123 this._valueMap.set(value.__firebaseKey__, value);
124 this.items.splice(this.items.indexOf(oldValue), 1, value);
125 }
126
127 _onItemMoved(snapshot, previousKey) {
128 let key = snapshot.key;
129 let value = this._valueMap.get(key);
130 if (!value) {
131 return;
132 }
133 let previousValue = this._valueMap.get(previousKey);
134 let newIndex = previousValue !== null ? this.items.indexOf(previousValue\
135 ) + 1 : 0;
136 this.items.splice(this.items.indexOf(value), 1);
137 this.items.splice(newIndex, 0, value);
138 }
139
140 _valueFromSnapshot(snapshot) {
141 let value = snapshot.val();
142 if (!(value instanceof Object)) {
143 value = {
144 value: value,
145 __firebasePrimitive__: true
146 };
147 }
Firebase 38

148 value.__firebaseKey__ = snapshot.key;


149 return value;
150 }
151
152 static _getChildLocation(root: string, path: Array<string>) {
153 if (!path) {
154 return root;
155 }
156 if (!root.endsWith('/')) {
157 root = root + '/';
158 }
159 return root + (Array.isArray(path) ? path.join('/') : path);
160 }
161 }

Let’s get started to use the Firebase plugin on Aurelia, First we want to show the list of todo. Then
create a file called todo.js in collections directory and it will extends ReactiveCollection.

Listing-46: collection.js

1 import { Auth, ReactiveCollection } from 'resources/index';


2
3 @inject(Auth)
4 export class TodoCollection extends ReactiveCollection {
5 _user = null;
6 constructor(auth) {
7 super('todos');
8 this._user = authManager.currentUser;
9 }
10 }

To manage user data you can inject Auth in your class and to use todo collection enough inject it
to ReactiveCollection by super method. Now you ready to use todo collection. To display a list
of tasks you define a function as orderedItems(). Firebase due to use of the Realtime Database
you need to use Dirty Binding. The bindings for items are primitive bindings - in other words,
when Aurelia binds the html to the property, it creates an observer on the property so that when
the property is changed, a notification of the change is triggered. It’s all done under the covers
for you so you can just assume that any primitive on any model will trigger change notifications to
anything bound to them.
However, if you’re not using a primitive, then Aurelia has to fall-back on dirty binding.
Essentially it sets up a polling on the object (every 120ms).
Firebase 39

‘Listing-47:todo.js’

1 // **this property will require dirty-checking**


2 get orderedItems() { //... }

One problem with dirty-checking is that unless you actually check for a changed value you will
fire too many updates on an underlying bound control. To avoid dirty-checking use computedFrom
decorator.

‘Listing-48:todo.js’

@computedFrom('items')
get orderedItems() { //... }

orderedItemsarranges list by timestamp then returns it:

Listing-49: todo.js

1 @computedFrom('items')
2 get orderedItems() {
3 console.log('ordering');
4 return this.items.sort((item1, item2) => {
5 if (item1.timestamp < item2.timestamp) {
6 return -1;
7 }
8 if (item2.timestamp > item2.timestamp) {
9 return 1;
10 }
11 return 0;
12 });
13 }

For add method you will have:


Firebase 40

Listing-50: todo.js

1 add(text) {
2 if (!this._user || !this._user.isAuthenticated) {
3 return Promise.reject({ message: 'Authentication is required' });
4 }
5 if (!text) {
6 return Promise.reject({ message: 'A Todo message is required' });
7 }
8
9 return super.add({
10 ownerId: this._user.uid,
11 ownerProfileImageUrl: this._user.profileImageUrl,
12 text: text,
13 timestamp: Math.floor(Date.now() / 1000),
14 isCompleted: false
15 });
16 }

With the above functions you will be able to manage your todo collection in the ViewModel. It’s
important to note that, in order to use these methods, you must be logged in.
The next thing to do is create the index.js file in the todo directory.

Listing-51: index.js

1 import { inject, computedFrom } from 'aurelia-framework';


2 import { TodoCollection } from '../collections/todo';
3 import { Auth, User } from 'resources/index';
4
5 @inject(Auth, TodoCollection)
6 export class TodoIndex {
7 user = null;
8 collection = null;
9 todoText = null;
10 message = null;
11 selectedStateFilter = null;
12 selectedOwnerFilter = null;
13
14 constructor(authManager, collection: TodoCollection) {
15 this.user = authManager.currentUser;
16 this.collection = collection;
17 }
Firebase 41

18
19 add() {
20 this.collection.add(this.todoText).then(() => {
21 this.message = null;
22 this.todoText = null;
23 })
24 .catch((e) => {
25 this.message = e.message;
26 });
27 }
28
29 @computedFrom('collection.items', 'selectedStateFilter', 'selectedOwnerFilte\
30 r')
31 get filteredItems() {
32 let items = this.collection.items;
33 if (!this.selectedStateFilter && !this.selectedOwnerFilter) {
34 return items;
35 }
36
37 // Filter by owner
38 if (this.selectedOwnerFilter) {
39 items = items.filter((item) => {
40 return item.ownerId === this.user.uid;
41 }, this);
42 }
43
44 // Filter by status
45 if (this.selectedStateFilter) {
46 items = items.filter((item) => {
47 return item.isCompleted === (this.selectedStateFilter === 'compl\
48 eted');
49 }, this);
50 }
51 return items;
52 }
53 }

Ok. Now that you have a view-model with some basic data and behavior, let’s use the following
HTML to create the view:
Firebase 42

Listing-52: index.html

1 <template>
2 <section class="au-enter-active">
3 <div class="page-header">
4 <h1>Todo list</h1>
5 </div>
6 <div if.bind="!user.isAuthenticated" class="alert alert-info" role="aler\
7 t">
8 You must be <a route-href="route: accountSignin" class="alert-link">\
9 authenticated</a> to add a Todo
10 </div>
11 <div class="col-sm-offset-2 col-sm-8">
12 <form if.bind="user.isAuthenticated" class="form" submit.trigger="ad\
13 d()">
14 <div class="input-container input-group input-group-lg">
15 <input type="text" class="form-control" placeholder="What ne\
16 ed to be done ?" value.bind="todoText">
17 <span class="input-group-btn">
18 <button class="btn btn-primary" type="submit">Add task</button>
19 </span>
20 </div>
21 </form>
22 <div if.bind="message" class="alert alert-danger">
23 ${message}
24 </div>
25 <div class="filters btn-toolbar">
26 <div class="btn-group" data-toggle="buttons">
27 <label class="btn btn-default active" click.trigger="selecte\
28 dStateFilter = null">
29 <input type="radio" name="selectedStateFilter" autocomplete="off" ch\
30 ecked> All
31 </label>
32 <label class="btn btn-default" click.trigger="selectedStateF\
33 ilter = 'active'">
34 <input type="radio" name="selectedStateFilter" autocomplete="off"> A\
35 ctive
36 </label>
37 <label class="btn btn-default" click.trigger="selectedStateF\
38 ilter = 'completed'">
39 <input type="radio" name="selectedStateFilter" autocomplete="off"> C\
40 ompleted
41 </label>
Firebase 43

42 </div>
43 <div if.bind="user.isAuthenticated" class="btn-group" data-toggl\
44 e="buttons">
45 <label class="btn btn-default active" click.trigger="selecte\
46 dOwnerFilter = null">
47 <input type="radio" autocomplete="off" name="selectedOwnerFilter" ch\
48 ecked> Everyone's
49 </label>
50 <label class="btn btn-default" click.trigger="selectedOwnerF\
51 ilter = 'me'">
52 <input type="radio" autocomplete="off" name="selectedOwnerFilter"> M\
53 ine
54 </label>
55 </div>
56 <div class="counter">
57 <strong>${filteredItems.length} tasks</strong>
58 </div>
59 </div>
60 <div class="todo-list" style="margin-bottom: 20px;">
61 <div class="au-stagger">
62 <div class="todo-container au-animate" repeat.for="item of f\
63 ilteredItems">
64 <div class="todo row clearfix">
65 <div class="avatar col-sm-2">
66 <div class="moment">${$parent.fromNow(item.times\
67 tamp)}</div>
68 </div>
69 <div class="description col-sm-10">${item.text}</div>
70 </div>
71 </div>
72 </div>
73 </div>
74 </div>
75 </section>
76 </template>

For display the computed value the solution is much simpler- do this in above view.

Enabling Offline Capabilities


Firebase applications work even if your app loses its network connection temporarily.
Firebase 44

Managing Presence

In real-time applications, it is often useful to detect when clients connect and disconnect. For
example, we may want to mark a user as offline when their client disconnects.
Firebase Database clients provide simple primitives that allow data to be written to the database
when a client disconnects from the Firebase Database servers. These updates will occur whether the
client disconnects cleanly or not, so we can rely on them to clean up data even if a connection is
dropped or a client crashes. All write operations, including setting, updating, and removing, can be
performed upon a disconnection.
Here is a simple example of writing data upon disconnection by using the onDisconnect primitive:

‘Listing-53:disconnectmessage’

1 var presenceRef = firebase.database().ref("disconnectmessage");


2 // Write a string when this client loses connection
3 presenceRef.onDisconnect().set("I disconnected!");

How onDisconnect Works

When an onDisconnect() operation is established, it lives on the Firebase Realtime Database server.
The server checks security to make sure the user can perform the write event requested and informs
the client if it is invalid. The server then monitors the connection. If at any point it times out or is
actively closed by the client, the server checks security a second time (to make sure the operation is
still valid) and then invokes the event.
The client can use the callback on the write operation to ensure the onDisconnect was correctly
attached:

‘Listing-54:onDisconnect’

1 presenceRef.onDisconnect().remove(function(err) {
2 if (err) {
3 console.error('could not establish onDisconnect event', err);
4 }
5 });

An onDisconnect event can also be canceled by calling .cancel():


Firebase 45

‘Listing-55:cancellmethod’

1 var onDisconnectRef = presenceRef.onDisconnect();


2 onDisconnectRef.set('I disconnected');
3 // some time later when we change our minds
4 onDisconnectRef.cancel();

Detecting Connection State

For many presence-related features, it is useful for a client to know when it is online or offline.
Firebase Realtime Database clients provide a special location at /.info/connected which is updated
every time the client’s connection state changes. Here is an example:

‘Listing-56:connectionstate’

1 var connectedRef = firebase.database().ref(".info/connected");


2 connectedRef.on("value", function(snap) {
3 if (snap.val() === true) {
4 alert("connected");
5 } else {
6 alert("not connected");
7 }
8 });

/.info/connected is a boolean value which is not synchronized between clients because the value
is dependent on the state of the client. In other words, if one client reads /.info/connected as false,
this is no guarantee that a separate client will also read false.

Offline app shell

Making service worker for you.


Service worker is an event-driven Web Worker, which response to events dispatched from
documents and other sources. The service worker is a generic entry point for event-driven
background processing in the Web Platform that is extensible by other specifications.

Service workers are also intended to be used for such things as:

• Background data synchronization


• Responding to resource requests from other origins
Firebase 46

• Receiving centralized updates to expensive-to-calculate data such as geolocation or gyroscope,


so multiple pages can make use of one set of data
• Client-side compiling and dependency management of CoffeeScript, less, CJS/AMD modules,
etc. for dev purposes
• Hooks for background services
• Custom templating based on certain URL patterns
• Performance enhancements, for example, pre-fetching resources, that the user is likely to need
in the near future, such as the next few pictures in a photo album.

Service worker needs two things: - You need a secure connection, service worker only works with
SSL - Registering a service worker

You need HTTPS

During development you’ll be able to use service worker through localhost, but to deploy it on a site
you’ll need to have HTTPS setup on your server.
Using service worker you can hijack connections, fabricate, and filter responses. Powerful stuff.
While you would use these powers for good, a man-in-the-middle might not. To avoid this, you
can only register service workers on pages served over HTTPS, so we know the service worker the
browser receives hasn’t been tampered with during its journey through the network.

Firebase Hosting
Free custom domain with SSL

Registering a service worker

To register a service worker, you need to include a reference to the service worker JavaScript file in
your web page. You should check for service worker support first:

‘Listing-57:RegisteringServiceWorker’

1 if ('serviceWorker' in navigator) {
2 navigator.serviceWorker.register('/sw.js').then(function() {
3 // Success
4 }).catch(function() {
5 // Fail :(
6 });
7 }

Scope refers to the pages the service worker can control. In the above, the service worker can control
any under the root and its subdirectories.
Firebase 47

Lifecycle

The lifecycle of a service worker is a little bit more complex than this, but the two main events we
are interested in are

• Installing – perform some tasks at install time to prepare your service worker
• Activation – perform some tasks at activation time

Implicit in the above is that a service worker can be installed but not active. On first page-load, the
service worker will be installed, but it won’t be until the next page request that it actually takes
control of the page. This default behavior can be overridden, and a service worker can take control
of a page immediately by making calls to the skipWaiting() and clients.claim() methods.
Service workers can be stopped and restarted as they are needed. This means that global variables
don’t exist across restarts. If you need to preserve state then you should use IndexedDB, which is
available in service workers.
The other things you can use is a library called sw-precache. This is an open source node library the
basically gives you an easy way to generate your Service Worker javascript code that will precache
specific resources so they work offline. It integrates with your build process. Once configured, it
detects all your static resources (HTML, JavaScript, CSS, images, etc.) and generates a hash of each
file’s contents. Information about each file’s URL and versioned hash are stored in the generated
service worker file, along with logic to serve those files cache-first, and automatically keep those
files up to date when changes are detected in subsequent builds.
Serving your local static resources cache-first means that you can get all the crucial scaffolding for
your web app—your App Shell—on the screen without having to wait for any network responses.

Install sw-precache

Local build integration:

‘Listing-58:installingsw-precache’
1 $ npm install --save-dev sw-precache

Global command-line interface:

‘Listing-59:installingsw-precache’
1 $ npm install --global sw-precache

Usage

Let’s take a look how you cache static files. Here’s a simpler gulp example for a basic use case. It
assumes your site’s resources are located under app and that you’d like to cache all your JavaScript,
HTML, CSS, and image files.
Firebase 48

‘Listing-58:gulpfile.js’

1 gulp.task('generate-service-worker', function(callback) {
2 var path = require('path');
3 var swPrecache = require('sw-precache');
4 var rootDir = 'app';
5
6 swPrecache.write(path.join(rootDir, 'service-worker.js'), {
7 staticFileGlobs: [rootDir + '/**/*.{js,html,css,png,jpg,gif,svg,eot,\
8 ttf,woff}'],
9 stripPrefix: rootDir
10 }, callback);
11 });

This task will create app/service-worker.js, which your client pages need to register4 before it can
take control of your site’s pages. service-worker-registration.js5 is a ready-to- use script to handle
registration.
Or you can using this to cache static files:

‘Listing-59:gulpfile.js’

1 var paths = require('../paths');


2 var swPrecache = require('sw-precache');
3 var $ = require('gulp-load-plugins')();
4 var gulp = require('gulp');
5 gulp.task('generate-service-worker', function (callback) {
6 var config = {
7 cacheId: 'practical-aurelia',
8 /*
9 dynamicUrlToDependencies: {
10 'dynamic/page1': [
11 path.join(rootDir, 'views', 'layout.jade'),
12 path.join(rootDir, 'views', 'page1.jade')
13 ],
14 'dynamic/page2': [
15 path.join(rootDir, 'views', 'layout.jade'),
16 path.join(rootDir, 'views', 'page2.jade')
17 ]
18 },
19 */
4
https://slightlyoff.github.io/ServiceWorker/spec/service_worker/#navigator-service-worker-register
5
https://github.com/GoogleChrome/sw-precache/blob/master/demo/app/js/service-worker-registration.js
Firebase 49

20 // If handleFetch is false (i.e. because this is called from generat\


21 e-service-worker-dev), then
22 // the service worker will precache resources but won't actually ser\
23 ve them.
24 // This allows you to test precaching behavior without worry about t\
25 he cache preventing your
26 // local changes from being picked up during the development cycle.
27 handleFetch: false,
28 // logger: $.util.log,
29 runtimeCaching: [{
30 // See https://github.com/GoogleChrome/sw-toolbox#methods
31 urlPattern: /runtime-caching/,
32 handler: 'cacheFirst',
33 // See https://github.com/GoogleChrome/sw-toolbox#options
34 options: {
35 cache: {
36 maxEntries: 1,
37 name: 'runtime-cache'
38 }
39 }
40 }],
41 staticFileGlobs: [
42 paths.output + '/**/**.css',
43 paths.output + '/**/**.html',
44 paths.output + '/images/**.*',
45 paths.output + '/**/**.js'
46
47 ],
48 stripPrefix: paths.output,
49 // verbose defaults to false, but for the purposes of this demo, log\
50 more.
51 verbose: true
52 };
53
54 swPrecache.write(paths.output + 'service-worker.js', config, callback);
55 });

To install a service worker you need to kick start the process by registering it on your page. This
tells the browser where your service worker JavaScript file lives.
Firebase 50

‘Listing-60:registerserviceworker’

1 if ('serviceWorker' in navigator) {
2 navigator.serviceWorker.register('dist/service-worker.js').then(function\
3 (reg) {
4 // updatefound is fired if service-worker.js changes.
5 reg.onupdatefound = function() {
6 // The updatefound event implies that reg.installing is set; see
7 // https://slightlyoff.github.io/ServiceWorker/spec/service_work\
8 er/index.html#service-worker-container-updatefound-event
9 var installingWorker = reg.installing;
10
11 installingWorker.onstatechange = function() {
12 switch (installingWorker.state) {
13 case 'installed':
14 if (navigator.serviceWorker.controller) {
15 // At this point, the old content will have been\
16 purged and the fresh content will
17 // have been added to the cache.
18 // It's the perfect time to display a "New conte\
19 nt is available; please refresh."
20 // message in the page's interface.
21 console.log('New or updated content is available\
22 .');
23 } else {
24 // At this point, everything has been precached.
25 // It's the perfect time to display a "Content i\
26 s cached for offline use." message.
27 console.log('Content is now available offline!');
28 }
29 break;
30
31 case 'redundant':
32 console.error('The installing service worker became \
33 redundant.');
34 break;
35 }
36 };
37 };
38 }).catch(function(e) {
39 console.error('Error during service worker registration:', e);
40 });
41 }
Firebase 51

Your service-worker.js must be located at the top-level directory relative to your site. It won’t be
able to control pages unless it’s located at the same level or higher than them.

Warning
Don’t register service worker file in, e.g., a scripts/ subdirectory! see
https://github.com/slightlyoff/ServiceWorker/issues/4686

Cache Remote Files

It’s often desirable, even necessary to use precaching and runtime caching together. You may have
seen sw-toolbox7 tool, which handles runtime caching, and wondered how to use them together.
Fortunately, sw-precache handles this for you.
The sw-precache module has the ability to include the sw-toolbox code and configuration alongside
its own configuration. Using the runtimeCaching configuration option in sw-precache (see below) is
a shortcut that accomplishes what you could do manually by importing sw-toolbox in your service
worker and writing your own routing rules.

‘Listing-61:runtimecaching’

1 runtimeCaching: [{
2 // cache Google Web Fonts (both css and font files)
3 urlPattern: /^https:\/\/fonts.(?googleapis|gstatic)\.com\/.*/,
4 handler: 'cacheFirst'
5 }, {
6 // cache Google user profiles pics
7 urlPattern: /^https:\/\/lh3.googleusercontent.com\/.*/,
8 handler: 'networkFirst'
9 }
10 }]

This pattern is used:

6
https://github.com/slightlyoff/ServiceWorker/issues/468
7
https://github.com/GoogleChrome/sw-toolbox
Firebase 52

‘Listing-62:runtimecachingpattern’

1 runtimeCaching: [{
2 urlPattern: /^https:\/\/example\.com\/api/,
3 handler: 'networkFirst'
4 }, {
5 urlPattern: /\/articles\//,
6 handler: 'fastest',
7 options: {
8 cache: {
9 maxEntries: 10,
10 name: 'articles-cache'
11 }
12 }
13 }]

Now, you can add generate-service-worker task to build process and register service worker in
app.js or import register-service-worker.js in index.html

‘Listing-63:buildprocess’

1 gulp.task('build', function(callback) {
2 return runSequence(
3 'clean',
4 ['build-system', 'build-html', 'build-css', 'generate-service-worker\
5 '],
6 callback
7 );
8 });

Finally, we’re going to offline the app data in our application that you can use IndexedDB. Let me
add this feature in future.
Loopback
LoopBack is an open source Node.js framework built on top of Express optimized for building APIs
for mobile, web, and other devices. Connect to multiple data sources, write business logic in Node.js,
glue on top of your existing services and data, connect using JS, iOS & Android SDKs.
This section has the following chapters: - What is LoopBack? - Creating server as loopback backend
- Using generated REST API in Aurelia

What is Loopback?
IBM and the StrongLoop team are committed to maintaining and improving the LoopBack open-
source project!8
Building on LoopBack’s success as an open-source Node.js framework, IBM API Connect provides
the newest tools to use with LoopBack projects. It includes a graphical tool with many of the API
composition features of StrongLoop Arc, plus assembly and testing of API Gateway policies using
the local Micro Gateway. Some features are still available only in Arc but these will be added to API
Connect in the next few months.
API Connect also provides its own command-line tool, integrated with API management and
gateway features. A free version of API Connect especially for developers is available called API
Connect Essentials.
LoopBack is a highly-extensible, open-source Node.js framework that enables you to:

• Create dynamic end-to-end REST APIs with little or no coding.


• Access data from Oracle, MySQL, PostgreSQL, MS SQL Server, MongoDB, SOAP and other
REST APIs.
• Incorporate model relationships and access controls for complex APIs.
• Use built-in push, geolocation, and file services for mobile apps.
• Easily create client apps using Android, iOS, and JavaScript SDKs.
• Run your application on-premises or in the cloud.

LoopBack consists of:

• A library of Node.js modules.


8
https://github.com/strongloop/loopback/?_ga=1.38476711.587365487.1480407550
Loopback 54

• Yeoman9 generators for scaffolding applications.


• Client SDKs for iOS, Android, and web clients.

LoopBack tools include:

• Command-line tool slc loopback to create applications, models, data sources, and so on.
• StrongLoop Arc, a graphical tool for editing LoopBack applications; and for deploying and
monitoring applications.

LoopBack modules
The LoopBack framework is a set of Node.js modules that you can use independently or together.

Core

• loopback10
• loopback-datasource-juggler11
• strong-remoting12

Connectors

• loopback-connector-mongodb13
• loopback-connector-mysql14
• loopback-connector-postgresql15
• loopback-connector-rest16

Enterprise Connectors
• loopback-connector-oracle17
• loopback-connector-mssql18
• loopback-connector-soap19
• loopback-connector-atg20
9
http://yeoman.io/
10
https://github.com/strongloop/loopback
11
https://github.com/strongloop/loopback-datasource-juggler
12
https://github.com/strongloop/strong-remoting
13
https://github.com/strongloop/loopback-connector-mongodb
14
https://github.com/strongloop/loopback-connector-mysql
15
https://github.com/strongloop/loopback-connector-postgresql
16
https://github.com/strongloop/loopback-connector-rest
17
https://github.com/strongloop/loopback-connector-oracle
18
https://github.com/strongloop/loopback-connector-mssql
19
https://github.com/strongloop/loopback-connector-soap
20
https://github.com/strongloop/loopback-connector-atg
Loopback 55

Community Connectors
The LoopBack community has created and supports a number of additional connectors. See
Community connectors21 for details.

Components
• loopback-component-push22
• loopback-component-storage23
• loopback-component-passport24

Client SDKs
• loopback-sdk-ios25
• loopback-sdk-android26
• loopback-sdk-angular27
– loopback-sdk-angular-cli28
– grunt-loopback-sdk-angular29

Tools
• loopback-explorer30
• loopback-workspace31
• generator-loopback32

Creating server as loopback backend


This chapter will walk through the initial steps to create a basic LoopBack application. To make it
easy for you to pick up the tutorial at any point, there are tags for each step of the tutorial.
21
http://loopback.io/doc/en/lb2/Community-connectors.html
22
https://github.com/strongloop/loopback-component-push
23
https://github.com/strongloop/loopback-component-storage
24
https://github.com/strongloop/loopback-component-passport
25
https://github.com/strongloop/loopback-sdk-ios
26
https://github.com/strongloop/loopback-sdk-android
27
https://github.com/strongloop/loopback-sdk-angular
28
https://github.com/strongloop/loopback-sdk-angular-cli
29
https://github.com/strongloop/grunt-loopback-sdk-angular
30
https://github.com/strongloop/loopback-explorer
31
https://github.com/strongloop/loopback-workspace
32
https://github.com/strongloop/generator-loopback
Loopback 56

Installation
You can install LoopBack via either API Connect or StrongLoop.

Command-line tools
Although in theory, you could code a LoopBack application from scratch, installing
LoopBack tools makes it much easier to get started. They will scaffold an application
that you can then customize to suit your needs. For more information, see Command-line
tools33 .

StrongLoop’s Node API platform consists of:

• LoopBack, an open-source Node application framework based on Express34 .


• StrongLoop Process Manager35 and related devops tools for Node applications.

Here we create a new directory as application root directory. LoopBack project files and directories
are in the application root directory. Within this directory the standard LoopBack project structure
has three sub-directories:

• server - Node application scripts and configuration files.


• client - Aurelia application.
• common - Files common to client and server. The /models sub-directory contains all model
JSON and JavaScript files.

server directory is consists Node application files:

• /boot directory - Add scripts to perform initialization and setup.


• component-config.json - Specifies LoopBack components to load.
• config.json - Application settings.
• datasources.json - Data source configuration file.
• middleware.json - Middleware definition file.
• middleware.production.json - Middleware definition file with production configuration.
• model-config.json - Model configuration file.
• server.js - Main application program file.

You can see a full description in Here36 !


Before running the application you need to many npm requirements. These requirements consists of:
33
http://loopback.io/doc/en/lb2/Command-line-tools.html
34
http://expressjs.com/
35
https://strong-pm.io/
36
http://loopback.io/doc/en/lb2/server-directory.html
Loopback 57

‘Listing-64:package.json’
{
"name": "practical-aurelia-server",
"version": "1.0.0",
"main": "src/server/server.js",
"scripts": {
"start": "node .",
"pretest": "jshint ."
},
"dependencies": {
"compression": "^1.0.3",
"cors": "^2.5.2",
"loopback": "^2.22.0",
"loopback-boot": "^2.6.5",
"loopback-component-explorer": "^2.1.0",
"loopback-datasource-juggler": "^2.39.0",
"serve-favicon": "^2.0.1"
},
"devDependencies": {
"jshint": "^2.5.6"
},
"repository": {
"type": "",
"url": ""
}
}

Running The App


To run the app, follow these steps.

1. Ensure that NodeJS is installed. This provides the platform on which the build tooling runs.
2. From the project folder, execute the following command:
npm install
3. To run the app, execute the following command:
npm start
4. Browse to http://localhost:3000 to see the app. You’ll see the default application response
that displays some JSON with some status information; for example: {"started":"2016-09-
10T21:59:47.155Z","uptime":42.054}
5. Browse to http://localhost:3000/explorer to see the StrongLoop API Explorer.

Now, you have a new server. Let’s creating a model and working with database and use API.
Loopback 58

Creating models
Models are at the heart of LoopBack, and represent back-end data sources such as databases
or other back-end services (REST, SOAP, and so on).

A LoopBack model represents data in backend systems such as databases, and by default has both
Node and REST APIs. Additionally, you can add functionality such as validation rules and business
logic to models.
Every LoopBack application has a set of predefined built-in models such as User, Role, and
Application. You can extend built-in models to suit your application’s needs.
Additionally, you can define your own custom models specific to your application:

• Use the model generator to create custom models from scratch. This creates a Model definition
JSON file that defines your model in LoopBack.
• Use Datasource.buildModelFromInstance() to create dynamic schema-less models for data
sources such as SOAP and REST services.
• For data sources backed by a relational database, a model typically corresponds to a table. Use
model discovery to create static, schema-driven models for database-backed data sources.

Well, you can manually create a model. To create a model you need only create two files, Let’s create
a simple customer model. To do that we’ll need a customer.js and customer.json in common/models
directory.

‘Listing-65:customer.js’

1 module.exports = function(Customer) {
2
3 };

‘Listing-65:customer.json’

1 {
2 "name": "customer",
3 "plural": "customers",
4 "base": "PersistedModel",
5 "idInjection": true,
6 "options": {
7 "validateUpsert": true
8 },
9 "properties": {
10 "firstName": {
11 "type": "string",
Loopback 59

12 "required": true
13 },
14 "lastName": {
15 "type": "string",
16 "required": true
17 }
18 },
19 "validations": [],
20 "relations": {},
21 "acls": [],
22 "methods": []
23 }

Since you will eventually connect this model to a persistent data source in a database, use
PersistedModel as base.
PersistedModel37 is the base object for all models connected to a persistent data source such as a
database. See LoopBack core concepts for an overview of the model inheritance hierarchy.
Every model has properties. Right now, you’re going to define two properties, firstName and
lastName for the Customer model. Pay attention you can define the property type.
Each property can be optional or required. Even you can define a default value for the property.

Important
You must convert camel-case model names (for example MyModel) to lowercase dashed
names (my-model). For example, You create a model named “FooBar” and creates files
foo-bar.json and foo-bar.js in common/models. The model name (“FooBar”) will be
preserved via the model’s name property.

Through a set of simple steps using LoopBack, you’ve created a Customer model, specified its
properties and then exposed it through REST. One of the powerful advantages of LoopBack is that
it automatically generates a REST API for your model.
LoopBack applications come with a built-in API Explorer you can use to test REST API operations
during development. You’re not the only one who’ll use the API you just created. That means you’ll
need to document your API. Fortunately, LoopBack provides API Explorer for you.
Afte running the app go to http://localhost:3000/explorer. You’ll see the StrongLoop API Explorer
showing the two models this application has: Users and Customers.
In addition to the Customer model that you defined, by default Loopback generates the User model
and its endpoints for every application.
Actually, LoopBack creates several other built-in models for common use cases:
37
http://apidocs.strongloop.com/loopback/#persistedmodel
Loopback 60

• Application model - contains metadata for a client application that has its own identity and
associated configuration with the LoopBack server.
• User model - register and authenticate users of your app locally or against third-party services.
• Access control models - ACL, AccessToken, Scope, Role, and RoleMapping models for
controlling access to applications, resources, and methods.
• Email model (see email connector38 ) - send emails to your app users using SMTP or third-party
services.

The built-in models (except for Email) extend PersistedModel, so they automatically have a full
complement of create, update, and delete (CRUD) operations.
Now you need to exposed your model (Customer) over REST. To expose the models, first you must
define config for model in server/model-config.json, then change the model’s public property to
true.

‘Listing-66:model-config.json’

...
"customer": {
"public": true,
"dataSource": "db"
},
...

To “hide” the model’s REST API, simply change public to false.

Note
By default, only the User model is exposed over REST. To expose the other models, change
the model’s public property to true in server/model-config.json.

You can see a property as datasource in model-config.json indicates that you use the db
datasource. What is datasource?

Connect your API to a data source


LoopBack enables you to easily persist your data model to a variety of data sources without having
to write code.
Now you’re going to define a data source. To do that, adds the data source definition to the
server/datasources.json file, which will now look as shown below. Notice the in-memory data
source named “db,” which is there by default.
38
http://loopback.io/doc/en/lb2/Email-connector.html
Loopback 61

‘Listing-67:datasource.json’

{
"db": {
"name": "db",
"connector": "memory"
}
}

You can add your datasource as below:

‘Listing-68:datasource.json’

{
"db": {
"name": "db",
"connector": "memory"
},
"mysqlDs": {
"name": "mysqlDs",
"connector": "mysql"
}
}

Notice the “mysqlDs” data source you just added. Now add the loopback-connector-mysql module
and install the dependencies:
npm install loopback-connector-mysql --save

Important
If you have a MySQL database server that you can use, please use it. Create a new database
called “getting_started.” If you wish, you can use a different database name. Just make sure
the mysqlDs.database property in datasources.json matches it (see below). If not, you can
use the StrongLoop MySQL server running on demo.strongloop.com39 . However, be aware
that it is a shared resource. There is a small chance that two users may run the script that
creates sample data (see below) at the same time and may run into race condition. For this
reason, we recommend you use your own MySQL server if you have one

Next, you need configure the data source to use the desired MySQL server.
Edit /server/datasources.json and after the line "connector": "mysql" add host, port, database,
username, and password properties.
39
http://demo.strongloop.com/
Loopback 62

‘Listing-69:datasource.json’

{
"db": {
"name": "db",
"connector": "memory"
},
"mysqlDs": {
"name": "mysqlDs",
"connector": "mysql",
"host": "demo.strongloop.com",
"port": 3306,
"database": "getting_started",
"username": "demo",
"password": "L00pBack"
}
}

Now you’ve created a MySQL data source and you have a Customer model; you just need to connect
them. Edit /server/model-config.json and look for the customer entry:

‘Listing-70:model-config.json’

...
"customer": {
"dataSource": "mysqlDs",
"public": true
}
...

Change the dataSource property from db to mysqlDs. This attaches the Customer model to the
MySQL datasource you just created and configured.
Now you have a Customer model in LoopBack, how do you create the corresponding table in MySQL
database?
You could try executing some SQL statements directly…but LoopBack provides a Node API to do it
for you automatically using a process called auto-migration. For more information, see Creating a
database schema from models40 .
The loopback-getting-started module contains the create-sample-models.js script to demon-
strate auto-migration. If you’ve been following along from the beginning (and didn’t just clone
40
http://loopback.io/doc/en/lb2/Creating-a-database-schema-from-models
Loopback 63

this module), then you’ll need to copy it from below or from GitHub41 . Put it in the application’s
/server/boot directory so it will get executed when the application starts.

Note
The auto-migration script below is an example of a boot script that LoopBack executes
when an application initially starts up. Use boot scripts for initialization and to perform
any other logic your application needs to perform when it starts. See Defining boot scripts42
for more information.

‘Listing-71:/server/boot/create-sample-models.js’
1 module.exports = function(app) {
2 app.dataSources.mysqlDs.automigrate('customer', function(err) {
3 if (err) throw err;
4
5 app.models.CoffeeShop.create([{
6 firstName: 'Behzad',
7 lastName: 'Abbasi'
8 }, {
9 firstName: 'Vahid',
10 lastName: 'Saberi'
11 }, {
12 firstName: 'Samin',
13 lastName: 'Gvh'
14 }, ], function(err, customer) {
15 if (err) throw err;
16
17 console.log('Models created: \n', customer);
18 });
19 });
20 };

This will save some test data to the data source.

Note
The boot script containing the auto-migration command will run each time you run your
application. Since automigrate()43 first drops tables before trying to create new ones, it
won’t create duplicate tables.

Now run the application. In the console, you’ll see this:


41
https://github.com/strongloop/loopback-getting-started/blob/master/server/boot/create-sample-models.js
42
http://loopback.io/doc/en/lb2/Defining-boot-scripts
43
http://apidocs.strongloop.com/loopback-datasource-juggler/#datasource-prototype-automigrate
Loopback 64

… Browse your REST API at http://0.0.0.0:3000/explorer


Web server listening at: http://0.0.0.0:3000/
Models created: [ { firstName: ‘Behzad’,
lastName: ‘Abbasi’,
id: 1 },
{ firstName: ‘Vahid’,
lastName: ‘Saberi’,
id: 3 },
{ firstName: ‘Samin’,
lastName: ‘Gvh’,
id: 2 } ]

You can also use the API Explorer.


Let’s see how to install mongoDB as data source.

MongoDB Connector
The MongoDB connector enables LoopBack applications to connect to MongoDB data sources.
By default, examples and tests from this module assume there is a MongoDB server instance running
on localhost at port 27017. To customize the settings, you can drop in a .loopbackrc file to the root
directory of the project or the home folder.

Note
Note: Tests and examples in this project configure the data source using the deprecated
.loopbackrc file method, which is not suppored in general. For information on configuring
the connector in a LoopBack application, please refer to loopback.io44 .

The .loopbackrc file is in JSON format, for example:

44
http://loopback.io/doc/en/lb2/MongoDB-connector.html
Loopback 65

‘Listing-72:.loopbackrc’

{
"dev": {
"mongodb": {
"host": "127.0.0.1",
"database": "test",
"username": "youruser",
"password": "yourpass",
"port": 27017
}
},
"test": {
"mongodb": {
"host": "127.0.0.1",
"database": "test",
"username": "youruser",
"password": "yourpass",
"port": 27017
}
}
}

Notice that you must install mongoDB connector.


npm install --save loopback-connector-mongodb

Then, as usual before you must create a data source that uses the MongoDB connector. For
the purposes of this practice, we will use a preconfigured StrongLoop MongoDB server. Edit
server/datasources.json to set the MongoDB configs:

‘Listing-73:datasource.json’

{
"db": {
"name": "mongoDS",
"connector": "mongodb",
"host": "127.0.0.1",
"database": "practicalAureliaDB",
"port": 27017
}
}
Loopback 66

With the customer model configured, we can generate the corresponding MongoDB collection using
the info from the Customer metadata in common/models/customer.json via auto-migration.
Start by creating a dir to store general-purpose scripts:
mkdir bin

Inside that dir, create a script named automigrate.js. To create the Customer collection and create
two sample customers, run:
node bin/automigrate.js

Warning
The automigrate function creates a new collection if it doesn’t exist. If the collection already
exists, it will be destroyed and it’s data will be deleted. If you want to keep this data, use
autoupdate instead.

Using generated REST API in Aurelia

Create the application


Create main.js in the client for Aurelia configure and add plugins. In this practice you can use
aurelia-api plugin.

aurelia-api plugin

First, head on over to your favorite terminal and run jspm install aurelia-api from your project
root. This will install the module of which you’re reading the getting-started right now.
Second, in order for us to actually make calls to loopback server, we’ll have to configure an
endpoint.
Let’s configure a new endpoint by editing main.js:

‘Listing-74:main.js’

1 export function configure(aurelia) {


2 aurelia.use
3 .standardConfiguration()
4 // Uncomment this if you want some development logs
5 // .developmentLogging()
6 .plugin('aurelia-animator-css')
7 .plugin('aurelia-api', config => {
8 config.registerEndpoint('api', 'http://localhost:3000/api/');
Loopback 67

9 });
10 aurelia.start().then(() => aurelia.setRoot());
11 }

You should create customers directory in src, create require files for customer list named list.js,
add and edit customers as addOrEdit.js. Then define your routes as below:

‘Listing-75:app.js’

1 import 'bootstrap/css/bootstrap.min.css!';
2 import { inject } from 'aurelia-framework';
3
4 export class App {
5 configureRouter(config, router) {
6 config.title = 'Aurelia and Loopback';
7 config.map([
8 { route: ['', 'list'], name: 'list', moduleId: 'customers/list', nav: true\
9 , title: 'Customers List' },
10 { route: 'addorEdit:/id', name: 'addorEdit', moduleId: 'customers/addorEdi\
11 t', nav: true, title: 'Customer Add/Edit' }
12 ]);
13
14 this.router = router;
15 }
16 }

‘Listing-76:app.html’

1 <template>
2 <div class="container-fluid page-host">
3 <router-view></router-view>
4 </div>
5 </template>
Loopback 68

‘Listing-77:list.js’
1 import { inject } from 'aurelia-framework';
2 import { Endpoint } from 'aurelia-api';
3
4 @inject(Endpoint.of('api'))
5 export class List {
6 customers = [];
7
8 constructor(apiEndpoint) {
9 this.apiEndpoint = apiEndpoint;
10 }
11
12 activate() {
13 return this.apiEndpoint.find('customers')
14 .then(customers => this.customers = customers);
15 }
16 }

‘Listing-78:list.html’
1 <template>
2 <a class="btn btn-success" route-href="route: addorEdit; params.bind: {id:''\
3 }">New</a>
4 <table class="table table-striped table-hover">
5 <thead>
6 <tr>
7 <th>Firstname</th>
8 <th>Lastname</th>
9 <th>Edit</th>
10 </tr>
11 </thead>
12 <tbody>
13 <tr repeat.for="item of customers">
14 <td>${item.firstName}</td>
15 <td>${item.lastName}</td>
16 <td><a class="btn btn-success" route-href="route: addorEdit; par\
17 ams.bind: {id:item.id}">Edit</a> </td>
18 </tr>
19 </tbody>
20 </table>
21 </template>
Loopback 69

‘Listing-79:addOrEdit.js’

1 import { inject } from 'aurelia-framework';


2 import { Endpoint } from 'aurelia-api';
3 import { Router } from 'aurelia-router';
4
5 @inject(Endpoint.of('api'), Router)
6 export class AddOrEdit {
7 domain = {
8 firstName: '',
9 lastName: ''
10 }
11 id = '';
12
13 constructor(apiEndpoint, router) {
14 this.apiEndpoint = apiEndpoint;
15 this.router = router;
16 }
17
18 activate(params) {
19 if (params.id != '') {
20 this.id = params.id;
21 this.apiEndpoint.findOne('customers', this.id)
22 .then(customer => {
23 this.domain = customer
24 });
25 }
26 }
27
28 submit() {
29 this.apiEndpoint.update('customers', this.id, this.domain)
30 .then((res) => {
31 this.router.navigate('list');
32 console.log('Successful!')
33 })
34 .catch((err) => {
35 console.log('An error occured!')
36 });
37 }
38 }
Loopback 70

‘Listing-80:addorEdit.html’

1 <template>
2 <div class="row">
3 <form submit.trigger="submit()">
4 <div class="form-group">
5 <label>First Name</label>
6 <input type="text" value.bind="domain.firstName" />
7 </div>
8 <div class="form-group">
9 <label>Last Name</label>
10 <input type="text" value.bind="domain.lastName" />
11 </div>
12 <input type="submit" value="Save" />
13 </form>
14 </div>
15 </template>

For more information about aurelia-api, see aurelia-api-doc45


45
http://aurelia-api.spoonx.org/
Working with Apache Cordova
Cordova CLI lets you build Cordova applications for your smartphone with normal web technologies
via the command line. This book is a guide through all the pieces of the command line interface,
including how to apply software development best practices.
We want to use Aurelia on Cordova as you would in a regular browser.
This section has the following chapters: - What is Cordova? - Installing, Upgrading and Basic Usage
- Plugins - Platform Assets - Device Installation

What is Cordova?
Apache Cordova (formerly PhoneGap) is a popular mobile application development framework
originally created by Nitobi. Adobe Systems purchased Nitobi in 2011, rebranded it as PhoneGap,
and later released an open source version of the software called Apache Cordova.
Cordova ,formerly called as Phone Gap is a platform to build Native Mobile Applicatons using
HTML5, CSS and Java Script.
In other words it acts a container for running a web application written in HTML, CSS,JS Typically
Web applications cannot use the native device functionality like Camera, GPS, Accelerometer ,
Contacts etc. . With Cordova we can very much achieve this and package the web application in the
devices installer format.

Installing Cordova
The Cordova command-line tool is distributed as an npm package.
To install the cordova command-line tool, follow these steps:

1. Download and install Node.js. On installation you should be able to invoke node and npm on
your command line.
2. (Optional) Download and install a git client, if you don’t already have one. Following
installation, you should be able to invoke git on your command line. The CLI uses it to
download assets when they are referenced using a url to a git repo.
3. Install the cordova module using npm utility of Node.js. The cordova module will automati-
cally be downloaded by the npm utility.
• on OS X and Linux:
Working with Apache Cordova 72

$ sudo npm install -g cordova

On OS X and Linux, prefixing the npm command with sudo may be necessary to install this
development utility in otherwise restricted directories such as /usr/local/share. If you are using
the optional nvm/nave tool or have write access to the install directory, you may be able to omit the
sudo prefix. There are more tips46 available on using npm without sudo, if you desire to do that.

• on Windows:

C:>npm install -g cordova

The -g flag above tells npm to install cordova globally. Otherwise it will be installed in the node_-
modules subdirectory of the current working directory.

Following installation, you should be able to run cordova on the command line with no arguments
and it should print help text.

Create a project
Go to the directory where you maintain your source code, and create a cordova project:

$ cordova create hello com.example.hello HelloWorld

This creates the required directory structure for your cordova app. By default, the cordova create
script generates a skeletal web-based application whose home page is the project’s www/index.html
file.

Add a platform
After creating a Cordova project, navigate to the project directory. From the project directory, you
need to add a platform for which you want to build your app.
46
http://justjs.com/posts/npm-link-developing-your-own-npm-modules-without-tears
Working with Apache Cordova 73

To add a platform, type cordova platform add <platform name>.


Add the platforms that you want to target your app. We will add the ios and android platform and
ensure they get saved to config.xml:

$ cordova platform add ios –save $ cordova platform add android –save

To check your current set of platforms:

$ cordova platform ls

$ cordova platform ls Running commands to add or remove platforms affects the contents of the
project’s platforms directory, where each specified platform appears as a subdirectory.

Note
When using the CLI to build your application, you should not edit any files in the
/platforms/ directory. The files in this directory are routinely overwritten when preparing
applications for building, or when plugins are re-installed.

Run your app


From the command line, run cordova run <platform name>.

Add Plugins
You can modify the default generated app to take advantage of standard web technologies, but for
the app to access device-level features, you need to add plugins.
A plugin exposes a Javascript API for native SDK functionality. Plugins are typically hosted on npm
and you can search for them on the plugin search page47 . Some key APIs are provided by the Apache
Cordova open source project and these are referred to as Core Plugin APIs48 . You can also use the
CLI to launch the search page:

47
https://cordova.apache.org/plugins/
48
https://cordova.apache.org/docs/en/latest/guide/support/index.html#core-plugin-apis
Working with Apache Cordova 74

$ cordova plugin search camera

To add the camera plugin, we will specify the npm package name for the camera plugin:

$ cordova plugin add cordova-plugin-camera Fetching plugin “cordova-plugin-camera@∼2.1.0”


via npm Installing “cordova-plugin-camera” for android Installing “cordova-plugin-camera” for
ios

Plugins can also be added using a directory or a git repo.

Note
The CLI adds plugin code as appropriate for each platform. If you want to develop with
lower-level shell tools or platform SDKs as discussed in the Overview49 , you need to run
the Plugman utility to add plugins separately for each platform. (For more information, see
Using Plugman to Manage Plugins50 .)

Use plugin ls (or plugin list, or plugin by itself) to view currently installed plugins. Each
displays by its identifier:

$ cordova plugin ls cordova-plugin-camera 2.1.0 “Camera” cordova-plugin-whitelist 1.2.1 “Whitelist”

Updating Cordova and Your Project


After installing the cordova utility, you can always update it to the latest version by running the
following command:

$ sudo npm update -g cordova

Use this syntax to install a specific version:


49
https://cordova.apache.org/docs/en/latest/guide/overview/index.html
50
https://cordova.apache.org/docs/en/latest/plugin_ref/plugman.html
Working with Apache Cordova 75

$ sudo npm install -g cordova@3.1.0-0.2.0

Run cordova -v to see which version is currently running. To find the latest released cordova
version, you can run:

$ npm info cordova version

To update platform that you’re targeting:

$ cordova platform update android –save $ cordova platform update ios –save …etc.

Using Aurelia
The main application source is inside the folder src. With the gulp build task we copy the source and
it’s dependencies to the www folder. These step also transpiles the ECMAScript 6 code to ECMAScript
5. The gulp task bundle will use the aurelia bundler to combine all javascripts and templates to two
files. Inside the aurelia.js file will be the aurelia framework with all dependencies. Our source
folder src/js will be combined to app-build.js. The system.js file of jspm will read the config.js
and knows which files to use after bundling.
To do that you can use gulp tasks as below:

‘Listing-81:build.js’

1 gulp.task('build-scripts', function () {
2 return gulp.src(['client/*.js', '!' + 'client/' + 'index.js'])
3 .pipe(plumber({ errorHandler: notify.onError("Error: <%= error.messa\
4 ge %>") }))
5 .pipe(changed('www/', { extension: '.js' }))
6 .pipe(sourcemaps.init({ loadMaps: true }))
7 .pipe(babel(assign({}, compileOptions, { modules: 'system' })))
8 .pipe(sourcemaps.write({ includeContent: true }))
9 .pipe(gulp.dest('www/'));
10 });
Working with Apache Cordova 76

Please see more samples on https://github.com/au-cordova51 organization.


Now that you know what each of the folders in a Cordova app is for, it’s time to move and start
building the app. Open index.html, which is located in the www directory. This is the default page
that Cordova serves. Cordova apps are best written as a single page application so the user doesn’t
see the page reloading every time a page in the app is accessed. A single page application is nothing
more than a web application that only uses one HTML page. Templates are then used to inject
different states of the application as needed.
By default, index.html contains the following code:

‘Listing-81:index.html’

1 <!DOCTYPE html>
2 <html>
3 <head>
4 <meta http-equiv="Content-Security-Policy" content="default-src 'sel\
5 f' data: gap: https://ssl.gstatic.com 'unsafe-eval'; style-src 'self' 'unsafe-in\
6 line'; media-src *">
7 <meta name="format-detection" content="telephone=no">
8 <meta name="msapplication-tap-highlight" content="no">
9 <meta name="viewport" content="user-scalable=no, initial-scale=1, ma\
10 ximum-scale=1, minimum-scale=1, width=device-width">
11 <link rel="stylesheet" type="text/css" href="css/index.css">
12 <title>Practical Aurelia</title>
13 </head>
14 <body>
15 <div class="app">
16 <h1>Apache Cordova</h1>
17 <div id="deviceready" class="blink">
18 <p class="event listening">Connecting to Device</p>
19 <p class="event received">Device is Ready</p>
20 </div>
21 </div>
22 <script type="text/javascript" src="cordova.js"></script>
23 <script type="text/javascript" src="js/index.js"></script>
24 </body>
25 </html>

You can see that there’s nothing really special. It’s a simple HTML file. The only thing that’s different
are the meta tags. Let me walk you through some of them.
51
https://github.com/au-cordova
Working with Apache Cordova 77

The first meta tag specifies the content security policy. This makes the Cordova application secure
from cross-site scripting (XSS) attacks. Any script that an attacker manages to inject into the
page would simply refuse to be loaded with this meta tag in place.
<meta http-equiv="Content-Security-Policy" content="default-src 'self' data: gap: https://ssl.gsta
'unsafe-eval'; style-src 'self' 'unsafe-inline'; media-src *">

The format-detection meta tag automatically converts phone numbers into links. The user can
then click the link to make a call to that phone number.
<meta name="format-detection" content="telephone=no">

The msapplication-tap-highlight meta tag disables the grey tap highlight on Windows Phone 8
and later. If you’re not planning to deploy on a Windows Phone device, you can safely remove this
tag.
<meta name="msapplication-tap-highlight" content="no">

The next tag is the viewport meta tag. Let’s go through the attributes of this tag from left to right. The
tag first specifies that the user shouldn’t be able to zoom the page in or out. Next, the initial-scale
is set to 1. This means that the content is loaded at 100%. The maximum-scale and minimum-scale
are both set to 1. This sets the minimum and maximum values that the user can set for the zoom
level. The width attribute specifies that the maximum device width is used for the content.
<meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-
scale=1, width=device-width">

Next up is the default stylesheet. This stylesheet contains basic styling for the app.
<link rel="stylesheet" type="text/css" href="css/index.css">

Inside the body tag lives the markup for showing the status of Cordova. The p element with a class
of listening is shown when the device APIs aren’t fully loaded yet. It is hidden when the device
APIs are ready. At that point, the p element below it is shown instead. This markup is specific to the
default Cordova application so we can safely remove it later.

‘Listing-82:index.html’

1 <div class="app">
2 <h1>Apache Cordova</h1>
3 <div id="deviceready" class="blink">
4 <p class="event listening">Connecting to Device</p>
5 <p class="event received">Device is Ready</p>
6 </div>
7 </div>

We then encounter two script tags. The first one includes cordova.js. This file provides a unified
API for accessing native device features. You won’t find this file under the www directory, because
Working with Apache Cordova 78

it is injected to the specific platform when you build your app. This means that there’s a different
version of cordova.js depending on the platform the app is running on.
<script type="text/javascript" src="cordova.js"></script>

The second script tag includes index.js. This file is specific to the default application that’s created
when you start a new Cordova project. All it does, is execute specific code when a specific event
happens.
<script type="text/javascript" src="js/index.js"></script>

If you open the file, you will notice that the receivedEvent function is responsible for hiding and
showing the two p elements we encountered earlier. It’s being triggered by the onDeviceReady event.
So the event identifier that’s passed to the receivedEvent function is deviceready.

‘Listing-83:index.js’

1 onDeviceReady: function() {
2 app.receivedEvent('deviceready');
3 },
4
5 receivedEvent: function(id) {
6 var parentElement = document.getElementById(id);
7 var listeningElement = parentElement.querySelector('.listening');
8 var receivedElement = parentElement.querySelector('.received');
9
10 listeningElement.setAttribute('style', 'display:none;');
11 receivedElement.setAttribute('style', 'display:block;');
12
13 console.log('Received Event: ' + id);
14 }

You’ve waited long enough. It’s now time to get your hands dirty. Let’s first make sure that
everything looks good by making use of Aurelia as a front end framework for building mobile web
apps.
After install Aurelia dependencies and replace index.html with the following:
Working with Apache Cordova 79

‘Listing-84:index.html’

1 <!DOCTYPE html>
2 <html>
3 <head>
4 <meta http-equiv="Content-Security-Policy" content="default-src 'sel\
5 f' data: gap: https://ssl.gstatic.com 'unsafe-eval'; style-src 'self' 'unsafe-in\
6 line'; media-src *">
7 <meta name="format-detection" content="telephone=no">
8 <meta name="msapplication-tap-highlight" content="no">
9 <meta name="viewport" content="user-scalable=no, initial-scale=1, ma\
10 ximum-scale=1, minimum-scale=1, width=device-width">
11 <link rel="stylesheet" href="jspm_packages/npm/font-awesome@4.6.3/cs\
12 s/font-awesome.css">
13 <link rel="stylesheet" type="text/css" href="css/index.css">
14 <title>Practical Aurelia</title>
15 </head>
16 <body>
17 <div class="splash">
18 </div>
19 <script src="jspm_packages/npm/bluebird@3.4.1/js/browser/bluebird.mi\
20 n.js"></script>
21 <script type="text/javascript" src="jspm_packages/system.js"></scrip\
22 t>
23 <script type="text/javascript" src="config.js"></script>
24 <script type="text/javascript" src="cordova.js"></script>
25 <script>
26 System.import('aurelia-bootstrapper');
27 </script>
28 </body>
29 </html>

Now, you can use Aurelia application on Cordova.

Taking photos with a Cordova application


The main purpose of the project was taking photos with the device’s camera.
Working with Apache Cordova 80

‘Listing-85:app.html’

1 <template>
2 <div class="content">
3 <div class="card">
4 <button class="btn btn-block btn-primary" id="take-photo" click.\
5 delegate="takePhoto()">Take Photo</button>
6 </div>
7 <div class="card" show.bind="showContainer == true">
8 <img id="photo" src.bind="imagSrc">
9 <form submit.delegate="sharePhoto()">
10 <input type="text" id="caption" value.bind="caption" placeholder\
11 ="Caption">
12 <button class="btn btn-positive btn-block" id="share">Share</but\
13 ton>
14 </form>
15 </div>
16 </div>
17 </template>

The above markup adds a button for taking a photo. Below it is the div tag for sharing the photo. It
has a text field for entering the caption and a button for sharing.

‘Listing-86:app.js’

1 export class App{


2 showContainer = false;
3 imagSrc = '';
4 caption = '';
5 takePhoto(){
6 let that = this;
7 let camerOptions = {
8 quality: 90,
9 destinationType: Camera.DestinationType.FILE_URI,
10 encodingType: Camera.EncodingType.JPEG,
11 targetWidth: 200,
12 targetHeight: 350
13 };
14 navigator.camera.getPicture(function(imageURI){
15 that.imagSrc = imageURI;
16 that.showContainer = true;
17 }, function(errorMessage){
18 alert('The following error occured: ' + errorMessage)
Working with Apache Cordova 81

19 }, camerOptions);
20
21 }
22
23 sharePhoto(){
24 window.plugins.socialsharing.share(this.caption, null, this.imagSrc,\
25 null);
26 }
27 }

The camerOptions is for the camera plugin52 .


Here’s a brief description of each of the options that we use:

• quality: This option lets us specify the quality of the photo that will be saved, 100 being the
highest and 0 being the lowest.
• destinationType: With this option, we specify the type of the resulting photo. This can have
a value of DATA_URL, which returns a base64 encoded string, or FILE_UR, which returns the
path to the file on the local file system.
• encodingType: This option defines how the photo will be encoded. The possible values are
JPEG and PNG.
• targetWidth: Use this option to specify the width of the photo.
• targetHeight: Use this option to specify the height of the photo.

We have the code that launches the default camera app of the device. It has two callback functions.
The first one is the success callback, which is executed when the photo is successfully captured. The
URL of the image is passed as an argument. It is then used as the value for the src attribute of the img
element. If there was an error, the second callback function is executed. If something goes wrong,
we show an alert to notify the user about the error.
Once a photo has been captured, the showContainer is shown to the user. This contains the share
button to which we add a click event. This uses the social sharing plugin53 . In the code below, we
pass in the value entered in the caption text field and the file path to the photo. The social sharing
plugin then launches the default sharing window of the operating system. At that point, the user
can select the application in which to share the photo.

Installing Plugins
In the previous section, we already used two plugins, the camera plugin and the social sharing plugin.
But you may have noticed that we haven’t installed any of them yet. In this section, we’ll do just
that.
52
https://github.com/apache/cordova-plugin-camera
53
https://github.com/EddyVerbruggen/SocialSharing-PhoneGap-Plugin
Working with Apache Cordova 82

Installing plugins is simple if you already know the specific plugin that you want to install. All you
have to do is determine the plugin identifier. This is usually the name given to the GitHub repository
or the package name on the npm website.
The identifier of the camera plugin54 is cordova-plugin-camera. Installing the plugin is as simple
as executing the cordova plugin add command and passing in the identifier of the plugin:
cordova plugin add cordova-plugin-camera
For the social sharing plugin55 , things are a bit different since it doesn’t comply with the naming
convention:
cordova plugin add cordova-plugin-x-socialsharing
If you’re not sure what plugin you need to install, then simply Google it. There’s also the Cordova
plugins56 website that you can use to search for Cordova plugins.

Adding Crosswalk
Crosswalk57 is a pluggable web view for Cordova apps based on Chromium and Blink. By using
Crosswalk, you replace the default web view that is used by Cordova. This allows you to use browser
features such as WebRTC, WebAudio, and Web Components in your apps. It also improves the
performance of your app. The only known drawbacks are an increased memory footprint and the
size of the installer file.
Crosswalk is distributed as a plugin. You can install it with the following command:
cordova plugin add cordova-plugin-crosswalk-webview
The next time you build the app, it should install the web view for the platform you’re deploying to.
It might take a while depending on your internet connection. But it only does this the first time you
build the app after installing Crosswalk. This is because it has to download all the necessary files
from a remote server and then perform the compilation.
After the build process, it should yield two separate apk files for Android, one for the armv7
architecture and one for x86. Crosswalk makes a separate package file for each of these architectures,
because the apk file would otherwise become large if they’re packaged into one. Luckily, Google’s
Play store allows developers to upload different versions of an apk file for the same app.

Configuring the App


If you want to update the configuration of your app at some point, then you can edit config.xml,
which is located at the root of your project. This is what config.xml looks like by default:
54
https://github.com/apache/cordova-plugin-camera
55
https://github.com/EddyVerbruggen/SocialSharing-PhoneGap-Plugin
56
https://cordova.apache.org/plugins/
57
https://crosswalk-project.org/
Working with Apache Cordova 83

‘Listing-87:config.xml’

1 <?xml version='1.0' encoding='utf-8'?>


2 <widget id="com.auCordova.practicalAurelia" version="0.0.1" xmlns="http://ww\
3 w.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
4 <name>Practise</name>
5 <description>
6 An app for practise
7 </description>
8 <author email="behzad88.2012@gmail.com" href="http://gitlearn.com">
9 Behzad Abbasi
10 </author>
11 <content src="index.html" />
12 <plugin name="cordova-plugin-whitelist" version="1" />
13 <access origin="*" />
14 <allow-intent href="http://*/*" />
15 <allow-intent href="https://*/*" />
16 <allow-intent href="tel:*" />
17 <allow-intent href="sms:*" />
18 <allow-intent href="mailto:*" />
19 <allow-intent href="geo:*" />
20 <platform name="android">
21 <allow-intent href="market:*" />
22 </platform>
23 <platform name="ios">
24 <allow-intent href="itms:*" />
25 <allow-intent href="itms-apps:*" />
26 </platform>
27 </widget>

You can update the identifier of your app:


<widget id="com.auCordova.practicalAurelia">

The versioning uses the semantic versioning58 convention (major.minor.patch):


<widget version="1.0.0">

Update the name tag to update the name of your app:


<name>Practise</name>

Or the description:

58
http://semver.org/
Working with Apache Cordova 84

‘Listing-88’

1 <description>
2 An app for practise
3 </description>

You can also specify which HTML file is rendered in the web view when the app is opened.
<content src="index.html" />

The configuration file also defines the domains the app is allowed to access. By default, a Cordova
app is configured to allow access to all (*) domains.
<access origin="*" />

This isn’t recommended. It makes the app less secure, because it can make requests to external
domains that might contain malicious content. That’s why the whitelist plugin59 is installed by
default. This gives developers fine control over which domains or intents (tel, geo, sms) are allowed
in the app.
For more information on app configuration, check out the documentation for the config.xml file60 .

Deploying the App


In this section, I’ll be walking you through the steps involved to prepare and deploy a Cordova app
to Google’s Play store.
Step 1: Adding Platforms
The first thing you need to do is add the platform(s) you’re targeting. To list the platforms you
can target with Cordova, execute the cordova platform list command. This command lists the
platforms that you’ve installed as well as the platforms that are available for publication:

Installed platforms: android 6.0.0, browser 4.1.0 Available platforms: amazon-fireos, blackberry10,
firefoxos, ubuntu

As you can see, I already installed two platforms, browser and android. You can do the same by
executing the following commands:

59
https://github.com/apache/cordova-plugin-whitelist
60
https://cordova.apache.org/docs/en/latest/config_ref/index.html
Working with Apache Cordova 85

cordova platform add browser cordova platform add android

The browser platform is mainly used for testing your app in the browser. This is done by executing
the cordova serve command. This command creates a server that serves your app on a specific port.
By default, it’s serving your app on port 8000. You can view it by opening a browser and browsing
to http://localhost:8000/browser/www. Also you can use gulp serve command for testing your app
on browser.

‘Listing-89:serve.js’
1 var gulp = require('gulp');
2 var browserSync = require('browser-sync');
3
4 gulp.task('serve', function(done) {
5 browserSync({
6 online: false,
7 open: false,
8 port: 8000,
9 server: {
10 baseDir: ['./www'],
11 middleware: function(req, res, next) {
12 res.setHeader('Access-Control-Allow-Origin', '*');
13 next();
14 }
15 }
16 }, done);
17 });

You can use the emulator in your browser to display the app in a smaller screen. In Google Chrome,
for example, you can click on the mobile icon in the upper left of the developer tools.
The cordova serve command copies your source files to the platforms/browser directory. This
means that any time you make a change to the files in the www directory, you have to restart the
server for the changes to take effect. If you want to learn more about this topic, then I recommend
you check out Grunt or Gulp, two automation tools that help you copy files to the browser platform
every time you make a change to the source code.
Next, we have the Android platform. If you open the platforms/android directory, you’ll see a lot
of folders and files. Most of them are files used by Cordova during build time so you don’t really
have to worry about those. In fact, you don’t really want to mess with the files in this directory
unless it’s really necssary. However, you have to keep in mind what the following files and folders
are for, because you may need to work with them later.
Working with Apache Cordova 86

• assets: This is where the files from the www directory are copied to.
• build: Once you build your app, this is where the build files are stored. For Android, they are
saved under the build/outputs/apk directory.
• AndroidManifest.xml: This file is specific to the Android platform. This is the app config-
uration. Anything that you’ve included in config.xml is copied over to this file. When you
install a plugin, the plugin adds an entry to this file for the specific permissions that it uses.
If there’s an issue with a specific plugin, this is where you would usually take a look, because
it’s possible that the plugin hasn’t added the necessary permissions to this file.
• res: Any assets that your app uses are copied over to this directory. This includes images,
sound files, welcome screens, etc.
• src: This folder contains platform specific code from the plugins that you’ve installed.

Step 2: Generating the App Build


After adding the Android platform, it’s time to build the app. This process will generate the package
or installer for each of the platforms that you have added to your project. For Android, it will be an
.apk file. You can use the following command to initiate the build process for the Android platform:
cordova build android

If you want to build for multiple platforms, simply ommit the platform parameter:
cordova build

Note that the above two commands don’t generate an .apk that’s ready for submission to Google’s
Play store. By default, the cordova build command generates a debug version of your app. This
version of the app is intended for testing by the developer. The only difference between the two is
the certificate used for signing the app. The debug version of an app is signed with a certificate from
the android SDK while the release version of the app is signed with a certificate that’s generated
by the developer. This means that, when you run the cordova build android command, it will
automatically sign the generated apk file for you. If you want to generate a release version of the
app, you have to add the --release option:
cordova build --release android

This tells Cordova that this app will be signed by the developer later on.
Step 3: Signing the App
We’re now ready to sign the app. Before we do, I’d like to make sure we’re on the same page. What
does signing the app actually do and why is it needed? To put it simply, you need to sign the app so
that you can be identified as the author of the app. This proves that the app came from you.
On Android, this can be done using a keystore file. You can use this file for signing your app. To
generate a keystore, you use the keytool which you can find in the sdk/tools directory of your
Android SDK installation path. Since we already added the path to the SDK tools earlier, we should
be able to execute the keytool command from the command line.
Working with Apache Cordova 87

keytool -genkey -v -keystore practise.keystore -alias practise -keyalg RSA -keysize 2048
-validity 10000
Here’s a breakdown of each of the options and arguments that we’ve passed in. The options are the
ones prefixed with a dash and the arguments are the ones without a dash.

• genkey: the command to generate the key


• keystore: the file name of the keystore
• alias: the alias that you want to give to the key
• keyalg: the algorithm to be used for generating the key
• keysize: the size of the key in bytes
• validity: how long (in days) the keystore should be valid

Next, we use another tool called jarsigner to sign the app with the keystore that we’ve generated.
Navigate to the directory where you have the unsigned release version of the app:
cd platforms/android/build/outputs/apk
Next, execute the following command:
jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore practise.keystore android-
release-unsigned.apk practise
This command will ask for the password that you’ve entered when you generated the keystore. Enter
the password to start the signing process.
Let’s break the command down:

• -verbose: specifies that the command should output all information while signing the apk file
• sigalg: the signing algorithm to be used, in this case we’re using SHA1withRSA
• digestalg: the message digest algorithm to use
• keystore: the path to the keystore file
• android-release-unsigned.apk the path to the unsigned release version of the apk file
• practise: the alias that you’ve specified earlier when you generated the keystore file

Finally, we need to make sure the app performs at its best. To do that, we use the Zipalign tool.
Zipalign is installed in the build-tools directory of your Android SDK install path. You can go
ahead and copy it to the tools directory of your Android SDK install path. This allows us to use
it from any terminal window since we already added the tools directory to the .bashrc file earlier.
Once that’s done, you can execute the command below. The -v option provides us with verbose
output. 4 is an integer that defines the byte-alignment boundaries. This should always be set to 4.
android-release-unsigned.apk is the path to the apk file that we want to optimize. practise.apk
is the name of the output file.
zipalign -v 4 android-release-unsigned.apk practise.apk
After it finishes executing, you should be able to upload the practise.apk file to the Play store. If
you used Crosswalk, you need to do the same process for both the armv7 and the x86 version of the
app.
Working with Apache Cordova 88

Using Aurelia
First, Go to the directory project and create a new project:

$ cordova create chapter4 com.practice.myProject MyProject

Next, you need to add platform such as android:

$ cordova platforms add android –save $ cordova platforms add ios –save

Note
When using the CLI to build your application, you should not edit any files in the
/platforms/ directory. The files in this directory are routinely overwritten when preparing
applications for building, or when plugins are re-installed.

Attention, To build and run apps, you need to install SDKs for each platform you wish to target.
Alternatively, if you are using browser for development you can use browser platform which does
not require any platform SDKs.
To check if you satisfy requirements for building the platform:

$ cordova requirements Requirements check results for android: Java JDK: installed . Android SDK:
installed Android target: installed android-19,android-21,android-22,android-23,Google Inc.:Google
APIs:19,Google Inc.:Google APIs (x86 System Image):19,Google Inc.:Google APIs:23 Gradle: in-
stalled
Requirements check results for ios: Apple OS X: not installed Cordova tooling for iOS requires
Apple OS X Some of requirements check failed

You can see Android platform requirements here61 , Also you can see iOS platform requrements
here62 , and for Windows platform please see this63
61
https://cordova.apache.org/docs/en/latest/guide/platforms/android/index.html#requirements-and-support
62
https://cordova.apache.org/docs/en/latest/guide/platforms/ios/index.html#requirements-and-support
63
https://cordova.apache.org/docs/en/latest/guide/platforms/win8/index.html#requirements-and-support
Working with Apache Cordova 89

After create a new project you need to enhance your workfow. We want to using Gulp, so you can
stop messing around and build Aurelia project. In here, we have many important tasks such as:

‘Listing-90:Publicvariables’
1 var gulp = require('gulp');
2 var runSequence = require('run-sequence');
3 var babel = require('gulp-babel');
4 var changed = require('gulp-changed');
5 var plumber = require('gulp-plumber');
6 var sourcemaps = require('gulp-sourcemaps');
7 var sass = require('gulp-sass');
8 var paths = require('../paths');
9 var compileOptions = require('../babel-options');
10 var assign = Object.assign || require('object.assign');
11 var notify = require('gulp-notify');

‘Listing-91:CompilenextgenerationJavaScript(es6)toes2015withbabel’
1 gulp.task('build-vvm', function () {
2 return gulp.src([paths.vvm, '!' + paths.root + 'app/index.js'])
3 .pipe(plumber({ errorHandler: notify.onError("Error: <%= error.messa\
4 ge %>") }))
5 .pipe(changed(paths.vvmOut, { extension: '.js' }))
6 .pipe(sourcemaps.init({ loadMaps: true }))
7 .pipe(babel(assign({}, compileOptions, { modules: 'system' })))
8 .pipe(sourcemaps.write({ includeContent: true }))
9 .pipe(gulp.dest(paths.vvmOut));
10 });

To move browser packages (e.g. jspm packages), We use this task from src to www directory.

‘Listing-91:MoveJspm_packages’
1 gulp.task('move-jspm', function () {
2 return gulp.src(paths.jspm)
3 .pipe(changed(paths.jspmOut))
4 .pipe(gulp.dest(paths.jspmOut));
5 });

Info
You can see all tasks in exercises files

Now, you can implement your Aurelia application.


Working with Apache Cordova 90

Cordova plugin APIs


Cordova ships with a minimal set of APIs, and projects add what extra APIs they require through
plugins.

back button

Routing is essential in any single-page application and is a hard problem to solve well. Aurelia
appears to have solved it very well. Has it ever been easier to configure routes than this?
It would be straightforward to quickly flesh out the routes for a large application, something that
has been painful with other approaches in my experience where a huge amount of boilerplate is
required to hook everything together.
After configure route, We have important matter about to use back button. Aurelia have a history
browser implementation to keep states. The navigateBack method of AppRouter will be useful when
the user presses the back button.
To override the default back-button behavior, register an event listener for the backbutton event,
typically by calling document.addEventListener once you receive the [deviceready](events.deviceready.html)
event. It is no longer necessary to call any other method to override the back-button behavior.

‘Listing-92:pressBackButton’
1 activate() {
2 let that = this;
3 function OnBackPress(e) {
4 that.router.navigateBack();
5 }
6 document.addEventListener("backbutton", OnBackPress, false);
7 }

Next, exit app when pressing the back button.

‘Listing-93:pressBackButton’
1 activate(params, routeConfig) {
2 let that = this;
3 function OnBackPress(e) {
4 if(routeConfig.name !== 'home')
5 that.router.navigateBack();
6 else
7 navigator.app.exitApp();
8 }
9 document.addEventListener("backbutton", OnBackPress, false);
10 }
Working with Apache Cordova 91

Battery Status

This plugin provides an implementation of the Battery Status Events API. It adds the following
three events to the window object:

• batterystatus
• batterycritical
• batterylow

Applications may use window.addEventListener to attach an event listener for any of the above
events after the deviceready event fires.
Installation:

$ cordova plugin add cordova-plugin-battery-status

Usage:

‘Listing-94:batterystatusviewModel’

1 attached(params, routeConfig) {
2 let that = this;
3 function onBatteryStatus(status) {
4 that.status = status.level;
5 that.isPlugged = status.isPlugged;
6 }
7 document.addEventListener("batterystatus", onBatteryStatus, false);
8
9 function onBatteryLow(status) {
10 that.batteryLow = true;
11 }
12 document.addEventListener("batterylow", onBatteryStatus, false);
13 }
Working with Apache Cordova 92

‘Listing-95:batterystatusview’

1 <template>
2 <h2>Level: ${status}</h2>
3 <h3>isPlugged: ${isPlugged}</h3>
4
5 <h1 if.bind="batteryLow === true">Battery Level Low ${status}%</h1>
6 </template>

Camera

This plugin defines a global navigator.camera object, which provides an API for taking pictures
and for choosing images from the system’s image library.
Although the object is attached to the global scoped navigator, it is not available until after the
deviceready event.

Installation:

$ cordova plugin add cordova-plugin-camera

Usage:

‘Listing-96:cameraviewModel’

1 import { Container } from 'aurelia-framework';


2
3 export class Camera{
4 takePhoto(){
5 let options = {
6 quality: 75,
7 destinationType: navigator.camera.DestinationType.FILE_URI,
8 sourceType: navigator.camera.PictureSourceType.CAMERA,
9 allowEdit: true,
10 saveToPhotoAlbum: false
11 };
12
13 navigator.camera.getPicture(this.successCallback,this.errorCallback,\
14 options);
15 }
16
17 successCallback(imageData){
Working with Apache Cordova 93

18 let that = Container.instance.get(Camera);


19 that.imgURI = "data:image/jpeg;base64," + imageData;
20 }
21
22 errorCallback(err){
23 // An error occured. Show a message to the user
24 }
25 }

Creating a viewModel as Camera and add the function for take picture, getPicture function used to
take and choose photo by sending the options. We can set quality, destinationType, sourceType,
etc. using option values. The return value is sent to the successCallback function or errorCallback
function. The return value is sent to the successCallback function, in one of the following formats,
depending on the specified cameraOptions:

• An String containing the Base64-encoded photo image.


• An String representing the image file location on local storage (default).

Also, in successCallback function, we was spent describing how Aurelia’s DI container works with
respect to one use case: instantiating a viewmodel. Also, you can use WeakMap feauture to do that.

‘Listing-97:cameraview’

1 <template>
2 <h2>Level: ${status}</h2>
3 <h3>isPlugged: ${isPlugged}</h3>
4
5 <h1 if.bind="batteryLow === true">Battery Level Low ${status}%</h1>
6 </template>

You can see more plugin APIs here64 .

Progressive Web App (WPA)


This section contains push messages, cache api, indexedDB, background sync and servre sent
event(not really anything to do with PWA’s).
The two key pieces that are needed for a web app to become a progressive web app are a manifest
and a service worker. The manifest gives the browser metadata about your app, so that when the
64
https://cordova.apache.org/docs/en/latest/#plugin-apis
Working with Apache Cordova 94

user chooses to add your PWA to their home screen, it knows what icon to use, how the PWA should
be displayed, the PWA’s name, and more. Normally, you’d have to add this yourself.
A service worker is a script that allows you to control how your PWA uses the network. Service
workers allow you to give your PWA functionality such as the ability to work offline, send push
notifications, background sync, and more!
We provide the service worker registration code in index.html:

‘Listing-98:serviceworkerinstaller’

1 <script>
2 if ('serviceWorker' in navigator) {
3 window.addEventListener('load', function () {
4 navigator.serviceWorker.register('/sw.js').then(function (regist\
5 ration) {
6 // Registration was successful
7 //console.log('ServiceWorker registration successful with sc\
8 ope: ', registration.scope);
9 }).catch(function (err) {
10 // registration failed :(
11 console.log('ServiceWorker registration failed: ', err);
12 });
13 });
14 }
15 </script>

To enable the service worker, you should create the sw.js file in the client folder of your project.

‘Listing-99:serviceworker’

1 //OFFLINE
2 self.addEventListener('install', function (e) {
3 e.waitUntil(
4 caches.open('v1').then(function (cache) {
5 //Add all resources to cache
6 return cache.addAll([
7 'js',
8 'js/app.js',
9 'styles/style.css'
10 ]).then(() => self.skipWaiting());
11 })
12 );
13 });
14
Working with Apache Cordova 95

15 self.addEventListener('activate', event => {


16 event.waitUntil(self.clients.claim());
17 //Remove old caches
18 var cacheWhitelist = ['v1'];
19 event.waitUntil(
20 caches.keys().then(function (keyList) {
21 return Promise.all(keyList.map(function (key) {
22 if (cacheWhitelist.indexOf(key) === -1) {
23 return caches.delete(key);
24 }
25 }));
26 })
27 );
28 });
29
30 self.addEventListener('fetch', event => {
31 //Proxy the request and respond from cache
32 event.respondWith(
33 caches.match(event.request).then(response => {
34 return response || fetch(event.request);
35 })
36 );
37 });
38
39 ///PUSH
40 self.addEventListener('push', function (event) {
41 const title = 'Push message';
42 const options = {
43 body: event.data.text() //Your push message - event.data.text() for \
44 instance
45 // icon: '/icon.png', //image
46 // badge: '/icon.png' ////image
47 };
48 event.waitUntil(self.registration.showNotification(title, options));
49 });
50
51 //Function for fetching data from indexedDB
52 function getOfflineData() {
53 return new Promise((res, rej) => {
54 let request = self.indexedDB.open("text"),
55 db;
56
Working with Apache Cordova 96

57 request.onupgradeneeded = function (event) {


58 db = event.target.result;
59 // Create an objectStore for this database
60 let objectStore = db.createObjectStore("texts", {
61 keyPath: "id",
62 autoIncrement: true
63 });;
64 };
65 request.onerror = function (event) {
66 console.log('error:', event);
67 };
68 request.onsuccess = function (e) {
69 db = e.target.result;
70 let result = [],
71 transaction = db.transaction(["texts"], IDBTransaction.READ_\
72 WRITE),
73 objectStore = transaction.objectStore('texts');
74 objectStore.index('content').openCursor().onsuccess = (event) =>\
75 {
76 let c = event.target.result;
77 if (c) {
78 result.push(c.value);
79 c.continue();
80 } else {
81 res(result);
82 }
83 };
84 }
85 });
86 }
87
88 //Function to sync data between app and server
89 function doSync() {
90 return new Promise((res, rej) => {
91 let data = getOfflineData();
92 data.then((d) => {
93 fetch('/api/offline', {
94 method: 'post',
95 headers: {
96 "Content-type": "application/json; charset=UTF-8"
97 },
98 body: JSON.stringify(d)
Working with Apache Cordova 97

99 }).then((data) => {
100 data.json().then((d) => {
101 const title = 'Background sync is done';
102 const options = {
103 body: "Data have been synced with the server", //You\
104 r push message - event.data.text() for instance
105 icon: '/icon.png', //image
106 badge: '/icon.png' ////image
107 };
108 self.registration.showNotification(title, options);
109 })
110 }).catch((error) => {
111 console.log('Request failed', error);
112 });
113 })
114 });
115 }
116
117 //Trigger background sync
118 self.addEventListener('sync', function (event) {
119 if (event.tag == 'syncoffline') {
120 event.waitUntil(doSync());
121 }
122 });

Now, for offline manifest you shoud create a file and named to manifest.json. The Manifest for
Web applications65 is a simple JSON file that gives you, the developer, the ability to control how
your app appears to the user in the areas that they would expect to see apps (for example the mobile
home screen), direct what the user can launch and, more importantly, how they can launch it. In
the future the manifest will give you even more control over your app, but right now we are just
focusing on how your app can be launched.

65
https://w3c.github.io/manifest/
Working with Apache Cordova 98

‘Listing-100:manifest.json’
1 {
2 "manifest_version": 2,
3 "name": "MyProject",
4 "short_name": "MyProject",
5 "icons": [
6 {
7 "src": "favicon.png",
8 "type": "ico",
9 "sizes": "128x128"
10 }
11 ],
12 "start_url": "/",
13 "scope": "/",
14 "display": "browser"
15 }

Once you have the manifest created and it is hosted on your site, all you need to do is add a link tag
from all your pages that encompass your app, as follows:

‘Listing-101:index.html’
1 <link rel="manifest" href="/manifest.json">

That’s it.
Let’s to implementing push notification with install FCM plugin. The cordova-plugin-fcm is
extremely easy plug&play push notification plugin for Cordova applications with Google Firebase
FCM.
Installation Make sure you have google-services.json for Android or GoogleService-Info.plist
for iOS in your Cordova project root folder. You don´t need to configure anything else in order to
have push notification working for both platforms, everything is magic.

$ cordova plugin add cordova-plugin-fcm

Firebase configuration files


Get the needed configuration files for Android or iOS from the Firebase Console (see docs:
https://firebase.google.com/docs/).
Android compilation details :
To download a config file for an Android app:
Working with Apache Cordova 99

1. Sign in to Firebase and open your project.


2. Click the Settings icon and select Project settings.
3. In the Your apps card, select the package name of the app you need a config file for from the
list.
4. Click google-services.json.

Put the downloaded file google-services.json in the Cordova project root folder.
You will need to ensure that you have installed the appropiate Android SDK libraries.
iOS compilation details :
To download a config file for an iOS app:

1. Sign in to Firebase and open your project.


2. Click the Settings icon and select Project settings.
3. In the Your apps card, select the bundle ID of the app you need a config file for from the list.
4. Click GoogleService-Info.plist.

Put the downloaded file GoogleService-Info.plist in the Cordova project root folder.

‘Listing-102:google-services.jsonsample’

1 {
2 "project_info": {
3 "project_number": "904541...",
4 "firebase_url": "https://practicalaurelia.firebaseio.com",
5 "project_id": "practicalaurelia",
6 "storage_bucket": "practicalaurelia.appspot.com"
7 },
8 "client": [
9 {
10 "client_info": {
11 "mobilesdk_app_id": "1:904541...:android:43a4860c...",
12 "android_client_info": {
13 "package_name": "com.practicalaurelia"
14 }
15 },
16 "oauth_client": [
17 {
18 "client_id": "904541771161-...apps.googleusercontent.com",
19 "client_type": 3
20 }
21 ],
Working with Apache Cordova 100

22 "api_key": [
23 {
24 "current_key": "AIzaSyBKZ3fjKHStaJTb1-..."
25 }
26 ],
27 "services": {
28 "analytics_service": {
29 "status": 1
30 },
31 "appinvite_service": {
32 "status": 1,
33 "other_platform_oauth_client": []
34 },
35 "ads_service": {
36 "status": 2
37 }
38 }
39 }
40 ],
41 "configuration_version": "1"
42 }

Reciecing Token Refresh :

‘Listing-103:ReciecingTokenRefresh’

1 attached() {
2 //Keep in mind the function will return null if the token has not been e\
3 stablished yet.
4 FCMPlugin.onTokenRefresh((token) => {
5 console.log(token);
6 }, (err) => {
7 // throw an error
8 });
9 }

Get Token :
Working with Apache Cordova 101

‘Listing-104:GetToken’

1 attached() {
2 //Keep in mind the function will return null if the token has not been e\
3 stablished yet.
4 FCMPlugin.getToken(function(token){
5 console.log(token);
6 }, (err) => {
7 // throw an error
8 });
9 }

Receiving push notification data :

‘Listing-105:Receivingpushnotificationdata’

1 attached() {
2 //Here you define your application behaviour based on the notification d\
3 ata.
4 FCMPlugin.onNotification(function (payload) {
5 if(payload.wasTapped) {
6 //Notification was received on device tray and tapped by the use\
7 r.
8 console.log(JSON.stringify(payload) );
9 } else {
10 //Notification was received in foreground. Maybe the user needs \
11 to be notified.
12 console.log(JSON.stringify(payload) );
13 }
14 }
15 }

Now, you can send data with push notification, but you must have server to send notification. Thus,
you can use Firebase or implement an server. To implementing push notification server you can
use Node application by the ExpressJs.
First create a directory named server, change to it and run npm init. Then install express as a
dependency.
Now install Express in the server directory and save it in the dependencies list. For example:
Working with Apache Cordova 102

$ npm install express –save

To install Express temporarily and not add it to the dependencies list, omit the –save option:

$ npm install express

In the server directory, create a file named server.js and add the following code:

‘Listing-106:server.js’

1 import express from "express";


2 var app = express();
3
4 app.get('/', function (req, res) {
5 res.send('Hello World!')
6 })
7
8 app.listen(3000, function () {
9 console.log('Example app listening on port 3000!')
10 })

The app starts a server and listens on port 3000 for connections. The app responds with “Hello
World!” for requests to the root URL (/) or route. For every other path, it will respond with a 404
Not Found.
You can run the app with the following command:

$ node server.js

Then, load http://localhost:3000/ in a browser to see the output.


To parse incoming request bodies in a middleware before your handlers, available under the req.body
property.
Working with Apache Cordova 103

‘Listing-107:server.js’

1 import bodyParser from 'body-parser';


2 var app = express();
3 app.use(bodyParser.json());

Now, returns middleware that only parses json.


Before initial APIs we need to:

• A variables named queue for messages queue.

var queue = [];

• Node js Even Emitter that allows one or more functions to be attached to named events emitted
by the object.

$ npm install events –save

‘Listing-108:server.js’

1 import EventEmitter from 'events';


2 var em = new EventEmitter();

Let’s to implementing APIs:


First, An API to get a push message
Working with Apache Cordova 104

‘Listing-109:server.js’

1 app.post('/api/push', function(req, res) {


2 let sub = req.body.subscription;
3 queue.push(sub);
4 res.send('OK');
5 });

Next, An API to sync data between app and server

‘Listing-110:server.js’

1 app.post('/api/offline', function(req, res) {


2 res.json(req.body);
3 });

Finally, you need to implement a push service as below:

$ npm install web-push –save

Web push requires that push messages triggered from a backend be done via the Web Push Protocol
and if you want to send data with your push message, you must also encrypt that data according to
the Message Encryption for Web Push spec.
To use push messages you need to generate a set of vapid keys to use during subscription and sending
pushmessages. Don’t use the ones provided… they are just for testing…

$ npm i web-push -g
$ web-push generate-vapid-keys [–json]
Working with Apache Cordova 105

‘Listing-111:server.js’

1 function pushService() {
2 let success = (res) => {
3 console.log(res);
4 },
5 error = (err) => {
6 console.log(err);
7 };
8 setInterval(() => {
9 let i = queue.length;
10 while(i--) {
11 const pushSubscription = queue.pop(); //your subscription object
12 const payload = 'This is the push message you asked for :)';
13 if(pushSubscription) {
14 const options = {
15 vapidDetails: {
16 subject: 'http://localhost:4000/',
17 publicKey: 'BHVJ8n4KMCPy7YOTwNTwn-M3lSKOP.......', //The\
18 se are the keys you generated by web-push
19 privateKey: 'FLuaKQ7........' //These are the keys you g\
20 enerated by web-push
21 },
22 TTL: 90000,
23 headers: {
24 //'< header name >': '< header value >'
25 }
26 };
27 webpush.sendNotification(
28 pushSubscription,
29 payload,
30 options
31 ).then(success).catch(error);
32 }
33 }
34 }, 60000);
35 }

To use APIs you can use Postman, Fiddler or implement your application.
Now, run the application and send your push messages.
Planning an Aurelia Application
Planning an Aurelia project is something you may have already done, or will be soon attempting.
This post is a high level outline of things to consider when planning an Aurelia application, from
tooling choices during development all the way through to deployment and performance strategies.
I try to use tools that used in my work experience. There’s certainly a lot more to it than meets the
initial eye:

• Project management
• Accessibility, i18n and environments
• Development Process Methodology
• Tooling and Development
• Testing Methodologies
• Codebase distribution strategies
• Style guide
• Backend API
• Performance Strategies

Project Management
Before you get started, you need to consider how you’re going to get the ball rolling – and keep it
rolling. This usually starts with project management and discussing and agreeing upon particular
toolchains to accomplish your next application.

Software Management Tools


To manage the development of the front-end application, you’ll minimally need to select the
following software management tools to manage code, assets, and team members’ tasks:

Software management tools Examples


Issue and Feature tracker GitHub, BitBucket, JIRA, Microsoft TFS

Version control system GitHub, BitBucket, Microsoft TFS

Document/asset storage Slack, internal network storage, cloud


Planning an Aurelia Application 107

Team Communication Slack, HipChat, IRC, Google Hangouts, Microsoft TFS

Task Manager GitHub Org Tasks, Trello, JIRA, Issue Tracker

Ensure that you and your team adopt the tools you choose, and frequently assess and improve your
workflow. New applications and tools are released to the public all the time and you may wish to
address new tools that coincide with features or things you feel are missing – and you’ll naturally
adopt new tools as time progresses.

Accessibility, i18n and environments


Accessibility, i18n (internationalization) and building for the correct environments are an essential
part of any application. It’s not just deciding what to build, but how you’re going to build and
support it. Addressing these considerations at the beginning of a project will enable you to clearly
vision how you’ll implement the said features, such as accessibility concerns and i18n for example.

Software management Examples Links


tools
Internationallisation / Translations for different http://aurelia.io/hub.html#/doc/api/aurelia/i18n
Globalisation languages / Culture
differences

Broswer Support Aurelia targets Evergreen http://blog.aurelia.io/2015/01/26/introducing-


Browsers only aurelia

Accessibility WAI-ARIA https://www.w3.org/WAI/intro/aria

Offline-first https://developers.google.com/web/fundamentals/getting-
started/primers/service-workers

Progressive Web App https://developers.google.com/web/progressive-web-


apps/

Above are some examples for consideration when deciding baseline standards and types of support
your application can offer. These details may differ per project, for things such as i18n and offline
strategies, it comes down to the goals of your project.

Development Process Methodology


Typically there are a few different approaches to development, such as Agile, Waterfall, Scrum,
Kanban and likely many more adaptations.
Planning an Aurelia Application 108

Whichever approach you take, it’s important to remain consistent. The processes I’ve found to
be ideal are the custom, loosely enforced, agile-like processes that can be wrapped around the
uniqueness of the project and team members.

Tooling and Development


Tooling has been increasingly important when developing any kind of application for the web
or other platforms. There are a vast amount of tooling options available with Aurelia. SystemJs
was introduced first, however WebPack has seemingly become the standard across the JavaScript
ecosystem. Let’s dive into some tooling a little further. Also, there are many modules to compiling
your Javascript codes. system, es2015, commonjs, amd, native-modules are these modules, that you
can see these in Aurelia-Skeleton-Navigation repo.

Package Managers
Package managers allow you to grab dependencies from an external host, for example using npm
to fetch your dependencies for development and also any dependencies that will make it into
production.
An example of this would be using a development dependency such as TypeScript, which will never
make its way into the browser as it’s pre-compiled locally during development and for project builds
before deployment. An example of a dependency that needs to make its way into production would
be parts of Aurelia itself, such as HttpClient, inject and other modules.
Here are a few examples when considering a package manager.

Task Runners
Task runners will allow you to configure particular tasks depending on what you’re aiming to
achieve. Managing third party code and their dependencies should not be a manual task performed
by a human, it’s not productive.
For example, you can use a particular command from a task runner to start a local server, compile
all assets and then serve those assets in your browser with ease.
Planning an Aurelia Application 109

Linters and Enforcement


When working on a team, the goal should be that each file is written as if it were coded from a
single developer’s mind in regards to error prevention, formatting, and style. Three main types of
tools (i.e. code linters/hinters, code style checker, and a code editor config file) aid this process and
should be properly implemented and configured before coding begins.

Tools Examples
Linters / Hinters Codelyzer, CSSLint, ESLint, TSLint, HTMLHint

Code style checker ESLint, TSLint

Code editor formatting/style .editorconfig


Planning an Aurelia Application 110

Aurelia CLI
The Aurelia CLI will allow you to do most of the above, all in a single environment. Using the CLI
will allow you to create projects, scaffold components and bundle your app for release via commands
in your terminal.

UI Components
Building web applications means that you are likely going to require some additional UI components
beyond what the browser itself has to offer. Textboxes, labels and dropdown lists will only get you
so far.
When it comes to UI components, there are a lot of great options. You can choose either commercial
or open-source components. The important thing is to pick a component library which is built on
Aurelia, not wrapped with it.

Tools Purpose
Kendo UI The popular commercial Kendo UI component library

Aurelia Materialize Components An open-source library containing many of the


components needed to create applications which
adhere to the Material Design specification

Bootstrap A baseline CSS framework that is often used for application layout and
it’s popular grid system

Testing Methodologies
How you test and what you test is less important than the fact that you test something. It’s likely the
case that you’ll want to test each module or unit of code by writing unit tests. When all of the units
of code unite to form a running application, you’ll want to do functional end-to-end testing. Below
I detail the tools required (tasking tool facilitate all this) to do cross-browser unit and functional
testing.

Tools Purpose
Karma The karma test runner is ideal for writing and running unit tests while
developing the application. It can be an integral part of the project’s
development and continuous integration processes. This chapter describes
how to setup and run tests with karma
Planning an Aurelia Application 111

Aurelia Testing Simplifies the testing of UI components by providing an elegant,


fluent interface for arranging test setups along with a number of
runtime debug/test helpers

Protractor Use protractor to write and run end-to-end (e2e) tests. End-to-end tests
explore the application as users experience it. In e2e testing, one
process runs the real application and a second process runs protractor
tests that simulate user behavior and assert that the application
responds in the browser as expected

Jasmine The Jasmine test framework. provides everything needed to write basic
tests. It ships with an HTML test runner that executes tests in the browser

Jest Delightful JavaScript Testing. Complete and easy to set-up JavaScript testing
solution. Fast interactive watch mode runs only test files related to changed
files and is optimized to give signal quickly

Codebase Distribution Strategies


Gone are the days where we can just build an application purely for the browser environment. We’re
at the stage where, without necessarily knowing it, we’re writing code in a format that can run pretty
much nearly anywhere. Under the hood, language parsers such as Babel or TypeScript convert our
code into an AST (Abstract Syntax Tree). An AST is a chain of commands that describe our code, at
a higher level. This means that we’re not limited to the original language it was written in. People
can then (and already have for most cases) write programs that interpret these ASTs and spit them
out in whatever language is needed.
Via an AST, things like NativeScript exist to transform that AST into native code on mobile for
impeccable performance and native experience.
For your application, you need to consider the initial environments you’ll be deploying to, as well as
any future considerations – such as NativeScript for native mobile applications (it’ll compile your
Aurelia code for you, reusing your existing codebase).

Browser Only
If your application will only run in a browser, then your strategy will be fairly simple. You’ll be able
to deploy to a single environment and the code will run in the browser like any other web app that’s
“browser only”.
Planning an Aurelia Application 112

Server-Side Rendering (SSR)


Server-side rendering has a huge performance and SEO benefit to loading an Aurelia application
directly in the browser. For this Aurelia Universal is Comming soon… or use ssr branch on aurelia-
skeleton-navigation repository.

Style guide
Defining a style for your team and project is essential, as is deciding what architectural practices
you’ll be using.

Aurelia Style Guide


The Aurelia style guide66 should be a go-to consideration to get familiar with architectural and style
best practices before diving into any Aurelia project. It offers a lot of help towards sensible app
structure, offers common “gotchas” and recommendations for you.

Backend API
The first step assumes API-first development, which is an excellent method that I highly recom-
mended.
In a nutshell, API-first development means that you document, build, and test your API first. This
means you have a relatively stable API before you write any application code. Note that during API
construction, front-end developers should be prototyping minimal viable features using the API and
providing feedback to the API engineers.
The main reason to follow API-first development is to reduce the possible deficiencies in the API
from being amplified in the data layer. You want to do everything in your power up front to avoid
having to make up for your API’s deficiencies in your application logic. Having a documented and
mostly solidified data API before a line of application code is written can go a long way towards
reducing pain and misery in the future. Build your API first. Document it, test it, and then be ready
to evolve it as you build out the applications that use it.
It’s worth noting that it may be assumed that security and authentication details will accompany
the API. It is also assumed that the API developers will provide a development API to be used for
development. Using the same data API for both development and production should never be an
option.
66
https://github.com/behzad888/Aurelia-styleguide
Planning an Aurelia Application 113

Performance Strategies
It’s worth investigating how to get the most out of your Aurelia application before you’ve even set
foot in the codebase. Let’s investigate some approaches.

Isomorphic
Some of Aurelia is designed to run on the server, such as our Dependency Injection library, Event
Aggregator, etc. These are components that are useful even in standard server-side code.
Currently, Aurelia doesn’t do server-sider rendering. However, we have put in place a platfrom
abstraction layer (PAL) in the current version so that no Aurelia library accesses the DOM or browser
global variables directly. This is part of a plan we are working on for 2017 year. The plan for this
year has several stages:

• Implement the PAL


• Implement pre-compilation of views during application bundling
• Implement server-render for SEO optimization
• Implement client-side “continue” of server-rendered applications

Read more about optimization plan67

67
https://github.com/aurelia/benchmarks/blob/master/optimization-plan.md

You might also like