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/
[email protected]/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> name: 'contact'.map([ 7 { route: [''. 'home'].html file in the project root directory.js as aurelia-app 1 // main.router = router. create index. To use Aurelia’s router. 13 } 1. .js 2 export function configure(aurelia) { 3 aurelia.setRoot('your-directory/your-class. Navigation Menu As a first example. name: 'about'. 11 { route: 'contact'. 9 { route: 'about'.start().js') instead app.js 2 export class App { 3 configureRouter(config. moduleId: 'navigation-menu/pages\ 12 /contact'.use 4 5 //You can use custom configuration 6 . your component view must have a <router-view></router-view> element.js file for implement Aurelia configuration. router) { 4 config.standardConfiguration() 7 8 //Show logging 9 .then(() => aurelia. name: 'home'.js contains aurelia route config 1 // app. 14 15 this. nav: true. In order to configure the router. nav: true. title: 'Contact' } 13 ]). nav: true. moduleId: 'navigation-menu/pages\ 10 /about'. title: 'Home' }. title: 'About' }.setRoot()).developmentLogging(). Five Practical Examples For Learning Aurelia 2 At first. we will build a navigation menu that to routes the selected entry.title = 'Navigation Menu'. the component’s view-model requires a configureRouter() function.js 12 aurelia. 10 11 //You can use aurelia. Listing-3: app. 5 // Define routes 6 config. moduleId: 'navigation-menu/pages\ 8 /home'. Then creating app directory and main. Listing-2: main. css"></require> 7 8 <!-.require menu file --> 4 <require from="nav-bar.show routes --> 13 <router-view></router-view> 14 </div> 15 </template> At the top. Listing-5: nav-bar.app.bind="router"></nav-bar> 10 11 <div class="page-host"> 12 <!-.html --> 2 <template> 3 <!-.html as navigation bar 1 <!-. By doing so. Five Practical Examples For Learning Aurelia 3 16 } 17 } Listing-4: app. you can use ViewModels and Models as minor at page.title}</span> 15 </a> 16 </div> . you can see require element for import HTML and CSS file by Url that define in from attribute.require bootstrap css file --> 6 <require from="bootstrap/css/bootstrap.html"></require> 5 <!-.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.html as router view 1 <!-.nav-bar.using custom element --> 9 <nav-bar router. in" href. 2. you are familiar with the ${row.bind="row.title} syntax.We will use a controller that will initialize the models and declare two methods for toggling the visibility of the tooltip. it replaces it with the contents of the variable.bind="router. this HTML is called View Model that contains a Model by same name. In Aurelia’s terminology. When it changes.show loading --> 29 <li class="loader" if.for="row of router.title}</a> 25 </li> 26 </ul> 27 <ul class="nav navbar-nav navbar-right"> 28 <!-. we are using custom element to set and read the route list. 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 <!-.isActive ? 'activ\ 22 e' : ''}"> 23 <a data-toggle="collapse" data-target="#skeleton-navigation-navbar-col\ 24 lapse. If you have used JavaScript templates or Model before.repeat li tag to display route title --> 21 <li repeat. Inline Editor For the second example.href">${row. we will create a simple inline editor clicking a paragraph will show a tooltip with a text field. When the framework sees such a string. it causes change the route and the HTML that uses it to be updated automatically.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.navigation" class="${row. . Listing-7: app.showtooltip = false./app. It is shown only when the showtooltip variable\ 5 is truthful --> 6 <div class="tooltip" click.bind="title" /> 13 </div> 14 <!-. 14 } 15 } Adding properties or functions to it makes them available to the view.showtooltip. --> 12 <input type="text" value. 13 this.css"></require> 3 <div id="main" click.bind=\ 7 "showtooltip"> 8 <!--value. 5 this.stopPropagation().trigger="toggleTooltip($event)">${title}</p> 17 </div> 18 </template> .bind binds the contents of the text field with the "title"\ 9 model. Five Practical Examples For Learning Aurelia 5 Listing-6: app.'. 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).showtooltip = false.html 1 <template> 2 <require from=". 10 } 11 toggleTooltip(e) { 12 e.trigger="hideTooltip()"> 4 <!-. and 11 all other bindings on the page that depend on it.trigger="$event.showtooltip = !this.Call a method defined in the InlineEditorController that toggles 15 the showtooltip variable --> 16 <p click.stopPropagation()" show. 9 this.js 1 export class App { 2 constructor() { 3 //Here we set some default values: 4 this.title = 'Edit me. 6 } 7 hideTooltip() { 8 // In this case it will hide the tooltip.This is the tooltip. 10 Any changes to the text field will automatically update the title. to turn a number into a properly formatted price. 29 service. 20 price: 220. Order Form In this example. 8 price: 300. In the example below. 21 active: false 22 } 23 ]. 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.lastServiceName = service.active.services = [ 6 { 7 name: 'Web Development'. 30 this. I am using the currency format. we will code an order form with a total price updated in real time.name.js 1 import {computedFrom} from 'aurelia-framework'. { 11 name: 'Design'. complete with a dollar sign and cents. 13 active: false 14 }. 9 active: true 10 }. 2 export class App { 3 constructor() { 4 //Here we set some default values: 5 this. Listing-8: app.lastServiceName = ''. 16 price: 250.active = !service. Five Practical Examples For Learning Aurelia 6 3. { 15 name: 'Integration'. using another one of Aurelia’s useful features as ValueConverter. 17 active: false 18 }. 26 } 27 toggleActive(service) { 28 // In this case it will hide the tooltip. 31 } 32 @computedFrom('lastServiceName') . 24 // using for computed observe 25 this. { 19 name: 'Training'. 12 price: 400. 42 } 43 } Aurelia polls your property for changes because it has no way of knowing when your property-getter will return a different value. 2 export class CurrencyFormatValueConverter { 3 toView(value) { 4 return numeral(value). . no polling would be needed. Simply decorate any property with @computedFrom(propertyName1[.00)').services.active) { 38 total += service. To avoid the polling you could tell Aurelia’s binding system what to observe. propertyName2.format('($0. If it were a simple property (without a getter). 5 } 6 } We created a value converter CurrencyFormatValueConverter.0.the @computedFrom dec- orator. 35 var total = 0.price. It has a toView method that the Aurelia framework will apply to model values before displaying that in the view..js 1 import numeral from 'numeral'. propertyNameN]) and Aurelia’s binding system will observe the specified properties and re-evaluate bindings when any of the properties change. Five Practical Examples For Learning Aurelia 7 33 get total() { 34 // "total" will be observed.. 36 this. Aurelia could observe the property directly. 41 return total. Listing-9: currency-format. Our converter use the NumeralJS library to format the data..forEach((service) => { 37 if (service. Aurelia’s binding system has a method for observing computed properties. 39 } 40 }). for="service of services" click.active?'active':''"> 12 <!-.Notice the use of the currency filter. assign a click handler. and se\ 8 t or 9 remove the "active" css class if needed --> 10 <li repeat.for binding is another useful feature of the framework.bind="service. Instant Search This example will allow users to filter a list of items by typing into a text field./currency-format"></require> 4 <form role="form"> 5 <h1>Services</h1> 6 <ul> 7 <!-.price | currencyFormat}</span> 15 </li> 16 </ul> 17 <div class="total"> 18 <!-. . Format it as \ 19 currency. ${total | currencyFormat} 4. it will format the p\ 13 rice --> 14 ${service.trigger="toggleActive(ser\ 11 vice)" class.html 1 <template> 2 <require from=". Five Practical Examples For Learning Aurelia 8 Listing-10: app. This is another place where Aurelia shines. It lets you loop through an array of items and generate markup for them. and is the perfect use case for writing a value converter.name} <span>${service.css"></require> 3 <require from=". Finally we applied the converter in the binding using the pipe | syntax.Calculate the total price of all chosen services. It is intelligently updated when an item is changed or deleted. --> 20 Total: <span>${total | currencyFormat}</span> 21 </div> 22 </form> 23 </template> The repeat./app.Loop through the services array. Listing-12: seacrh-for. 2 import {HttpClient} from 'aurelia-fetch-client'.push(item). Five Practical Examples For Learning Aurelia 9 Listing-11: app.then(response => response.com/'). 3 import 'fetch'. 12 }). 14 return result. 7 searchString = searchString. 14 } 15 activate() { 16 return this. 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.http = http. 5 } 6 var result = [].fetch('users') 17 .js 1 import {inject} from 'aurelia-framework'. 12 } 13 }).configure(config => { 9 config 10 . 19 } 20 } The HttpClient is another useful feature of the framework.http. 4 @inject(HttpClient) 5 export class App { 6 constructor(http) { 7 //Here we set some default values: 8 http. 13 this.users = users).js 1 export class SearchForValueConverter { 2 toView(array. It is merely a thin wrapper around the native Fetch specification.then(users => this.forEach(function (item) { 10 if (item.login.toLowerCase().toLowerCase(). 8 // Using the forEach helper method to loop through the array 9 array. .useStandardConfiguration() 11 .indexOf(searchString) !== -1) { 11 result.github.withBaseUrl('https://api.json()) 18 . searchString) { 3 if (!searchString) { 4 return array. html_url"><img src.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.css"></require> 3 <require from="./app.bind="user.Render a li element for every entry in the items array.bind="searchString" placeholder="Enter your\ 9 search terms" /> 10 </div> 11 <ul> 12 <!-. It takes the value of the 14 searchString model as an argument. I will introduce another important . Notice 13 the custom search value converter "searchFor". This is very easy to do in Aurelia. Switchable Grid Another popular UI interaction is switching between different layout modes (grid or list) with a click of a button.for="user of users | searchFor:searchString"> 17 <a href. 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. In addition.bind="user./search-for"></require> 4 <div id="main"> 5 <div class="bar"> 6 <!-. Then we’d be able to specify the value in the binding and get filter users out of our value converter. A better approach would be to modify the converters to accept a searchString parameter. It would be quite repetitive to define a converter for each value we needed to display. Listing-13: app.Create a binding between the searchString model and the text fi\ 7 eld --> 8 <input type="text" value. 15 --> 16 <li repeat.avatar_url" /><\ 18 /a> 19 <p>${user.html 1 <template> 2 <require from=". /instagram-service'. or another data source. . 4 import 'fetch'. 18 }) 19 } 20 } If you create a service called InstagramService and inject it throughout different parts of your app.fetchPopular(function (data) { 15 // Assigning the pics array will cause the view 16 // to be automatically redrawn by Aurelia. provided you’re injecting it without telling Aurelia to do anything non-standard. 5 @inject(HttpClient) 6 export default class InstagramService { 7 constructor(httpClient) { 8 let clientId = '3872137809. 14 this. Listing-15: app.instagramService.pics = data. 11 } 12 activate() { 13 let that = this. Listing-14: app. we will write a service that communicates with Instagram’s API and returns an array with the most popular photos at the moment. 4 @inject(InstagramService) 5 export class App { 6 constructor(instagramService) { 7 //Here we set some default values: 8 this.js 1 //instagram-service.instagramService = instagramService.pics = []. an API. 9 this.js 1 // app.11de5ddc30274a349dd7d09141677115'.js 2 import {inject} from 'aurelia-framework'. 9 this.layout = 'grid'. Five Practical Examples For Learning Aurelia 11 concept – Services. 3 import {HttpClient} from 'aurelia-http-client'. In our case. A class by default in Aurelia is a singleton and unless you specify otherwise using a decorator like @transient() then that is guaranteed.js 2 import {inject} from 'aurelia-framework'.http = httpClient. They are objects that can be used by your application to communicate with a server. it will be a singleton. 3 import InstagramService from '. 17 that.b91ba91. 10 this. instagram./app. 'callback'). 18 } 19 } Instagram API expects callback GET parameter to be present in request URL.http. However.bind="item. We choose which one to show depending on the "\ 13 layout" binding --> 14 <ul show. Listing-16: app.A view with big photos and no text --> 16 <li repeat. 6 which causes the correct UL to be shown. including the AJAX request.bind="layout == 'grid'? 'active' : ''" cl\ 10 ick. Warning Due to changes in the Instagram API. this demo is no longer working. 17 }).low_resolution.jsonp(this.content. the rest of the code.css"></require> 3 <section> 4 <div class="bar"> 5 <!-.bind="layout == 'list'? 'active' : ''" cl\ 8 ick.url = 'https://api.These two buttons switch the layout variable. Five Practical Examples For Learning Aurelia 12 10 this.We have two layouts.bind="layout == 'grid'" class="grid"> 15 <!-. is still correct.link" target="_blank"><img src.ima\ 18 ges.for="item of pics"> 17 <a href.url.html 1 <template> 2 <require from=".then(response => { 16 callback(response.trigger="layout = 'grid'"></a> 11 </div> 12 <!-.trigger="layout = 'list'"></a> 9 <a class="grid-icon" class.bind="item.url" /></a> 19 </li> 20 </ul> . This parameter should be a function name to wrap response object into (Aurelia will add something like &callback=JSON_- CALLBACK).data).com/v1/tags/puppy/media/recent?code=19\ 11 b6dee5c5f24950b026a8cd591a99bb&access_token=' + 12 clientId + '&callback=JSON_CALLBACK' 13 } 14 fetchPopular(callback) { 15 return this. It adds callback parameter automatically behind the scene. --> 7 <a class="list-icon" class. thumbnail. Five Practical Examples For Learning Aurelia 13 21 <ul show.caption.bind="layout == 'list'" class="list"> 22 <!-.for="item of pics"> 24 <a href.link" target="_blank"><img src.ima\ 25 ges.bind="item.bind="item.url" /></a> 26 <p>${item.text}</p> 27 </li> 28 </ul> 29 </section> 30 </template> .A compact view smaller photos and titles --> 23 <li repeat. owned by Google. http://www. For that. Listing-17: index.Firebase password authentication . This section has the following chapters: .js 1 export function configure(aurelia.js file goes in our resources directory and should always export a configure method. For our purpose let’s call our file index. Firebase (Firebase is a Real-Time Database. and static website hosting for your Aurelia app.firebase. 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. Our index. 1 http://www. We are going to create an configuration. just the configure function. creating a file in resources directory and importing to index.js.com . Reactive data collections (auto-sync) and other Firebase features.js file that holds our firebase configurations. authentication.Creating Aurelia plugin for Firebase .firebase.js.Firebase ReactiveCollection Creating a Firebase plugin for Aurelia A Firebase plugin for Aurelia that supports Authentication. It doesn’t have to export a class.com1 ) Firebase is a backend service that provides data storage. innerConfig.values[identifier]. 7 config: { 8 apiKey: 'AIzaSyAx02B4a0nbMvQxsvliyMmIjlWKqfIm58M'._defaults = { 6 name: 'practicalaurelia'. 10 databaseURL: "https://practicalaurelia. ConfigurationDefaults.com". 19 20 export class Configuration { 21 22 constructor(innerConfig) { 23 this.innerConfig !== null) { 33 return this._defaults).values.assign(defaults.defaults = function () { 15 let defaults = {}. 17 return defaults. 25 } 26 27 getValue(identifier) { 28 if (this. Firebase 15 Listing-18: configuration.innerConfig ? {} : ConfigurationDefaults. 16 Object. 18 }.values[ident\ 29 ifier] !== undefined) { 30 return this.js 1 export class ConfigurationDefaults { 2 3 } 4 5 ConfigurationDefaults.values = this.values[identifier] = value. 40 return this. 24 this.firebaseapp. 34 } 35 throw new Error('Config not found: ' + identifier). // fluent API 41 } .innerConfig = innerConfig.firebaseio.defaults(). 31 } 32 if (this.getValue(identifier).hasOwnProperty(identifier) !== null && this. value) { 39 this. 9 authDomain: "practicalaurelia. 13 14 ConfigurationDefaults. 36 } 37 38 setValue(identifier.com" 11 } 12 }. therefore it can be used in main configuration. • firebase-storage .The core firebase client (required). As you can see here.Firebase Cloud Messaging (optional). 2 3 export { Configuration } from '.js 1 import { Configuration } from '. This function returns the current configuration instance (Fluent API) Listing-19: index. This function returns the value of the configuration option. . • firebase-auth . The individually installable components are: • firebase-app . 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. throws new error. config). 4 export function configure(aurelia. You can reduce the amount of code your app uses by just including the features you need. • firebase-messaging .The Firebase Realtime Database (optional). new instance from configuration has been made and set to aurelia configCallback. 8 } 9 aurelia. configCallback) { 5 let config = new Configuration(Configuration.Firebase Storage (optional). • firebase-database . If not provided will initialize using the defaults./configuration'. Storage and the Realtime Database. 6 if (configCallback !== undefined && typeof configCallback === 'function') { 7 configCallback(config). For gets the value of a configuration option by its identifier we use getValue(identifier) that contains a param for the configuration option identifier. Configuration class used by the plugin. If configuration option is not found.instance(Configuration.defaults).js. we use the constructor for initializes a new instance of the Configuration class that contains an object params that the optional initial configuration values./configuration'.Firebase Authentication (optional). By setValue we can sets the value of a configuration option. 10 } Now we can import and export configurations in index. password.password.standardConfiguration() 4 . 'myName') 6 }) 7 aurelia.provider || null.password || {}.profileImageURL || null. 5 auth = null.token = userData.update(userData).auth = userData.profileImageUrl = userData.expires || 0. 4 token = null.isTemporaryPassword || fals\ 25 e. 3 provider = null. 21 this.auth || null.expires = userData. 12 } 13 constructor(userData = null) { 14 this.password. 19 this.password = userData.isTemporaryPassword = userData.setValue('name'. 24 this.js 1 export function configure(aurelia) { 2 aurelia. 8 isTemporaryPassword = null.uid || null. 9 profileImageUrl = null. 18 this.email = userData. Listing-21: user.use 3 .then(() => aurelia.expires > 0) || false. 26 this. 7 email = null. we are going to create User model that represents a Firebase User.setRoot()).start(). 23 userData. Firebase 17 Listing-20: main. 6 expires = 0. 8 } Now that we have our configurations. 27 this.auth && this.provider = userData. .email || null.plugin('resources/index'. 15 } 16 update(userData) { 17 userData = userData || {}. config => { 5 config. 22 this.uid = userData. 10 get isAuthenticated() { 11 return (this.js 1 export class User { 2 uid = null. 20 this.token || null.token && this. The URL to the user’s Gravatar profile image Also.A unique user ID./configuration'.The user’s email address • isTemporaryPassword ./configuration'.defaults).The authentication method used • token . 5 export function configure(aurelia.A timestamp. what remains is just exporting in index. 31 } 32 } At this model we have many property that contains: • uid .instance(Configuration. indicated when the authentication token expires • email .update({}).Whether or not the user authenticated using a temporary password.js 1 import { Configuration } from '. intented as the user’s unique key accross all providers • provider .The contents of the authentication token • expires . 4 export { Configuration } from '. 9 } 10 aurelia. Listing-22: index. We also have an constructor for initializes a new instance of the user.The Firebase authentication token for this session • auth .Reinitializes the current user instance./user'. 2 3 export {User} from '.js. as used in password reset flows • profileImageUrl .Update the current user instance with the provided data • reser . we’ve many method for manage users that contains: • isAuthenticated . Firebase 18 28 } 29 reset() { 30 this. 11 } . 7 if (configCallback !== undefined && typeof configCallback === 'function') { 8 configCallback(config). in seconds since UNIX epoch. config). configCallback) { 6 let config = new Configuration(Configuration.Whether or not the user is authenticated • update . and to manage your app’s password-based accounts. 2 import Promise from 'bluebird'. install bluebird: jspm install npm:bluebird Bluebird is a fully featured promise library with focus on innovative features and performance. Now that we have our dependencies installed. 5 import { User } from '. 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 in resources directory. Firebase 19 Firebase password authentication You can use Firebase Authentication to let your users authenticate with Firebase using their email addresses and passwords.js./user'. see the Why Promises?2 article.html . 6 7 @inject(Configuration. 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.Attention. we are going to create a new file as auth. 3 import Firebase from 'firebase'. 4 import { Configuration } from '. First we inject our dependencies for initialize Firebase App and create a new user in the construc- tor. Firebase) 8 export class Auth { 2 http://bluebirdjs.com/docs/why-promises. This page will explore why one might use bluebird promises over other third party or the standard library implementations.js 1 import { inject } from 'aurelia-framework'. so we use auth() method. Listing-23: auth./configuration'. Next. this class created for users manager. For reasons why to use promises in general. initializeApp(configuration. 12 this. 4 import { Configuration } from '.firebase.getValue('config').currentUser = new User().getValue('name')). firebase) { 10 var app = firebase. 3 import Firebase from 'firebase'.email = user. password). . confi\ 11 guration. reject) => { 17 this. password) { 16 return new Promise((resolve.js 1 import { inject } from 'aurelia-framework'. 14 } 15 } Let’s start by looking at what we’d like to achieve. 24 }).createUserWithEmailAndPassword(email.firebase = app./configuration'. 2 import Promise from 'bluebird'. Listing-23: auth. 14 } 15 createUser(email. this user will also be signed.currentUser = new User(). confi\ 11 guration. firebase) { 10 var app = firebase. 23 }). 25 } 26 } On successful creation of the user account. 6 7 @inject(Configuration.getValue('config')./user'.then((\ 18 result) => { 19 let user = new User(result).auth(). 13 this. Firebase) 8 export class Auth { 9 constructor(configuration.getValue('name')).auth(). 12 this. User account creation can fail if the account already exists or the password is invalid. // Because firebase result doe\ 21 sn't provide the email 22 resolve(user).email || email. 5 import { User } from '. 20 user. To create a new user accounts with a password we must passing the new user’s email address and password to createUserWithEmailAndPassword method.initializeApp(configuration. Firebase 20 9 constructor(configuration.firebase = app. 13 this. password) { 17 return new Promise((resolve. pass the user’s email address and password to signInWithEmailAndPassword method. Firebase) 8 export class Auth { 9 constructor(configuration. password). Have a look at below to get the signed in user details the similar to creating a new account. confi\ 11 guration. 3 import Firebase from 'firebase'. If the new account was created. This function will create a new user account and set the initial user password. the user is signed in automatically. 2 import Promise from 'bluebird'.firebase.. Listing-24: auth./configuration'. 23 }). 12 this. the user name and password.currentUser = user. reject) => { 18 this. . 16 signIn(email.getValue('config'). or auth provider information—the user signed in with.getValue('name')). firebase) { 10 var app = firebase. 21 this.js 1 import { inject } from 'aurelia-framework'. 6 7 @inject(Configuration. 13 this.currentUser = new User(). 14 } 15 // ..firebase = app. To user signs in to app. Firebase 21 Note The email address acts as a unique identifier for the user and enables an email-based password reset. a new user account is created and linked to the credentials— that is. and can be used to identify a user across every app in project. 4 import { Configuration } from '. 24 })./user'.auth(). regardless of how the user signs in. 25 } 26 } After a user signs in for the first time.initializeApp(configuration. 22 resolve(user).then(resul\ 19 t => { 20 let user = new User(result).signInWithEmailAndPassword(email. 5 import { User } from '. This new account is stored as part of Firebase project. reset(). you can get the signed-in user’s unique user ID from the auth variable.js 1 import { inject } from 'aurelia-framework'. 2 import Promise from 'bluebird'..firebase = app.currentUser = new User().getValue('name')). You can then get the user’s basic profile information from the User object.auth(). 22 }). 13 this. 5 import { User } from '.. • In Firebase Realtime Database and Firebase Storage Security Rules./user'. 16 signOut() { 17 return new Promise((resolve. Firebase) 8 export class Auth { 9 constructor(configuration.signOut().initializeApp(configuration. 21 }). 14 } 15 // . 23 } 24 } Other methods are similar to creating a new account as you can see below: . Listing-25: auth.firebase. and use it to control what data a user can access. reject) => { 18 this. firebase) { 10 var app = firebase. 12 this. confi\ 11 guration. the recommended way to know the auth status of your user is to set an observer on the Auth object.currentUser.getValue('config'). 6 7 @inject(Configuration. Firebase 22 • In application. 3 import Firebase from 'firebase'. To sign out a user. 4 import { Configuration } from './configuration'. call signOut. 20 resolve().then(result => { 19 this. 25 } 26 createUserAndSignIn(email.getValue('name')). 6 7 @inject(Configuration.then(() => { 28 return this.currentUser = new User(). 24 }).firebase. 12 this.createUserWithEmailAndPassword(email. password). 37 resolve(user). 3 import Firebase from 'firebase'. 2 import Promise from 'bluebird'./user'. password)./configuration'.firebase.auth(). 20 user. password) { 32 return new Promise((resolve.firebase = app. password) { 16 return new Promise((resolve. 13 this. reject) => { 17 this. 39 }).createUser(email. 38 }).signIn(email. Firebase) 8 export class Auth { 9 constructor(configuration. Firebase 23 Listing-26: auth.signInWithEmailAndPassword(email. password). 40 } 41 getCurrentUser() { .getValue('config'). // Because firebase result doe\ 21 sn't provide the email 22 resolve(user). 14 } 15 createUser(email. confi\ 11 guration. 30 } 31 signIn(email. reject) => { 33 this. password) { 27 return this. 29 }). password).then(resul\ 34 t => { 35 let user = new User(result).currentUser = user.js 1 import { inject } from 'aurelia-framework'.email = user. 4 import { Configuration } from '. 5 import { User } from '. 36 this.email || email.initializeApp(configuration. 23 }). firebase) { 10 var app = firebase.then((\ 18 result) => { 19 let user = new User(result). currentUser.getCurrentUser(). reject) => { 65 var user = this.currentUser. 69 }). 57 user. I am going to start by defining the needed routes. 43 } 44 changeEmail(oldEmail. newEmail) { 45 return new Promise((resolve. 59 resolve(result).email = newEmail.html and signup. The first two will map the index and signup URLs to the app. 53 } 54 changePassword(oldPassword.delete(). 61 }).then(() => { 58 let result = { email: email }. newEmail }. 60 }).firebase. 68 }). respectively.updateEmail(newEmail). newPassword) { 55 return new Promise((resolve. 47 user. Firebase 24 42 this.html views. 52 }). 49 let result = { oldEmail. . 70 } 71 } Let’s see how it works on Aurelia.updatePassword(newPassword).then(() => { 48 this. password: string): Promise { 64 return new Promise((resolve.getCurrentUser().then(() => { 67 resolve(). 50 resolve(result). 66 user. which are the mappings between specific URLs and the views that should be displayed when the browser navigates to that URL.getCurrentUser(). 62 } 63 deleteUser(email: string. 51 }). reject) => { 56 var user = this. reject) => { 46 var user = this. 2 export class App { 3 configureRouter(config.html 1 <template> 2 <ul if. AuthorizeStep). title: 'Dashboard' }. moduleId: 'accou\ 10 nt/signup'. 6 config. Listing-28: navbar identity nav-bar. nav: true. 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. name: 'dashboard'. title: 'Account'.isAuthenticated" class="nav navbar-nav navbar-right"> 3 <li> 4 <p class="navbar-text">Welcome.js 1 import 'bootstrap/css/bootstrap. auth: true } 13 ]). Let’s start at the top and setup the navigation bar. router) { 4 config. title: 'Sign up' }. 'dashboard']. moduleI\ 12 d: 'account/index'. In the above the accountIndex route is only available for authenticated users.css!'.map([ 7 { route: [''.bind="user. name: 'accountIndex'.bind="!user. Firebase 25 Listing-27: app. 'account/index'].min. 9 { route: ['account/signup'].router = router.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.title = 'Aurelia on Fire'. 14 this. 15 } 16 } In the router config function. moduleId: 'dashboard/\ 8 index'.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> . name: 'accountSignup'. 5 config. 11 { route: ['account'.addPipelineStep('authorize'. 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. message. 11 this. 12 } 13 signUp() { 14 this.then(() => { 16 this.catch((e) => { 19 this.js and signup. Firebase 26 17 <li><a click. Router) 5 export class SignUp { 6 email = null. this. which is made available via constructor injection.router. 3 import { Auth } from 'resources/index'. 9 constructor(authManager. router) { 10 this.router = router. 17 }) 18 .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. 20 }).aureliaFirebase = authManager.message = e.html file.email. which either creates a new user or returns an error if there was a problem. The signup() method uses Auth to send a POST request to the API.createUserAndSignIn(this. This allows us to hide any nav menu items that are to be protected if the user isn’t authenticated. . SignUp If you guessed that we need to create a signup. and Logout links. 7 password = null. 8 message = null. and this is how we will hide the super-secret-quote menu item when the user isn’t logged in. 4 @inject(Auth. Login.navigateToRoute('accountIndex'). 2 import { Router } from 'aurelia-router'.aureliaFirebase. 21 } 22 } The signup view model will speak directly with the auth plugin service. you are correct.password) 15 . We’re also conditionally showing the Signup. Here’s the source: Listing-29: signup view model 1 import { inject } from 'aurelia-framework'. . signout and resetPassword routes. Next.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.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="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.delegate="signup()" for submit. SignIn The signin route is pretty similar.bind="password"> 16 </div> 17 <div if. The JavaScript for signin looks similar as well. We’ve also got an alert box at the bottom to show the user any errors that are returned.delegate="signin()" and adjust the other pieces of markup appropriately. 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. You’ll just need to swap out submit. we’re providing two <input>s that take the user’s email and password. let’s set up the signin. . 11 } 12 run(navigationInstruction. 3 import { Auth } from 'resources/index'.email. next) { . change the route to accountIndex. Let’s see how that works: Listing-32: app.signIn(this.aureliaFirebase. after signing user.message = null. 2 import { Auth.then(() => { 17 this.fbConfig = config.message = e.catch((e) => { 20 this.navigateToRoute('accountIndex'). config) { 9 this. 18 }) 19 . 12 } 13 signIn() { 14 this.router. 21 }). 2 import { Router } from 'aurelia-router'. 4 @inject(Auth. Firebase 28 Listing-31: signin view model 1 import { inject } from 'aurelia-framework'.password) 16 . 7 password = null. Configuration } from 'resources/index'. 8 message = null. 9 constructor(auth. 15 this. Configuration) 7 class AuthorizeStep { 8 constructor(authManager. 5 6 @inject(Auth..aureliaFirebase = auth. 3 4 //.message. Router) 5 export class SignIn { 6 email = null. this.router = router. But we should check the user is authenticated in the route config. 22 } 23 } In the above code..js 1 //..authManager = authManager. 11 this. router) { 10 this. 10 this. signout() ‘Listing-33SignOutinnav-bar.router.google. 5 } Change Password and Change Email The changePassword and the changeEmail has the same pattern using updatePassword and updateEmail.navigateToRoute('dashboard').then(() => { 3 this. first you must create a file in resource directory as collection.isAuthenticated) { 19 return next.com/docs/auth/ .getAllInstructions().currentUser. Firebase 29 13 // Check if the route has an "auth" key 14 // Then check if the current authenticated user is valid 15 if (navigationInstruction.html’ 1 signOut() { 2 this.authManager || !this.config.cancel(new Redirect(this.auth))\ 16 { 17 if (!this.some(i => i. 20 } 21 } 22 return next().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. 4 }).fbConfig.currentUser || !this.authManager.getLoginRoute())).aureliaFirebase.signOut(). 23 } 24 } SignOut The signout route essentially follows the same pattern using .auth\ 18 Manager. To use it. For another methods you can see Firebase docs3 Firebase ReactiveCollection The ReactiveCollection class handles firebase data synchronization. 3 https://firebase. 2 import Firebase from 'firebase'.. 3 import {Configuration} from '. 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.get(Configuration).js 1 import Promise from 'bluebird'.instance. . First you must doing import Container from aurelia-dependency-injection. 11 if (!config) throw Error('Configuration has not been set'). } To get an instance of Firebase you should receive it from Aurelia Container and use the database() the database service./configuration'. 12 } 13 } Now you need to make a name for table to communicate with the database that you can use constructor. then use it to get instance of Configuration. 5 6 export class ReactiveCollection { 7 constructor(){ 8 if (!Container || !Container.instance) throw Error('Container has not be\ 9 en made global').js’ 1 constructor(path: Array<string>){ . 3 import {Container} from 'aurelia-dependency-injection'. Firebase 30 Listing-34: collection. ‘Listing-36:collection. Listing-35: collection./configuration'. 4 import {Configuration} from '. 2 import Firebase from 'firebase'.. 10 let config = Container. By doing this you will be able to using Firebase storage API.js 1 import Promise from 'bluebird'. 3 var firebase = Container. ‘Listing-38’ 1 constructor(){ 2 //.js’ 1 constructor(path: Array<string>) { 2 //. Attach the listener to a location using Query.on('child_changed'. (snapshot. 3 this. Now that you have access to reference by path you can use ChildEventListener insterface.database. 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. 2 _listenToQuery(query) { 3 let that = this.js 1 //. previousKey) => { 11 that.ref(path).on('child_added'._listenToQuery(this._query)..get(Firebase). (snapshot.. previousKey). (snapshot) => { 8 that. 9 }).database = firebase.on('child_removed'.apps[0].database() 6 this. previousKey). .database. previousKey) => { 5 that..Reference._onItemChanged(snapshot._onItemRemoved(snapshot). Firebase 31 ‘Listing-37:collection. Classes implementing this interface can be used to receive events about changes in the child locations of a given Firebase ref._onItemAdded(snapshot. 4 query.. 4 // You can use application name instead '[0]' to manage many applications 5 this. 6 }). 4 } Listing-39: collection.. The listener is triggered once for the initial state of the data and again anytime the data changes.instance.. 7 query.addChildEventListener(ChildEventListener) and the appropriate method will be triggered when changes occur. 10 query._query = this. 9 } 10 resolve(item). so: .. • Child Changed: This method is triggered when the data at a child location has changed. previousKey). your item will be updated. Let’s get started to create methods for working with data. 8 return.. 13 } 14 //. 12 }). Note that in child_added and child_changed.set(item. Firebase 32 12 }).js 1 //. 15 }). if you don’t use it. (error) => { 6 if (error) { 7 reject(error). • 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.push(). 16 } 17 //.on('child_moved'. the new child or the child location. 13 query. All of the above have snapshot parameter. child_added or child_changed runs. that is an immutable snapshot of the data. (snapshot.. • Child Moved: This method is triggered when a child location’s priority changes._onItemMoved(snapshot. When item was created or was updated. 5 query. First. reject) => { 4 let query = this. The previousKey is the key name of sibling location ordered before the child. previousKey) => { 14 that. 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. 11 }). to create Add method as below: Listing-40: collection. Note In add method you must use the push() function.._query. 2 add(item): Promise { 3 return new Promise((resolve.. 0. 6 __firebasePrimitive__: true 7 }.get(previousKey)) + 1 : 0.set(value. 6 } 7 8 removeByKey(key) { 9 return new Promise((resolve..__firebaseKey__ === null) { 3 return Promise. 4 let index = previousKey !== null ? 5 this. 8 } 9 value.__firebaseKey__). value). 7 this.key. 4 } 5 return this.val(). value). reject) => { .__firebaseKey__ = snapshot.splice(index.js 1 //.indexOf(this._valueFromSnapshot(snapshot).items. Listing-42: collection. it will be held in items list in _valueFromSnapshot function key or the id assigned to firebase will be added to item._valueMap.__firebaseKey__.removeByKey(item. 8 } 9 //. The saved value in _onItemAdded will be get with the help of _valueFromSnapshot and after its index check. 6 this.js 1 _valueFromSnapshot(snapshot) { 2 let value = snapshot.. 11 } The next step is to create the Remove method as below: Listing-43: collection. 3 if (!(value instanceof Object)) { 4 value = { 5 value: value. 2 _onItemAdded(snapshot. 10 return value. previousKey) { 3 let value = this.reject({ message: 'Unknown item' })..items.js 1 remove(item): Promise { 2 if (item === null || item._valueMap.. Firebase 33 Listing-41: collection. _query).js 1 _stopListeningToQuery(query) { 2 query. .instance.off(). 18 } For stop listen to query you can use this method: Listing-44: collection.database = firebase. 15 let config = Container._query = this._query.ref(path). 3 import { Container } from 'aurelia-dependency-injection'.child(key). 14 } 15 resolve(key). 4 import { Configuration } from '. 2 import Firebase from 'firebase'.js 1 import Promise from 'bluebird'.remove((error) => { 11 if (error) { 12 reject(error)._listenToQuery(this.get(Firebase). 13 return. 16 if (!config) throw Error('Configuration has not been set'). 9 _valueMap = new Map(). 10 items = []. 20 this. 18 // You can use application name instead '[0]' to manage many applications 19 this. 17 }). 5 6 export class ReactiveCollection { 7 8 _query = null. 21 this.get(Configuration).instance) throw Error('Container has not be\ 14 en made global').database. 11 12 constructor(path: Array<string>) { 13 if (!Container || !Container. 3 } See the complete class below: Listing-45: collection.database().ref(). Firebase 34 10 this.apps[0]. 17 var firebase = Container.instance. 16 })./configuration'. 40 } 41 return this.ref(). 30 return. Firebase 35 22 } 23 24 add(item: any): Promise { 25 return new Promise((resolve. 42 } 43 44 getByKey(key): any { 45 return this. 63 query._query.removeByKey(item. reject) => { 26 let query = this. 53 return.reject({ message: 'Unknown item' }). reject) => { 62 let query = this.remove((error) => { 51 if (error) { 52 reject(error)._query.ref()._query. 57 }). 56 }).get(key).child(key).remove((error) => { . 58 } 59 60 clear() { 61 return new Promise((resolve. 33 }). 46 } 47 48 removeByKey(key) { 49 return new Promise((resolve. 35 } 36 37 remove(item: any): Promise { 38 if (item === null || item. 27 query. 54 } 55 resolve(key).set(item.__firebaseKey__ === null) { 39 return Promise. 34 }). (error) => { 28 if (error) { 29 reject(error).push(). reject) => { 50 this.__firebaseKey__)._valueMap. 31 } 32 resolve(item). 0. ._onItemChanged(snapshot._valueMap. value).items._onItemAdded(snapshot. 66 return. 97 this.on('child_added'. 77 }). previousKey) { 94 let value = this.off().on('child_removed'._valueMap. 95 let index = previousKey !== null ? 96 this. (snapshot) => { 79 that.get(key)._onItemRemoved(snapshot). 75 query. 99 } 100 101 _onItemRemoved(oldSnapshot) { 102 let key = oldSnapshot._onItemMoved(snapshot. value). (snapshot. 103 let value = this._valueFromSnapshot(snapshot).set(value.key.items. 86 }). 98 this. 71 } 72 73 _listenToQuery(query) { 74 let that = this. previousKey).indexOf(this._valueMap. 83 }). (snapshot. previousKey) => { 76 that. 87 } 88 89 _stopListeningToQuery(query) { 90 query. 84 query.__firebaseKey__. previousKey) => { 82 that. previousKey) => { 85 that. 80 }). 70 }). previousKey). 78 query.on('child_changed'. 81 query.get(previousKey)) + 1 : 0. previousKey). (snapshot.splice(index. 69 }). 104 if (!value) { 105 return. 67 } 68 resolve(). 91 } 92 93 _onItemAdded(snapshot. Firebase 36 64 if (error) { 65 reject(error).on('child_moved'. 117 118 if (!oldValue) { 119 return. 130 if (!value) { 131 return.items.key._valueMap.delete(oldValue.__firebaseKey__._valueMap. 125 } 126 127 _onItemMoved(snapshot.items. 132 } 133 let previousValue = this.splice(index. 134 let newIndex = previousValue !== null ? this._valueMap.get(previousKey).items._valueMap. 109 if (index !== -1) { 110 this.items.splice(this.delete(key).splice(this.__firebaseKey__). value). 116 let oldValue = this.items.get(key). 0.__firebaseKey__). value).splice(newIndex._valueMap. 136 this.get(value. previousKey) { 128 let key = snapshot.indexOf(previousValue\ 135 ) + 1 : 0. 1).indexOf(value). 123 this.items.indexOf(oldValue). 147 } . 137 this. value). 1. 108 this. 120 } 121 122 this.items._valueMap._valueFromSnapshot(snapshot). 138 } 139 140 _valueFromSnapshot(snapshot) { 141 let value = snapshot. 129 let value = this.val(). 142 if (!(value instanceof Object)) { 143 value = { 144 value: value. 1). 124 this. previousKey) { 115 let value = this.indexOf(value).set(value. 145 __firebasePrimitive__: true 146 }. 111 } 112 } 113 114 _onItemChanged(snapshot. Firebase 37 106 } 107 let index = this.items. . Firebase 38 148 value.js in collections directory and it will extends ReactiveCollection. ReactiveCollection } from 'resources/index'. 150 } 151 152 static _getChildLocation(root: string. First we want to show the list of todo. path: Array<string>) { 153 if (!path) { 154 return root. Then create a file called todo. then Aurelia has to fall-back on dirty binding. To display a list of tasks you define a function as orderedItems(). 8 this. Essentially it sets up a polling on the object (every 120ms). 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.in other words.endsWith('/')) { 157 root = root + '/'. 160 } 161 } Let’s get started to use the Firebase plugin on Aurelia. 2 3 @inject(Auth) 4 export class TodoCollection extends ReactiveCollection { 5 _user = null. Now you ready to use todo collection.isArray(path) ? path.currentUser. 155 } 156 if (!root.js 1 import { Auth.key. However. 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. The bindings for items are primitive bindings . Listing-46: collection._user = authManager. if you’re not using a primitive. a notification of the change is triggered. it creates an observer on the property so that when the property is changed. 149 return value. when Aurelia binds the html to the property. 158 } 159 return root + (Array.__firebaseKey__ = snapshot. 6 constructor(auth) { 7 super('todos'). Firebase due to use of the Realtime Database you need to use Dirty Binding.join('/') : path). ..log('ordering'). 12 }). } 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.timestamp < item2..items.timestamp) { 6 return -1. 7 } 8 if (item2.timestamp > item2. 10 } 11 return 0.. 13 } For add method you will have: . To avoid dirty-checking use computedFrom decorator. Firebase 39 ‘Listing-47:todo.timestamp) { 9 return 1. item2) => { 5 if (item1. } orderedItemsarranges list by timestamp then returns it: Listing-49: todo.sort((item1.js’ 1 // **this property will require dirty-checking** 2 get orderedItems() { //. ‘Listing-48:todo. 4 return this.js’ @computedFrom('items') get orderedItems() { //.js 1 @computedFrom('items') 2 get orderedItems() { 3 console. The next thing to do is create the index. 13 14 constructor(authManager. Firebase 40 Listing-50: todo. 11 ownerProfileImageUrl: this. 4 5 @inject(Auth. collection: TodoCollection) { 15 this. computedFrom } from 'aurelia-framework'.reject({ message: 'Authentication is required' }). 11 selectedStateFilter = null.collection = collection. you must be logged in.js 1 add(text) { 2 if (!this._user || !this. 12 text: text. 13 timestamp: Math. 12 selectedOwnerFilter = null.now() / 1000).user = authManager. 17 } . 9 todoText = null. 4 } 5 if (!text) { 6 return Promise.add({ 10 ownerId: this.isAuthenticated) { 3 return Promise.currentUser.profileImageUrl. 8 collection = null.js file in the todo directory._user./collections/todo'. 16 this. 10 message = null. 16 } With the above functions you will be able to manage your todo collection in the ViewModel. User } from 'resources/index'.js 1 import { inject._user. 3 import { Auth. 14 isCompleted: false 15 })._user. It’s important to note that.. Listing-51: index.uid.reject({ message: 'A Todo message is required' }).floor(Date. 7 } 8 9 return super. in order to use these methods. TodoCollection) 6 export class TodoIndex { 7 user = null. 2 import { TodoCollection } from '. todoText = null.uid.selectedStateFilter) { 46 items = items.message.items. 42 } 43 44 // Filter by status 45 if (this.selectedStateFilter && !this. 49 }.user. 22 this. Firebase 41 18 19 add() { 20 this. 23 }) 24 . 35 } 36 37 // Filter by owner 38 if (this.catch((e) => { 25 this.message = null. 27 } 28 29 @computedFrom('collection.filter((item) => { 47 return item.selectedOwnerFilter) { 34 return items. 50 } 51 return items.add(this.todoText). let’s use the following HTML to create the view: .isCompleted === (this.collection. 33 if (!this. this).collection.selectedOwnerFilter) { 39 items = items.ownerId === this.then(() => { 21 this.selectedStateFilter === 'compl\ 48 eted'). 26 }). this). Now that you have a view-model with some basic data and behavior. 'selectedOwnerFilte\ 30 r') 31 get filteredItems() { 32 let items = this.message = e. 41 }. 'selectedStateFilter'.filter((item) => { 40 return item.items'. 52 } 53 } Ok. 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="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="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.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.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.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.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.trigger="selectedStateF\ 38 ilter = 'completed'"> 39 <input type="radio" name="selectedStateFilter" autocomplete="off"> C\ 40 ompleted 41 </label> .bind="!user.isAuthenticated" class="form" submit. Firebase 42 Listing-52: index. Enabling Offline Capabilities Firebase applications work even if your app loses its network connection temporarily.do this in above view."> 61 <div class="au-stagger"> 62 <div class="todo-container au-animate" repeat.length} tasks</strong> 58 </div> 59 </div> 60 <div class="todo-list" style="margin-bottom: 20px.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.fromNow(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.times\ 67 tamp)}</div> 68 </div> 69 <div class="description col-sm-10">${item. Firebase 43 42 </div> 43 <div if.for="item of f\ 63 ilteredItems"> 64 <div class="todo row clearfix"> 65 <div class="avatar col-sm-2"> 66 <div class="moment">${$parent.isAuthenticated" class="btn-group" data-toggl\ 44 e="buttons"> 45 <label class="btn btn-default active" 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.bind="user. updating. For example.database().ref("disconnectmessage").error('could not establish onDisconnect event'.onDisconnect().onDisconnect(). 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. and removing.remove(function(err) { 2 if (err) { 3 console.cancel(): . 2 // Write a string when this client loses connection 3 presenceRef. These updates will occur whether the client disconnects cleanly or not. An onDisconnect event can also be canceled by calling .set("I disconnected!"). How onDisconnect Works When an onDisconnect() operation is established. we may want to mark a user as offline when their client disconnects. including setting. The client can use the callback on the write operation to ensure the onDisconnect was correctly attached: ‘Listing-54:onDisconnect’ 1 presenceRef. All write operations. so we can rely on them to clean up data even if a connection is dropped or a client crashes. The server then monitors the connection. it lives on the Firebase Realtime Database server. Firebase 44 Managing Presence In real-time applications. it is often useful to detect when clients connect and disconnect. the server checks security a second time (to make sure the operation is still valid) and then invokes the event. Firebase Database clients provide simple primitives that allow data to be written to the database when a client disconnects from the Firebase Database servers. The server checks security to make sure the user can perform the write event requested and informs the client if it is invalid. err). 4 } 5 }). If at any point it times out or is actively closed by the client. val() === true) { 4 alert("connected"). Service workers are also intended to be used for such things as: • Background data synchronization • Responding to resource requests from other origins .onDisconnect(). 2 connectedRef.info/connected which is updated every time the client’s connection state changes. /. 5 } else { 6 alert("not connected"). 3 // some time later when we change our minds 4 onDisconnectRef. this is no guarantee that a separate client will also read false.cancel(). which response to events dispatched from documents and other sources. Detecting Connection State For many presence-related features.info/connected as false. function(snap) { 3 if (snap.set('I disconnected'). 2 onDisconnectRef. Service worker is an event-driven Web Worker.info/connected is a boolean value which is not synchronized between clients because the value is dependent on the state of the client. The service worker is a generic entry point for event-driven background processing in the Web Platform that is extensible by other specifications. if one client reads /. 7 } 8 }).on("value". it is useful for a client to know when it is online or offline. Here is an example: ‘Listing-56:connectionstate’ 1 var connectedRef = firebase. Firebase 45 ‘Listing-55:cancellmethod’ 1 var onDisconnectRef = presenceRef. Firebase Realtime Database clients provide a special location at /.database(). In other words.info/connected").ref(". Offline app shell Making service worker for you. 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 because this is called from generat\ 21 e-service-worker-dev). 28 // logger: $. 43 paths.output + 'service-worker. 49 // verbose defaults to false.com/GoogleChrome/sw-toolbox#options 34 options: { 35 cache: { 36 maxEntries: 1.output.js' 46 47 ].js'.output + '/**/**. 44 paths. but for the purposes of this demo. 27 handleFetch: false. 48 stripPrefix: paths. . 55 }). 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. 29 runtimeCaching: [{ 30 // See https://github.e. 41 staticFileGlobs: [ 42 paths. callback). 33 // See https://github.css'. 53 54 swPrecache.html'. To install a service worker you need to kick start the process by registering it on your page. log\ 50 more. 37 name: 'runtime-cache' 38 } 39 } 40 }]. This tells the browser where your service worker JavaScript file lives.util.com/GoogleChrome/sw-toolbox#methods 31 urlPattern: /runtime-caching/.write(paths.*'. 51 verbose: true 52 }. 32 handler: 'cacheFirst'.log. 45 paths. config. Firebase 49 20 // If handleFetch is false (i.output + '/**/**. then 22 // the service worker will precache resources but won't actually ser\ 23 ve them.output + '/images/**.output + '/**/**. 27 console.error('The installing service worker became \ 33 redundant. please refresh. 10 11 installingWorker. 41 } .').io/ServiceWorker/spec/service_work\ 8 er/index. 38 }). 25 // It's the perfect time to display a "Content i\ 26 s cached for offline use.installing. 21 console.state) { 13 case 'installed': 14 if (navigator.controller) { 15 // At this point. Firebase 50 ‘Listing-60:registerserviceworker’ 1 if ('serviceWorker' in navigator) { 2 navigator.onupdatefound = function() { 6 // The updatefound event implies that reg.register('dist/service-worker. everything has been precached. e). see 7 // https://slightlyoff.serviceWorker.installing is set. the old content will have been\ 16 purged and the fresh content will 17 // have been added to the cache.onstatechange = function() { 12 switch (installingWorker. 28 } 29 break.error('Error during service worker registration:'.js changes. 40 }).then(function\ 3 (reg) { 4 // updatefound is fired if service-worker." message.html#service-worker-container-updatefound-event 9 var installingWorker = reg. 37 }. 35 } 36 }.js'). 23 } else { 24 // At this point.'). 18 // It's the perfect time to display a "New conte\ 19 nt is available. 5 reg." 20 // message in the page's interface.serviceWorker.log('Content is now available offline!'). 34 break. 30 31 case 'redundant': 32 console.catch(function(e) { 39 console.github.log('New or updated content is available\ 22 . It won’t be able to control pages unless it’s located at the same level or higher than them. 8 handler: 'networkFirst' 9 } 10 }] This pattern is used: 6 https://github.com/slightlyoff/ServiceWorker/issues/468 7 https://github. Firebase 51 Your service-worker. even necessary to use precaching and runtime caching together.com/slightlyoff/ServiceWorker/issues/4686 Cache Remote Files It’s often desirable. Fortunately. You may have seen sw-toolbox7 tool. { 6 // cache Google user profiles pics 7 urlPattern: /^https:\/\/lh3. a scripts/ subdirectory! see https://github.googleusercontent.*/. and wondered how to use them together.com\/. ‘Listing-61:runtimecaching’ 1 runtimeCaching: [{ 2 // cache Google Web Fonts (both css and font files) 3 urlPattern: /^https:\/\/fonts. sw-precache handles this for you.com/GoogleChrome/sw-toolbox . Warning Don’t register service worker file in..(?googleapis|gstatic)\.*/.com\/.g. which handles runtime caching. 4 handler: 'cacheFirst' 5 }. The sw-precache module has the ability to include the sw-toolbox code and configuration alongside its own configuration.js must be located at the top-level directory relative to your site. 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. e. 6 handler: 'fastest'. Let me add this feature in future. .task('build'. 4 ['build-system'. 3 handler: 'networkFirst' 4 }. function(callback) { 2 return runSequence( 3 'clean'.com\/api/. 6 callback 7 ). { 5 urlPattern: /\/articles\//.js or import register-service-worker. 10 name: 'articles-cache' 11 } 12 } 13 }] Now. you can add generate-service-worker task to build process and register service worker in app. Finally. 'generate-service-worker\ 5 ']. 'build-html'. Firebase 52 ‘Listing-62:runtimecachingpattern’ 1 runtimeCaching: [{ 2 urlPattern: /^https:\/\/example\. 7 options: { 8 cache: { 9 maxEntries: 10. 8 }).html ‘Listing-63:buildprocess’ 1 gulp. 'build-css'. we’re going to offline the app data in our application that you can use IndexedDB.js in index. • Easily create client apps using Android. It includes a graphical tool with many of the API composition features of StrongLoop Arc. MS SQL Server. plus assembly and testing of API Gateway policies using the local Micro Gateway. IBM API Connect provides the newest tools to use with LoopBack projects. Connect to multiple data sources. • Use built-in push.js framework. A free version of API Connect especially for developers is available called API Connect Essentials. 8 https://github. open-source Node. connect using JS.38476711.com/strongloop/loopback/?_ga=1. and other devices. web. API Connect also provides its own command-line tool. and JavaScript SDKs. MongoDB. Some features are still available only in Arc but these will be added to API Connect in the next few months.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. SOAP and other REST APIs. • Incorporate model relationships and access controls for complex APIs.js.js modules.js framework that enables you to: • Create dynamic end-to-end REST APIs with little or no coding.Creating server as loopback backend . write business logic in Node.js framework built on top of Express optimized for building APIs for mobile. geolocation. MySQL.Loopback LoopBack is an open source Node.What is LoopBack? . PostgreSQL. • Access data from Oracle. This section has the following chapters: .587365487.1480407550 . and file services for mobile apps. LoopBack consists of: • A library of Node. LoopBack is a highly-extensible. iOS. glue on top of your existing services and data. integrated with API management and gateway features. • Run your application on-premises or in the cloud. iOS & Android SDKs. models.io/ 10 https://github. 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.com/strongloop/loopback-connector-atg . and web clients.com/strongloop/loopback-connector-mysql 15 https://github.js modules that you can use independently or together.Loopback 54 • Yeoman9 generators for scaffolding applications. • StrongLoop Arc. LoopBack tools include: • Command-line tool slc loopback to create applications. and for deploying and monitoring applications. LoopBack modules The LoopBack framework is a set of Node. • Client SDKs for iOS. a graphical tool for editing LoopBack applications.com/strongloop/loopback-connector-mongodb 14 https://github.com/strongloop/loopback-connector-mssql 19 https://github.com/strongloop/loopback 11 https://github.com/strongloop/loopback-connector-postgresql 16 https://github.com/strongloop/loopback-connector-rest 17 https://github. data sources. Android.com/strongloop/strong-remoting 13 https://github.com/strongloop/loopback-datasource-juggler 12 https://github. and so on.com/strongloop/loopback-connector-oracle 18 https://github.com/strongloop/loopback-connector-soap 20 https://github. com/strongloop/loopback-sdk-angular-cli 29 https://github.com/strongloop/loopback-sdk-android 27 https://github. 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.html 22 https://github.com/strongloop/generator-loopback . To make it easy for you to pick up the tutorial at any point. 21 http://loopback.com/strongloop/loopback-component-storage 24 https://github.com/strongloop/grunt-loopback-sdk-angular 30 https://github.com/strongloop/loopback-explorer 31 https://github. there are tags for each step of the tutorial.com/strongloop/loopback-sdk-angular 28 https://github.com/strongloop/loopback-component-passport 25 https://github.Loopback 55 Community Connectors The LoopBack community has created and supports a number of additional connectors. See Community connectors21 for details.com/strongloop/loopback-workspace 32 https://github.io/doc/en/lb2/Community-connectors.com/strongloop/loopback-sdk-ios 26 https://github.com/strongloop/loopback-component-push 23 https://github. Here we create a new directory as application root directory.Application settings. LoopBack project files and directories are in the application root directory. • common .io/doc/en/lb2/Command-line-tools. • datasources.Loopback 56 Installation You can install LoopBack via either API Connect or StrongLoop. Within this directory the standard LoopBack project structure has three sub-directories: • server . StrongLoop’s Node API platform consists of: • LoopBack.json . You can see a full description in Here36 ! Before running the application you need to many npm requirements.Middleware definition file with production configuration.Model configuration file. For more information.json . These requirements consists of: 33 http://loopback. Command-line tools Although in theory.json .json . • server. • middleware.html 34 http://expressjs.com/ 35 https://strong-pm.Middleware definition file. an open-source Node application framework based on Express34 . • StrongLoop Process Manager35 and related devops tools for Node applications.json . installing LoopBack tools makes it much easier to get started.Add scripts to perform initialization and setup.Data source configuration file.json . The /models sub-directory contains all model JSON and JavaScript files.Aurelia application. see Command-line tools33 .io/ 36 http://loopback.Main application program file. • client . you could code a LoopBack application from scratch.Files common to client and server. • middleware.js .io/doc/en/lb2/server-directory. They will scaffold an application that you can then customize to suit your needs.Node application scripts and configuration files. server directory is consists Node application files: • /boot directory .html . • model-config. • config. • component-config.Specifies LoopBack components to load.production. 054} 5. . Let’s creating a model and working with database and use API. "scripts": { "start": "node .1" }. "loopback-datasource-juggler": "^2. "serve-favicon": "^2. Browse to http://localhost:3000 to see the app.3".0".0".Loopback 57 ‘Listing-64:package. "loopback": "^2.0". "devDependencies": { "jshint": "^2. you have a new server. "loopback-boot": "^2. "dependencies": { "compression": "^1."uptime":42. "repository": { "type": "". 1. "main": "src/server/server. "cors": "^2. "version": "1. for example: {"started":"2016-09- 10T21:59:47. Browse to http://localhost:3000/explorer to see the StrongLoop API Explorer.6" }.5. Ensure that NodeJS is installed.0. "pretest": "jshint ." }.0".2". From the project folder.js".".0. "loopback-component-explorer": "^2. "url": "" } } Running The App To run the app. You’ll see the default application response that displays some JSON with some status information.155Z". 2.5.1. To run the app. follow these steps. This provides the platform on which the build tooling runs.5".json’ { "name": "practical-aurelia-server". execute the following command: npm install 3.0.22. execute the following command: npm start 4.6. Now.39. To do that we’ll need a customer. Let’s create a simple customer model.json’ 1 { 2 "name": "customer". ‘Listing-65:customer. . 5 "idInjection": true. and so on). Well. Every LoopBack application has a set of predefined built-in models such as User. Use model discovery to create static. you can add functionality such as validation rules and business logic to models. 6 "options": { 7 "validateUpsert": true 8 }. ‘Listing-65:customer. SOAP. • Use Datasource.json in common/models directory. 3 "plural": "customers". you can manually create a model. and by default has both Node and REST APIs. 9 "properties": { 10 "firstName": { 11 "type": "string". • For data sources backed by a relational database. Role.js’ 1 module. and represent back-end data sources such as databases or other back-end services (REST. a model typically corresponds to a table. and Application. This creates a Model definition JSON file that defines your model in LoopBack. 4 "base": "PersistedModel". Additionally.js and customer. you can define your own custom models specific to your application: • Use the model generator to create custom models from scratch. schema-driven models for database-backed data sources. To create a model you need only create two files. A LoopBack model represents data in backend systems such as databases.exports = function(Customer) { 2 3 }. Additionally. You can extend built-in models to suit your application’s needs. Loopback 58 Creating models Models are at the heart of LoopBack.buildModelFromInstance() to create dynamic schema-less models for data sources such as SOAP and REST services. strongloop. PersistedModel37 is the base object for all models connected to a persistent data source such as a database. Every model has properties.json and foo-bar. That means you’ll need to document your API. Through a set of simple steps using LoopBack. Actually. 22 "methods": [] 23 } Since you will eventually connect this model to a persistent data source in a database. LoopBack provides API Explorer for you. Each property can be optional or required. The model name (“FooBar”) will be preserved via the model’s name property. use PersistedModel as base. LoopBack applications come with a built-in API Explorer you can use to test REST API operations during development. 14 "lastName": { 15 "type": "string". 16 "required": true 17 } 18 }. Loopback 59 12 "required": true 13 }. In addition to the Customer model that you defined. You’ll see the StrongLoop API Explorer showing the two models this application has: Users and Customers. 19 "validations": [].com/loopback/#persistedmodel . Even you can define a default value for the property. Afte running the app go to http://localhost:3000/explorer. specified its properties and then exposed it through REST. For example. Important You must convert camel-case model names (for example MyModel) to lowercase dashed names (my-model). Pay attention you can define the property type. firstName and lastName for the Customer model. you’ve created a Customer model. Right now. You create a model named “FooBar” and creates files foo-bar. Fortunately. 20 "relations": {}. See LoopBack core concepts for an overview of the model inheritance hierarchy. 21 "acls": []. by default Loopback generates the User model and its endpoints for every application.js in common/models. you’re going to define two properties. LoopBack creates several other built-in models for common use cases: 37 http://apidocs. You’re not the only one who’ll use the API you just created. One of the powerful advantages of LoopBack is that it automatically generates a REST API for your model. only the User model is exposed over REST.” which is there by default. "customer": { "public": true. and delete (CRUD) operations. • Email model (see email connector38 ) . To “hide” the model’s REST API.io/doc/en/lb2/Email-connector. To do that.json. AccessToken. so they automatically have a full complement of create. and RoleMapping models for controlling access to applications. • Access control models . "dataSource": "db" }.send emails to your app users using SMTP or third-party services. first you must define config for model in server/model-config. then change the model’s public property to true. resources. 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. The built-in models (except for Email) extend PersistedModel. ‘Listing-66:model-config. Note By default. which will now look as shown below. To expose the other models..register and authenticate users of your app locally or against third-party services. change the model’s public property to true in server/model-config.. update.contains metadata for a client application that has its own identity and associated configuration with the LoopBack server. Role. You can see a property as datasource in model-config. To expose the models.ACL. 38 http://loopback.json’ . simply change public to false. and methods. Scope... adds the data source definition to the server/datasources. . Notice the in-memory data source named “db. • User model .json indicates that you use the db datasource.html . Now you’re going to define a data source.json.Loopback 60 • Application model .json file. Now you need to exposed your model (Customer) over REST. json matches it (see below). database. Edit /server/datasources. "connector": "mysql" } } Notice the “mysqlDs” data source you just added.com39 . and password properties. you need configure the data source to use the desired MySQL server.database property in datasources. 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.Loopback 61 ‘Listing-67:datasource. However. be aware that it is a shared resource. For this reason.json’ { "db": { "name": "db". "mysqlDs": { "name": "mysqlDs". Create a new database called “getting_started. port. 39 http://demo.json’ { "db": { "name": "db". Just make sure the mysqlDs.com/ .json and after the line "connector": "mysql" add host. please use it. we recommend you use your own MySQL server if you have one Next. you can use a different database name.strongloop. If not. username.strongloop. 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. "connector": "memory" } } You can add your datasource as below: ‘Listing-68:datasource. you can use the StrongLoop MySQL server running on demo.” If you wish. "connector": "memory" }. If you’ve been following along from the beginning (and didn’t just clone 40 http://loopback. For more information. "mysqlDs": { "name": "mysqlDs".js script to demon- strate auto-migration.Loopback 62 ‘Listing-69:datasource. "port": 3306.io/doc/en/lb2/Creating-a-database-schema-from-models . "customer": { "dataSource": "mysqlDs". you just need to connect them. see Creating a database schema from models40 .strongloop. Change the dataSource property from db to mysqlDs. "connector": "memory" }.json’ .json and look for the customer entry: ‘Listing-70:model-config.. 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. Edit /server/model-config.. This attaches the Customer model to the MySQL datasource you just created and configured. The loopback-getting-started module contains the create-sample-models. "username": "demo".. "database": "getting_started". "password": "L00pBack" } } Now you’ve created a MySQL data source and you have a Customer model..com". Now you have a Customer model in LoopBack. "connector": "mysql". "host": "demo.json’ { "db": { "name": "db". "public": true } . 19 }).io/doc/en/lb2/Defining-boot-scripts 43 http://apidocs. function(err. it won’t create duplicate tables.mysqlDs.automigrate('customer'.exports = function(app) { 2 app.log('Models created: \n'. 7 lastName: 'Abbasi' 8 }. In the console.strongloop. Loopback 63 this module). customer) { 15 if (err) throw err. 16 17 console. Note The auto-migration script below is an example of a boot script that LoopBack executes when an application initially starts up. 13 lastName: 'Gvh' 14 }. Note The boot script containing the auto-migration command will run each time you run your application. then you’ll need to copy it from below or from GitHub41 .CoffeeShop. Use boot scripts for initialization and to perform any other logic your application needs to perform when it starts. customer).js 42 http://loopback. 20 }. ]. ‘Listing-71:/server/boot/create-sample-models.models. you’ll see this: 41 https://github.dataSources.js’ 1 module. Put it in the application’s /server/boot directory so it will get executed when the application starts.create([{ 6 firstName: 'Behzad'.com/loopback-datasource-juggler/#datasource-prototype-automigrate . 18 }). { 9 firstName: 'Vahid'. Since automigrate()43 first drops tables before trying to create new ones. See Defining boot scripts42 for more information. { 12 firstName: 'Samin'. This will save some test data to the data source. 10 lastName: 'Saberi' 11 }. function(err) { 3 if (err) throw err. 4 5 app.com/strongloop/loopback-getting-started/blob/master/server/boot/create-sample-models. Now run the application. for example: 44 http://loopback. For information on configuring the connector in a LoopBack application.io44 . { firstName: ‘Samin’.io/doc/en/lb2/MongoDB-connector. MongoDB Connector The MongoDB connector enables LoopBack applications to connect to MongoDB data sources.0.html . id: 2 } ] You can also use the API Explorer. lastName: ‘Saberi’.0.loopbackrc file method. please refer to loopback. which is not suppored in general.loopbackrc file to the root directory of the project or the home folder. To customize the settings. Note Note: Tests and examples in this project configure the data source using the deprecated . lastName: ‘Gvh’.0.Loopback 64 … Browse your REST API at http://0. examples and tests from this module assume there is a MongoDB server instance running on localhost at port 27017. id: 3 }.0.loopbackrc file is in JSON format. id: 1 }. { firstName: ‘Vahid’. Let’s see how to install mongoDB as data source.0:3000/ Models created: [ { firstName: ‘Behzad’. The . you can drop in a . By default. lastName: ‘Abbasi’.0:3000/explorer Web server listening at: http://0. 0. "password": "yourpass".0. "password": "yourpass".loopbackrc’ { "dev": { "mongodb": { "host": "127.0. we will use a preconfigured StrongLoop MongoDB server. "connector": "mongodb". "host": "127. "port": 27017 } } . "username": "youruser".json to set the MongoDB configs: ‘Listing-73:datasource. "port": 27017 } }. "username": "youruser".0. Edit server/datasources. as usual before you must create a data source that uses the MongoDB connector. "test": { "mongodb": { "host": "127. For the purposes of this practice. "database": "practicalAureliaDB".0.0. npm install --save loopback-connector-mongodb Then.Loopback 65 ‘Listing-72:. "database": "test". "database": "test".1".1". "port": 27017 } } } Notice that you must install mongoDB connector.json’ { "db": { "name": "mongoDS".1". Using generated REST API in Aurelia Create the application Create main.js.js Warning The automigrate function creates a new collection if it doesn’t exist. To create the Customer collection and create two sample customers. we’ll have to configure an endpoint.plugin('aurelia-api'.js: ‘Listing-74:main. we can generate the corresponding MongoDB collection using the info from the Customer metadata in common/models/customer. Loopback 66 With the customer model configured.plugin('aurelia-animator-css') 7 . Start by creating a dir to store general-purpose scripts: mkdir bin Inside that dir. If you want to keep this data. 'http://localhost:3000/api/').developmentLogging() 6 .json via auto-migration. Second.js in the client for Aurelia configure and add plugins. create a script named automigrate. head on over to your favorite terminal and run jspm install aurelia-api from your project root. in order for us to actually make calls to loopback server. config => { 8 config.use 3 .registerEndpoint('api'. it will be destroyed and it’s data will be deleted.js’ 1 export function configure(aurelia) { 2 aurelia. run: node bin/automigrate.standardConfiguration() 4 // Uncomment this if you want some development logs 5 // . In this practice you can use aurelia-api plugin. This will install the module of which you’re reading the getting-started right now. If the collection already exists. . use autoupdate instead. Let’s configure a new endpoint by editing main. aurelia-api plugin First. start(). 2 import { inject } from 'aurelia-framework'.min. add and edit customers as addOrEdit.setRoot()). nav: true. moduleId: 'customers/addorEdi\ 11 t'. 'list'].js. create require files for customer list named list. 3 4 export class App { 5 configureRouter(config. name: 'addorEdit'. nav: true\ 9 .js’ 1 import 'bootstrap/css/bootstrap.css!'.html’ 1 <template> 2 <div class="container-fluid page-host"> 3 <router-view></router-view> 4 </div> 5 </template> . 11 } You should create customers directory in src. title: 'Customer Add/Edit' } 12 ]).title = 'Aurelia and Loopback'. 10 { route: 'addorEdit:/id'. router) { 6 config. 13 14 this. Loopback 67 9 }). 15 } 16 } ‘Listing-76:app.js. title: 'Customers List' }.router = router.map([ 8 { route: [''. 7 config.then(() => aurelia. name: 'list'. moduleId: 'customers/list'. 10 aurelia. Then define your routes as below: ‘Listing-75:app. 15 } 16 } ‘Listing-78:list.id}">Edit</a> </td> 18 </tr> 19 </tbody> 20 </table> 21 </template> .lastName}</td> 16 <td><a class="btn btn-success" route-href="route: addorEdit.for="item of customers"> 14 <td>${item.js’ 1 import { inject } from 'aurelia-framework'.apiEndpoint = apiEndpoint. 7 8 constructor(apiEndpoint) { 9 this. 3 4 @inject(Endpoint.apiEndpoint.then(customers => this.customers = customers). 10 } 11 12 activate() { 13 return this.find('customers') 14 . 2 import { Endpoint } from 'aurelia-api'. Loopback 68 ‘Listing-77:list. params. par\ 17 ams.bind: {id:item.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.firstName}</td> 15 <td>${item.html’ 1 <template> 2 <a class="btn btn-success" route-href="route: addorEdit.of('api')) 5 export class List { 6 customers = []. router = router.update('customers'.apiEndpoint.id = params.findOne('customers'.catch((err) => { 35 console.domain) 30 .id. 16 } 17 18 activate(params) { 19 if (params. Loopback 69 ‘Listing-79:addOrEdit.then((res) => { 31 this.then(customer => { 23 this.log('Successful!') 33 }) 34 . this. this.router. 32 console.id.js’ 1 import { inject } from 'aurelia-framework'. 15 this. 21 this. 12 13 constructor(apiEndpoint. 37 } 38 } .id) 22 . Router) 6 export class AddOrEdit { 7 domain = { 8 firstName: ''.navigate('list'). 4 5 @inject(Endpoint. this.apiEndpoint. 2 import { Endpoint } from 'aurelia-api'.of('api').id != '') { 20 this. 3 import { Router } from 'aurelia-router'.apiEndpoint = apiEndpoint. 25 } 26 } 27 28 submit() { 29 this.domain = customer 24 }). 9 lastName: '' 10 } 11 id = ''.log('An error occured!') 36 }). router) { 14 this. Loopback 70 ‘Listing-80:addorEdit.html’ 1 <template> 2 <div class="row"> 3 <form submit.org/ .bind="domain.lastName" /> 11 </div> 12 <input type="submit" value="Save" /> 13 </form> 14 </div> 15 </template> For more information about aurelia-api.bind="domain.spoonx. see aurelia-api-doc45 45 http://aurelia-api.trigger="submit()"> 4 <div class="form-group"> 5 <label>First Name</label> 6 <input type="text" value.firstName" /> 7 </div> 8 <div class="form-group"> 9 <label>Last Name</label> 10 <input type="text" value. rebranded it as PhoneGap.Plugins . Cordova . Contacts etc. The CLI uses it to download assets when they are referenced using a url to a git repo.formerly called as Phone Gap is a platform to build Native Mobile Applicatons using HTML5. In other words it acts a container for running a web application written in HTML.js. To install the cordova command-line tool. CSS and Java Script.Installing. you should be able to invoke git on your command line.Platform Assets . (Optional) Download and install a git client. The cordova module will automati- cally be downloaded by the npm utility.Working with Apache Cordova Cordova CLI lets you build Cordova applications for your smartphone with normal web technologies via the command line. GPS. 2. including how to apply software development best practices. Download and install Node. if you don’t already have one. CSS.js. • on OS X and Linux: . This section has the following chapters: . Upgrading and Basic Usage . follow these steps: 1. We want to use Aurelia on Cordova as you would in a regular browser.JS Typically Web applications cannot use the native device functionality like Camera. On installation you should be able to invoke node and npm on your command line. Adobe Systems purchased Nitobi in 2011. 3. Accelerometer . and later released an open source version of the software called Apache Cordova. . Install the cordova module using npm utility of Node. Installing Cordova The Cordova command-line tool is distributed as an npm package. Following installation. This book is a guide through all the pieces of the command line interface.What is Cordova? .Device Installation What is Cordova? Apache Cordova (formerly PhoneGap) is a popular mobile application development framework originally created by Nitobi. With Cordova we can very much achieve this and package the web application in the devices installer format. Create a project Go to the directory where you maintain your source code.com/posts/npm-link-developing-your-own-npm-modules-without-tears . you should be able to run cordova on the command line with no arguments and it should print help text. navigate to the project directory.hello HelloWorld This creates the required directory structure for your cordova app. There are more tips46 available on using npm without sudo. • on Windows: C:>npm install -g cordova The -g flag above tells npm to install cordova globally. and create a cordova project: $ cordova create hello com.example.html file. If you are using the optional nvm/nave tool or have write access to the install directory.Working with Apache Cordova 72 $ sudo npm install -g cordova On OS X and Linux. the cordova create script generates a skeletal web-based application whose home page is the project’s www/index. Add a platform After creating a Cordova project. you may be able to omit the sudo prefix. you need to add a platform for which you want to build your app. 46 http://justjs. From the project directory. Otherwise it will be installed in the node_- modules subdirectory of the current working directory. By default. if you desire to do that. prefixing the npm command with sudo may be necessary to install this development utility in otherwise restricted directories such as /usr/local/share. Following installation. apache. run cordova run <platform name>. A plugin exposes a Javascript API for native SDK functionality.org/plugins/ 48 https://cordova. Note When using the CLI to build your application. or when plugins are re-installed.Working with Apache Cordova 73 To add a platform. Some key APIs are provided by the Apache Cordova open source project and these are referred to as Core Plugin APIs48 .html#core-plugin-apis . Run your app From the command line.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. Add Plugins You can modify the default generated app to take advantage of standard web technologies. Add the platforms that you want to target your app. you should not edit any files in the /platforms/ directory. Plugins are typically hosted on npm and you can search for them on the plugin search page47 .apache.org/docs/en/latest/guide/support/index. you need to add plugins. We will add the ios and android platform and ensure they get saved to config. You can also use the CLI to launch the search page: 47 https://cordova. The files in this directory are routinely overwritten when preparing applications for building. type cordova platform add <platform name>. where each specified platform appears as a subdirectory. but for the app to access device-level features. 0 “Camera” cordova-plugin-whitelist 1.2. If you want to develop with lower-level shell tools or platform SDKs as discussed in the Overview49 .org/docs/en/latest/plugin_ref/plugman.) Use plugin ls (or plugin list. Note The CLI adds plugin code as appropriate for each platform.org/docs/en/latest/guide/overview/index.1.Working with Apache Cordova 74 $ cordova plugin search camera To add the camera plugin. 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. Each displays by its identifier: $ cordova plugin ls cordova-plugin-camera 2.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. see Using Plugman to Manage Plugins50 .apache. or plugin by itself) to view currently installed plugins.html 50 https://cordova.1 “Whitelist” Updating Cordova and Your Project After installing the cordova utility. we will specify the npm package name for the camera plugin: $ cordova plugin add cordova-plugin-camera Fetching plugin “cordova-plugin-camera@∼2. you need to run the Plugman utility to add plugins separately for each platform.html .1. (For more information.apache. pipe(gulp. compileOptions.js' })) 6 .0 Run cordova -v to see which version is currently running.0-0.js']) 3 .pipe(plumber({ errorHandler: notify. Working with Apache Cordova 75 $ sudo npm install -g cordova@3. 10 }).js file of jspm will read the config.init({ loadMaps: true })) 7 .dest('www/')).write({ includeContent: true })) 9 . { modules: 'system' }))) 8 .js file will be the aurelia framework with all dependencies. Our source folder src/js will be combined to app-build.2. Inside the aurelia.js.pipe(sourcemaps.pipe(changed('www/'. .1.onError("Error: <%= error.js’ 1 gulp.js'.task('build-scripts'. With the gulp build task we copy the source and it’s dependencies to the www folder. The system.pipe(sourcemaps. To do that you can use gulp tasks as below: ‘Listing-81:build.messa\ 4 ge %>") })) 5 . These step also transpiles the ECMAScript 6 code to ECMAScript 5. '!' + 'client/' + 'index. 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.pipe(babel(assign({}. The gulp task bundle will use the aurelia bundler to combine all javascripts and templates to two files. { extension: '. To find the latest released cordova version.src(['client/*.js and knows which files to use after bundling. function () { 2 return gulp. Using Aurelia The main application source is inside the folder src. 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. initial-scale=1.com/au-cordova51 organization. Now that you know what each of the folders in a Cordova app is for. This is the default page that Cordova serves. Working with Apache Cordova 76 Please see more samples on https://github. By default.com 'unsafe-eval'.html.com/au-cordova . 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. A single page application is nothing more than a web application that only uses one HTML page. it’s time to move and start building the app. minimum-scale=1.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. Templates are then used to inject different states of the application as needed. Open index. 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.html contains the following code: ‘Listing-81:index. ma\ 10 ximum-scale=1. The only thing that’s different are the meta tags. which is located in the www directory.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. style-src 'self' 'unsafe-in\ 6 line'. It’s a simple HTML file. 51 https://github. Let me walk you through some of them. index.gstatic. This means that the content is loaded at 100%. Let’s go through the attributes of this tag from left to right. <meta name="viewport" content="user-scalable=no. ‘Listing-82:index. <meta name="format-detection" content="telephone=no"> The msapplication-tap-highlight meta tag disables the grey tap highlight on Windows Phone 8 and later.gsta 'unsafe-eval'. the p element below it is shown instead. style-src 'self' 'unsafe-inline'. This stylesheet contains basic styling for the app. The width attribute specifies that the maximum device width is used for the content. The user can then click the link to make a call to that phone number. The p element with a class of listening is shown when the device APIs aren’t fully loaded yet. minimum- scale=1. It is hidden when the device APIs are ready. If you’re not planning to deploy on a Windows Phone device. This markup is specific to the default Cordova application so we can safely remove it later. The maximum-scale and minimum-scale are both set to 1. you can safely remove this tag. maximum-scale=1.css"> Inside the body tag lives the markup for showing the status of Cordova. At that point. media-src *"> The format-detection meta tag automatically converts phone numbers into links. initial-scale=1. Any script that an attacker manages to inject into the page would simply refuse to be loaded with this meta tag in place. <meta name="msapplication-tap-highlight" content="no"> The next tag is the viewport meta tag. Working with Apache Cordova 77 The first meta tag specifies the content security policy. because .js. You won’t find this file under the www directory. the initial-scale is set to 1. Next. This makes the Cordova application secure from cross-site scripting (XSS) attacks.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. This sets the minimum and maximum values that the user can set for the zoom level. <link rel="stylesheet" type="text/css" href="css/index. The first one includes cordova. width=device-width"> Next up is the default stylesheet. This file provides a unified API for accessing native device features. <meta http-equiv="Content-Security-Policy" content="default-src 'self' data: gap: https://ssl. The tag first specifies that the user shouldn’t be able to zoom the page in or out. It’s now time to get your hands dirty. you will notice that the receivedEvent function is responsible for hiding and showing the two p elements we encountered earlier. 12 13 console. 'display:block. 8 var receivedElement = parentElement.log('Received Event: ' + id).html with the following: . 11 receivedElement. 14 } You’ve waited long enough.js depending on the platform the app is running on.querySelector('. 9 10 listeningElement.'). This file is specific to the default application that’s created when you start a new Cordova project. It’s being triggered by the onDeviceReady event. <script type="text/javascript" src="js/index. is execute specific code when a specific event happens.').listening'). 3 }. ‘Listing-83:index. 4 5 receivedEvent: function(id) { 6 var parentElement = document.js’ 1 onDeviceReady: function() { 2 app. This means that there’s a different version of cordova.js"></script> If you open the file.setAttribute('style'.received'). After install Aurelia dependencies and replace index.js.setAttribute('style'. 7 var listeningElement = parentElement.js"></script> The second script tag includes index. 'display:none. <script type="text/javascript" src="cordova. All it does. Let’s first make sure that everything looks good by making use of Aurelia as a front end framework for building mobile web apps. Working with Apache Cordova 78 it is injected to the specific platform when you build your app.querySelector('.getElementById(id).receivedEvent('deviceready'). So the event identifier that’s passed to the receivedEvent function is deviceready. initial-scale=1. minimum-scale=1.mi\ 20 n. 27 </script> 28 </body> 29 </html> Now. ma\ 10 ximum-scale=1.1/js/browser/bluebird. you can use Aurelia application on Cordova.4. width=device-width"> 11 <link rel="stylesheet" href="jspm_packages/npm/
[email protected]’ 1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta http-equiv="Content-Security-Policy" content="default-src 'sel\ 5 f' data: gap: https://ssl. .js"></script> 21 <script type="text/javascript" src="jspm_packages/system.js"></script> 25 <script> 26 System. Taking photos with a Cordova application The main purpose of the project was taking photos with the device’s camera.import('aurelia-bootstrapper').gstatic.com 'unsafe-eval'. 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.js"></script> 24 <script type="text/javascript" src="cordova. style-src 'self' 'unsafe-in\ 6 line'.3/cs\ 12 s/font-awesome.js"></scrip\ 22 t> 23 <script type="text/javascript" src="config.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/
[email protected]. Working with Apache Cordova 79 ‘Listing-84:index. FILE_URI. It has a text field for entering the caption and a button for sharing.bind="imagSrc"> 9 <form submit. 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. 12 targetHeight: 350 13 }.showContainer = true. 14 navigator. ‘Listing-86:app. 17 }. 7 let camerOptions = { 8 quality: 90.DestinationType. 9 destinationType: Camera.js’ 1 export class App{ 2 showContainer = false.imagSrc = imageURI. 3 imagSrc = ''.\ 5 delegate="takePhoto()">Take Photo</button> 6 </div> 7 <div class="card" show.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. 5 takePhoto(){ 6 let that = this. 10 encodingType: Camera.camera.EncodingType. function(errorMessage){ 18 alert('The following error occured: ' + errorMessage) .bind="showContainer == true"> 8 <img id="photo" src. 11 targetWidth: 200. 16 that. 4 caption = ''.delegate="sharePhoto()"> 10 <input type="text" id="caption" value.getPicture(function(imageURI){ 15 that.JPEG. We have the code that launches the default camera app of the device. Working with Apache Cordova 81 19 }. the camera plugin and the social sharing plugin. The URL of the image is passed as an argument. which returns the path to the file on the local file system. In this section. we pass in the value entered in the caption text field and the file path to the photo. the user can select the application in which to share the photo. which is executed when the photo is successfully captured. The possible values are JPEG and PNG. Installing Plugins In the previous section. we specify the type of the resulting photo. we already used two plugins. The first one is the success callback. But you may have noticed that we haven’t installed any of them yet. If something goes wrong. we show an alert to notify the user about the error. It is then used as the value for the src attribute of the img element. It has two callback functions.share(this. the second callback function is executed. • encodingType: This option defines how the photo will be encoded.imagSrc.plugins. null. 20 21 } 22 23 sharePhoto(){ 24 window. • destinationType: With this option. • targetHeight: Use this option to specify the height of the photo. At that point. This uses the social sharing plugin53 .com/EddyVerbruggen/SocialSharing-PhoneGap-Plugin . The social sharing plugin then launches the default sharing window of the operating system. 26 } 27 } The camerOptions is for the camera plugin52 . In the code below. This can have a value of DATA_URL. the showContainer is shown to the user. 100 being the highest and 0 being the lowest.caption.socialsharing. Once a photo has been captured. • targetWidth: Use this option to specify the width of the photo. 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. or FILE_UR.\ 25 null). which returns a base64 encoded string. this. This contains the share button to which we add a click event. camerOptions). we’ll do just that.com/apache/cordova-plugin-camera 53 https://github. If there was an error. 52 https://github. This is what config. 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 . it should yield two separate apk files for Android. 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.xml. Configuring the App If you want to update the configuration of your app at some point. and Web Components in your apps. one for the armv7 architecture and one for x86.apache.Working with Apache Cordova 82 Installing plugins is simple if you already know the specific plugin that you want to install. The only known drawbacks are an increased memory footprint and the size of the installer file.org/ . then you can edit config.com/apache/cordova-plugin-camera 55 https://github. It might take a while depending on your internet connection. This is because it has to download all the necessary files from a remote server and then perform the compilation. The identifier of the camera plugin54 is cordova-plugin-camera. By using Crosswalk. Adding Crosswalk Crosswalk57 is a pluggable web view for Cordova apps based on Chromium and Blink. you replace the default web view that is used by Cordova. After the build process. You can install it with the following command: cordova plugin add cordova-plugin-crosswalk-webview The next time you build the app. This is usually the name given to the GitHub repository or the package name on the npm website. There’s also the Cordova plugins56 website that you can use to search for Cordova plugins. Google’s Play store allows developers to upload different versions of an apk file for the same app.org/plugins/ 57 https://crosswalk-project. WebAudio. Crosswalk is distributed as a plugin. it should install the web view for the platform you’re deploying to. But it only does this the first time you build the app after installing Crosswalk. then simply Google it. It also improves the performance of your app.xml looks like by default: 54 https://github. Luckily. 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. This allows you to use browser features such as WebRTC. All you have to do is determine the plugin identifier. which is located at the root of your project.com/EddyVerbruggen/SocialSharing-PhoneGap-Plugin 56 https://cordova. minor. Working with Apache Cordova 83 ‘Listing-87:config.apache.0.practicalAurelia"> The versioning uses the semantic versioning58 convention (major.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.0.org/ .0"> Update the name tag to update the name of your app: <name>Practise</name> Or the description: 58 http://semver.auCordova.w3.com"> 9 Behzad Abbasi 10 </author> 11 <content src="index.xml’ 1 <?xml version='1.com" href="http://gitlearn.patch): <widget version="1.0' encoding='utf-8'?> 2 <widget id="
[email protected]"> 4 <name>Practise</name> 5 <description> 6 An app for practise 7 </description> 8 <author email="behzad88.1" xmlns="http://ww\ 3 w.org/ns/1.practicalAurelia" version="0.auCordova.org/ns/widgets" xmlns:cdv="http://cordova. 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. firefoxos. execute the cordova platform list command. I’ll be walking you through the steps involved to prepare and deploy a Cordova app to Google’s Play store.0. <access origin="*" /> This isn’t recommended. ubuntu As you can see. because it can make requests to external domains that might contain malicious content. To list the platforms you can target with Cordova. geo. This gives developers fine control over which domains or intents (tel. For more information on app configuration. Deploying the App In this section. By default.0. <content src="index. sms) are allowed in the app.html" /> The configuration file also defines the domains the app is allowed to access. blackberry10. That’s why the whitelist plugin59 is installed by default. I already installed two platforms. browser and android. It makes the app less secure. This command lists the platforms that you’ve installed as well as the platforms that are available for publication: Installed platforms: android 6. Step 1: Adding Platforms The first thing you need to do is add the platform(s) you’re targeting. a Cordova app is configured to allow access to all (*) domains.xml file60 . You can do the same by executing the following commands: 59 https://github.html .apache. browser 4.0 Available platforms: amazon-fireos.1.org/docs/en/latest/config_ref/index. check out the documentation for the config.com/apache/cordova-plugin-whitelist 60 https://cordova. . This is done by executing the cordova serve command. Most of them are files used by Cordova during build time so you don’t really have to worry about those. two automation tools that help you copy files to the browser platform every time you make a change to the source code. you’ll see a lot of folders and files./www']. This means that any time you make a change to the files in the www directory. Also you can use gulp serve command for testing your app on browser. 8 port: 8000. Next. 14 } 15 } 16 }.task('serve'. ‘Listing-89:serve. If you open the platforms/android directory. 9 server: { 10 baseDir: ['. 11 middleware: function(req. done). 2 var browserSync = require('browser-sync'). for example. '*'). By default. 7 open: false. we have the Android platform. You can view it by opening a browser and browsing to http://localhost:8000/browser/www.js’ 1 var gulp = require('gulp'). you don’t really want to mess with the files in this directory unless it’s really necssary. This command creates a server that serves your app on a specific port. 13 next(). next) { 12 res. 3 4 gulp. res. You can use the emulator in your browser to display the app in a smaller screen. function(done) { 5 browserSync({ 6 online: false. because you may need to work with them later. The cordova serve command copies your source files to the platforms/browser directory. then I recommend you check out Grunt or Gulp.setHeader('Access-Control-Allow-Origin'. 17 }). If you want to learn more about this topic. it’s serving your app on port 8000. In Google Chrome. In fact. you have to restart the server for the changes to take effect. However. 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. you can click on the mobile icon in the upper left of the developer tools. you have to keep in mind what the following files and folders are for. simply ommit the platform parameter: cordova build Note that the above two commands don’t generate an . If you want to generate a release version of the app. this can be done using a keystore file. this is where the build files are stored. For Android. By default. we should be able to execute the keytool command from the command line.xml: This file is specific to the Android platform. If there’s an issue with a specific plugin. etc. Step 3: Signing the App We’re now ready to sign the app.apk that’s ready for submission to Google’s Play store. This includes images. welcome screens. The only difference between the two is the certificate used for signing the app. This means that. 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. • build: Once you build your app. this is where you would usually take a look. This proves that the app came from you. the cordova build command generates a debug version of your app. . This process will generate the package or installer for each of the platforms that you have added to your project. because it’s possible that the plugin hasn’t added the necessary permissions to this file. it’s time to build 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. sound files. • AndroidManifest. Since we already added the path to the SDK tools earlier. You can use this file for signing your app. What does signing the app actually do and why is it needed? To put it simply. On Android. the plugin adds an entry to this file for the specific permissions that it uses. Before we do.xml is copied over to this file. For Android.Working with Apache Cordova 86 • assets: This is where the files from the www directory are copied to. Step 2: Generating the App Build After adding the Android platform. • res: Any assets that your app uses are copied over to this directory. • src: This folder contains platform specific code from the plugins that you’ve installed.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. it will automatically sign the generated apk file for you. it will be an . you use the keytool which you can find in the sdk/tools directory of your Android SDK installation path. This version of the app is intended for testing by the developer. they are saved under the build/outputs/apk directory. Anything that you’ve included in config. When you install a plugin. you need to sign the app so that you can be identified as the author of the app. This is the app config- uration. To generate a keystore. when you run the cordova build android command. I’d like to make sure we’re on the same page. you should be able to upload the practise.apk practise. Navigate to the directory where you have the unsigned release version of the app: cd platforms/android/build/outputs/apk Next.bashrc file earlier.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.apk is the name of the output file.apk practise This command will ask for the password that you’ve entered when you generated the keystore. you can execute the command below. Zipalign is installed in the build-tools directory of your Android SDK install path. android-release-unsigned. 4 is an integer that defines the byte-alignment boundaries.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. This allows us to use it from any terminal window since we already added the tools directory to the . we use another tool called jarsigner to sign the app with the keystore that we’ve generated.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. 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. we need to make sure the app performs at its best. The options are the ones prefixed with a dash and the arguments are the ones without a dash. execute the following command: jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore practise. in this case we’re using SHA1withRSA • digestalg: the message digest algorithm to use • keystore: the path to the keystore file • android-release-unsigned. This should always be set to 4.apk After it finishes executing. .apk is the path to the apk file that we want to optimize.Working with Apache Cordova 87 keytool -genkey -v -keystore practise. • 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. Enter the password to start the signing process. practise. Once that’s done. The -v option provides us with verbose output. To do that. You can go ahead and copy it to the tools directory of your Android SDK install path. zipalign -v 4 android-release-unsigned.keystore android- release-unsigned. we use the Zipalign tool. html#requirements-and-support 62 https://cordova.android-23. Alternatively.apache. The files in this directory are routinely overwritten when preparing applications for building.android-22. if you are using browser for development you can use browser platform which does not require any platform SDKs.org/docs/en/latest/guide/platforms/ios/index. or when plugins are re-installed.Google Inc.practice. and for Windows platform please see this63 61 https://cordova. To build and run apps.:Google APIs (x86 System Image):19. Attention. you need to install SDKs for each platform you wish to target.:Google APIs:19. you should not edit any files in the /platforms/ directory.apache.html#requirements-and-support 63 https://cordova. 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. Also you can see iOS platform requrements here62 .android-21. Go to the directory project and create a new project: $ cordova create chapter4 com.myProject MyProject Next.org/docs/en/latest/guide/platforms/android/index.: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 .Google Inc.org/docs/en/latest/guide/platforms/win8/index. Android SDK: installed Android target: installed android-19.Google Inc.Working with Apache Cordova 88 Using Aurelia First.apache.html#requirements-and-support . To check if you satisfy requirements for building the platform: $ cordova requirements Requirements check results for android: Java JDK: installed . task('build-vvm'.pipe(sourcemaps. 6 var sourcemaps = require('gulp-sourcemaps').dest(paths. .jspm) 3 . In here.onError("Error: <%= error.jspmOut)) 4 . 2 var runSequence = require('run-sequence').init({ loadMaps: true })) 7 . Working with Apache Cordova 89 After create a new project you need to enhance your workfow. We use this task from src to www directory. To move browser packages (e.vvmOut. 4 var changed = require('gulp-changed')./babel-options').pipe(gulp.assign || require('object.pipe(changed(paths.g. 10 var assign = Object. Info You can see all tasks in exercises files Now.js' })) 6 .dest(paths. jspm packages). you can implement your Aurelia application. 7 var sass = require('gulp-sass').write({ includeContent: true })) 9 .src([paths. { modules: 'system' }))) 8 ./paths').pipe(sourcemaps. 5 var plumber = require('gulp-plumber').vvmOut)). 11 var notify = require('gulp-notify'). ‘Listing-91:MoveJspm_packages’ 1 gulp. 9 var compileOptions = require('.messa\ 4 ge %>") })) 5 . function () { 2 return gulp.js']) 3 .assign').pipe(babel(assign({}.task('move-jspm'. 3 var babel = require('gulp-babel'). 5 }).jspmOut)).src(paths. '!' + paths. function () { 2 return gulp.. ‘Listing-91:CompilenextgenerationJavaScript(es6)toes2015withbabel’ 1 gulp.vvm.pipe(changed(paths. compileOptions. { extension: '. We want to using Gulp.. 8 var paths = require('. so you can stop messing around and build Aurelia project.pipe(plumber({ errorHandler: notify. we have many important tasks such as: ‘Listing-90:Publicvariables’ 1 var gulp = require('gulp'). 10 }).pipe(gulp.root + 'app/index. addEventListener("backbutton".deviceready.navigateBack().addEventListener("backbutton". false). Working with Apache Cordova 90 Cordova plugin APIs Cordova ships with a minimal set of APIs.exitApp(). Has it ever been easier to configure routes than this? It would be straightforward to quickly flesh out the routes for a large application.html) event. back button Routing is essential in any single-page application and is a hard problem to solve well. 7 } Next. 8 } 9 document. After configure route. 3 function OnBackPress(e) { 4 if(routeConfig. To override the default back-button behavior. 10 } . We have important matter about to use back button. OnBackPress. something that has been painful with other approaches in my experience where a huge amount of boilerplate is required to hook everything together.navigateBack(). 6 else 7 navigator. OnBackPress. 5 } 6 document.router. ‘Listing-92:pressBackButton’ 1 activate() { 2 let that = this.router. It is no longer necessary to call any other method to override the back-button behavior. Aurelia have a history browser implementation to keep states.addEventListener once you receive the [deviceready](events. false). typically by calling document. 3 function OnBackPress(e) { 4 that. The navigateBack method of AppRouter will be useful when the user presses the back button. routeConfig) { 2 let that = this. ‘Listing-93:pressBackButton’ 1 activate(params.app.name !== 'home') 5 that. exit app when pressing the back button. and projects add what extra APIs they require through plugins. register an event listener for the backbutton event. Aurelia appears to have solved it very well. isPlugged.addEventListener("batterylow". 11 } 12 document.addEventListener("batterystatus".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.status = status. 3 function onBatteryStatus(status) { 4 that. 8 9 function onBatteryLow(status) { 10 that. false). onBatteryStatus. false).isPlugged = status. 5 that.batteryLow = true. 13 } . 6 } 7 document. It adds the following three events to the window object: • batterystatus • batterycritical • batterylow Applications may use window. Working with Apache Cordova 91 Battery Status This plugin provides an implementation of the Battery Status Events API. routeConfig) { 2 let that = this.level. onBatteryStatus. FILE_URI.DestinationType. 15 } 16 17 successCallback(imageData){ . Working with Apache Cordova 92 ‘Listing-95:batterystatusview’ 1 <template> 2 <h2>Level: ${status}</h2> 3 <h3>isPlugged: ${isPlugged}</h3> 4 5 <h1 if.camera. 2 3 export class Camera{ 4 takePhoto(){ 5 let options = { 6 quality: 75.camera. 8 sourceType: navigator. 12 13 navigator.errorCallback.CAMERA.successCallback. which provides an API for taking pictures and for choosing images from the system’s image library.bind="batteryLow === true">Battery Level Low ${status}%</h1> 6 </template> Camera This plugin defines a global navigator.camera.this. Although the object is attached to the global scoped navigator.PictureSourceType. 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'. 10 saveToPhotoAlbum: false 11 }. 7 destinationType: navigator.getPicture(this.\ 14 options).camera object. 9 allowEdit: true. apache. you can use WeakMap feauture to do that. The return value is sent to the successCallback function or errorCallback function. etc. • An String representing the image file location on local storage (default). The return value is sent to the successCallback function. so that when the 64 https://cordova. The manifest gives the browser metadata about your app. using option values. cache api. ‘Listing-97:cameraview’ 1 <template> 2 <h2>Level: ${status}</h2> 3 <h3>isPlugged: ${isPlugged}</h3> 4 5 <h1 if. getPicture function used to take and choose photo by sending the options. 20 } 21 22 errorCallback(err){ 23 // An error occured. The two key pieces that are needed for a web app to become a progressive web app are a manifest and a service worker.imgURI = "data:image/jpeg. in successCallback function. 19 that. depending on the specified cameraOptions: • An String containing the Base64-encoded photo image. indexedDB. Also. destinationType. Also. Working with Apache Cordova 93 18 let that = Container. Show a message to the user 24 } 25 } Creating a viewModel as Camera and add the function for take picture. we was spent describing how Aurelia’s DI container works with respect to one use case: instantiating a viewmodel. sourceType. in one of the following formats." + imageData.get(Camera).base64.instance.org/docs/en/latest/#plugin-apis . We can set quality. background sync and servre sent event(not really anything to do with PWA’s).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. ‘Listing-99:serviceworker’ 1 //OFFLINE 2 self. 8 'js/app. err). send push notifications.serviceWorker. you’d have to add this yourself.js'). 12 }). it knows what icon to use.js'.then(() => self.catch(function (err) { 10 // registration failed :( 11 console. 9 'styles/style. Service workers allow you to give your PWA functionality such as the ability to work offline.register('/sw. function (e) { 3 e. 9 }). background sync.waitUntil( 4 caches.html: ‘Listing-98:serviceworkerinstaller’ 1 <script> 2 if ('serviceWorker' in navigator) { 3 window. how the PWA should be displayed. 14 .skipWaiting()).scope). Normally. Working with Apache Cordova 94 user chooses to add your PWA to their home screen.log('ServiceWorker registration failed: '.addAll([ 7 'js'.addEventListener('load'. 11 }) 12 ).open('v1').log('ServiceWorker registration successful with sc\ 8 ope: '. 13 }).addEventListener('install'. registration. 13 }).then(function (cache) { 5 //Add all resources to cache 6 return cache.css' 10 ]). A service worker is a script that allows you to control how your PWA uses the network. the PWA’s name.js file in the client folder of your project. and more. function () { 4 navigator. you should create the sw. and more! We provide the service worker registration code in index. 14 } 15 </script> To enable the service worker.then(function (regist\ 5 ration) { 6 // Registration was successful 7 //console. 48 event. 17 //Remove old caches 18 var cacheWhitelist = ['v1']. event => { 31 //Proxy the request and respond from cache 32 event. 38 39 ///PUSH 40 self. 28 }).open("text"). function (event) { 41 const title = 'Push message'. event => { 16 event.delete(key).then(function (keyList) { 21 return Promise.waitUntil( 20 caches.waitUntil(self.addEventListener('push'.respondWith( 33 caches. 50 51 //Function for fetching data from indexedDB 52 function getOfflineData() { 53 return new Promise((res.png' ////image 47 }. //image 46 // badge: '/icon. 19 event. options)).indexOf(key) === -1) { 23 return caches.event. 56 . rej) => { 54 let request = self.showNotification(title.text() for \ 44 instance 45 // icon: '/icon. 26 }) 27 ).data.addEventListener('activate'. 49 }). Working with Apache Cordova 95 15 self.png'.addEventListener('fetch'.waitUntil(self.map(function (key) { 22 if (cacheWhitelist.all(keyList.data.indexedDB. 37 }).then(response => { 34 return response || fetch(event.registration. 55 db.request).clients. 42 const options = { 43 body: event.request).claim()). 35 }) 36 ).match(event.keys(). 24 } 25 })).text() //Your push message . 29 30 self. 73 objectStore = transaction.onsuccess = function (e) { 69 db = e. 65 request. 70 let result = [].stringify(d) .onupgradeneeded = function (event) { 58 db = event. 71 transaction = db. charset=UTF-8" 97 }. 62 autoIncrement: true 63 }). 77 if (c) { 78 result. 98 body: JSON. { 61 keyPath: "id".result. 67 }.push(c.transaction(["texts"].then((d) => { 93 fetch('/api/offline'. 59 // Create an objectStore for this database 60 let objectStore = db.value). 86 } 87 88 //Function to sync data between app and server 89 function doSync() { 90 return new Promise((res. 80 } else { 81 res(result). 74 objectStore.READ_\ 72 WRITE).createObjectStore("texts". IDBTransaction. 92 data.onsuccess = (event) =>\ 75 { 76 let c = event.openCursor(). event). 84 } 85 }). 95 headers: { 96 "Content-type": "application/json.target.index('content'). 68 request. Working with Apache Cordova 96 57 request.log('error:'.objectStore('texts'). { 94 method: 'post'. 79 c. 82 } 83 }.onerror = function (event) { 66 console. rej) => { 91 let data = getOfflineData()..target.continue().target. 64 }.result.result. 102 const options = { 103 body: "Data have been synced with the server".json(). 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).json.tag == 'syncoffline') { 120 event. Now.github. //You\ 104 r push message . //image 106 badge: '/icon.event. 65 https://w3c.text() for instance 105 icon: '/icon. options). for offline manifest you shoud create a file and named to manifest. 113 }) 114 }). direct what the user can launch and.then((data) => { 100 data.showNotification(title.registration. 109 }) 110 }). 112 }). 115 } 116 117 //Trigger background sync 118 self. Working with Apache Cordova 97 99 }). error). 108 self. more importantly.then((d) => { 101 const title = 'Background sync is done'.waitUntil(doSync()). the developer.data. function (event) { 119 if (event.catch((error) => { 111 console.io/manifest/ .log('Request failed'.png'. but right now we are just focusing on how your app can be launched.addEventListener('sync'. The Manifest for Web applications65 is a simple JSON file that gives you. In the future the manifest will give you even more control over your app. 121 } 122 }).png' ////image 107 }. how they can launch it. png". 12 "start_url": "/". Installation Make sure you have google-services. 3 "name": "MyProject". everything is magic.json"> That’s it. as follows: ‘Listing-101:index. 8 "type": "ico". 9 "sizes": "128x128" 10 } 11 ].com/docs/).json’ 1 { 2 "manifest_version": 2.plist for iOS in your Cordova project root folder. Android compilation details : To download a config file for an Android app: . 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. You don´t need to configure anything else in order to have push notification working for both platforms. 4 "short_name": "MyProject". $ 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.json for Android or GoogleService-Info.html’ 1 <link rel="manifest" href="/manifest. 13 "scope": "/". 5 "icons": [ 6 { 7 "src": "favicon. Working with Apache Cordova 98 ‘Listing-100:manifest. 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. . iOS compilation details : To download a config file for an iOS app: 1.apps. 4 "firebase_url": "https://practicalaurelia. Put the downloaded file GoogleService-Info. Click google-services.. 8 "client": [ 9 { 10 "client_info": { 11 "mobilesdk_app_id": "1:904541. 6 "storage_bucket": "practicalaurelia.json. select the package name of the app you need a config file for from the list. In the Your apps card. Sign in to Firebase and open your project. 2. You will need to ensure that you have installed the appropiate Android SDK libraries. 19 "client_type": 3 20 } 21 ]... Click GoogleService-Info. Put the downloaded file google-services.googleusercontent.. Sign in to Firebase and open your project.". 12 "android_client_info": { 13 "package_name": "com.com". 3. Click the Settings icon and select Project settings. 16 "oauth_client": [ 17 { 18 "client_id": "904541771161-. . 2.practicalaurelia" 14 } 15 }. 3.appspot. select the bundle ID of the app you need a config file for from the list.plist in the Cordova project root folder..plist. 4.com". Working with Apache Cordova 99 1.jsonsample’ 1 { 2 "project_info": { 3 "project_number": "904541.".json in the Cordova project root folder. Click the Settings icon and select Project settings. 5 "project_id": "practicalaurelia". ‘Listing-102:google-services. 4. In the Your apps card.firebaseio.com" 7 }...:android:43a4860c. 35 "ads_service": { 36 "status": 2 37 } 38 } 39 } 40 ].onTokenRefresh((token) => { 5 console.. 27 "services": { 28 "analytics_service": { 29 "status": 1 30 }. 33 "other_platform_oauth_client": [] 34 }. 4 FCMPlugin. (err) => { 7 // throw an error 8 }).. Working with Apache Cordova 100 22 "api_key": [ 23 { 24 "current_key": "AIzaSyBKZ3fjKHStaJTb1-. 6 }. 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." 25 } 26 ].log(token). 31 "appinvite_service": { 32 "status": 1. 9 } Get Token : . stringify(payload) ). 6 }. 9 } else { 10 //Notification was received in foreground. change to it and run npm init.log(token).onNotification(function (payload) { 5 if(payload.wasTapped) { 6 //Notification was received on device tray and tapped by the use\ 7 r. 4 FCMPlugin. To implementing push notification server you can use Node application by the ExpressJs. you can send data with push notification. 12 console. First create a directory named server.log(JSON. Then install express as a dependency. (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. you can use Firebase or implement an server. Now install Express in the server directory and save it in the dependencies list. 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. but you must have server to send notification.log(JSON. Maybe the user needs \ 11 to be notified.getToken(function(token){ 5 console. 8 console. 4 FCMPlugin. For example: .stringify(payload) ). 13 } 14 } 15 } Now. Thus. body property. omit the –save option: $ npm install express In the server directory.get('/'. 3 4 app. To parse incoming request bodies in a middleware before your handlers.js’ 1 import express from "express". Working with Apache Cordova 102 $ npm install express –save To install Express temporarily and not add it to the dependencies list.send('Hello World!') 6 }) 7 8 app. create a file named server. For every other path. The app responds with “Hello World!” for requests to the root URL (/) or route.js and add the following code: ‘Listing-106:server. load http://localhost:3000/ in a browser to see the output. You can run the app with the following command: $ node server. . res) { 5 res.js Then.listen(3000. function (req. available under the req. function () { 9 console. 2 var app = express().log('Example app listening on port 3000!') 10 }) The app starts a server and listens on port 3000 for connections. it will respond with a 404 Not Found. An API to get a push message . 2 var app = express().js’ 1 import EventEmitter from 'events'. returns middleware that only parses json. var queue = [].json()). 2 var em = new EventEmitter(). Let’s to implementing APIs: First. Working with Apache Cordova 103 ‘Listing-107:server. 3 app. Now. • Node js Even Emitter that allows one or more functions to be attached to named events emitted by the object.js’ 1 import bodyParser from 'body-parser'.use(bodyParser. Before initial APIs we need to: • A variables named queue for messages queue. $ npm install events –save ‘Listing-108:server. To use push messages you need to generate a set of vapid keys to use during subscription and sending pushmessages. function(req.json(req. Finally.body. 5 }).body). Next. 4 res.post('/api/push'. Working with Apache Cordova 104 ‘Listing-109:server. Don’t use the ones provided… they are just for testing… $ npm i web-push -g $ web-push generate-vapid-keys [–json] . you must also encrypt that data according to the Message Encryption for Web Push spec. res) { 2 let sub = req.send('OK'). 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.js’ 1 app.js’ 1 app.subscription. An API to sync data between app and server ‘Listing-110:server. res) { 2 res.post('/api/offline'. 3 }). function(req. 3 queue.push(sub). . Now.' //These are the keys you g\ 20 enerated by web-push 21 }.... 35 } To use APIs you can use Postman.. 29 payload.log(res).'.. 17 publicKey: 'BHVJ8n4KMCPy7YOTwNTwn-M3lSKOP. run the application and send your push messages. //The\ 18 se are the keys you generated by web-push 19 privateKey: 'FLuaKQ7. 27 webpush. . 30 options 31 ). 32 } 33 } 34 }... Fiddler or implement your application.catch(error).then(success). 60000)... 13 if(pushSubscription) { 14 const options = { 15 vapidDetails: { 16 subject: 'http://localhost:4000/'..log(err). 10 while(i--) { 11 const pushSubscription = queue.pop(). 8 setInterval(() => { 9 let i = queue. 23 headers: { 24 //'< header name >': '< header value >' 25 } 26 }. 22 TTL: 90000.. 7 }. Working with Apache Cordova 105 ‘Listing-111:server. //your subscription object 12 const payload = 'This is the push message you asked for :)'.js’ 1 function pushService() { 2 let success = (res) => { 3 console.sendNotification( 28 pushSubscription.length. 5 error = (err) => { 6 console.. 4 }. cloud . and team members’ tasks: Software management tools Examples Issue and Feature tracker GitHub. you need to consider how you’re going to get the ball rolling – and keep it rolling. BitBucket. from tooling choices during development all the way through to deployment and performance strategies. BitBucket. internal network storage. you’ll minimally need to select the following software management tools to manage code.Planning an Aurelia Application Planning an Aurelia project is something you may have already done. JIRA. assets. Microsoft TFS Version control system GitHub. 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. This post is a high level outline of things to consider when planning an Aurelia application. Microsoft TFS Document/asset storage Slack. There’s certainly a lot more to it than meets the initial eye: • Project management • Accessibility. 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. I try to use tools that used in my work experience. or will be soon attempting. Software management Examples Links tools Internationallisation / Translations for different http://aurelia. i18n (internationalization) and building for the correct environments are an essential part of any application. Development Process Methodology Typically there are a few different approaches to development. These details may differ per project.google.aurelia. it comes down to the goals of your project.google. . Kanban and likely many more adaptations.html#/doc/api/aurelia/i18n Globalisation languages / Culture differences Broswer Support Aurelia targets Evergreen http://blog. such as accessibility concerns and i18n for example. and frequently assess and improve your workflow. Scrum.com/web/progressive-web- apps/ Above are some examples for consideration when deciding baseline standards and types of support your application can offer. JIRA. HipChat.io/hub.org/WAI/intro/aria Offline-first https://developers. but how you’re going to build and support it. Trello. Addressing these considerations at the beginning of a project will enable you to clearly vision how you’ll implement the said features. Google Hangouts. such as Agile. It’s not just deciding what to build.Planning an Aurelia Application 107 Team Communication Slack. for things such as i18n and offline strategies. Issue Tracker Ensure that you and your team adopt the tools you choose.io/2015/01/26/introducing- Browsers only aurelia Accessibility WAI-ARIA https://www. IRC.w3. i18n and environments Accessibility. Accessibility. 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. Microsoft TFS Task Manager GitHub Org Tasks.com/web/fundamentals/getting- started/primers/service-workers Progressive Web App https://developers. Waterfall. however WebPack has seemingly become the standard across the JavaScript ecosystem. Also. amd. for example using npm to fetch your dependencies for development and also any dependencies that will make it into production. system. Package Managers Package managers allow you to grab dependencies from an external host. There are a vast amount of tooling options available with Aurelia. Managing third party code and their dependencies should not be a manual task performed by a human. . 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. For example. commonjs. that you can see these in Aurelia-Skeleton-Navigation repo. it’s important to remain consistent. SystemJs was introduced first. Let’s dive into some tooling a little further. compile all assets and then serve those assets in your browser with ease. such as HttpClient. Task Runners Task runners will allow you to configure particular tasks depending on what you’re aiming to achieve. es2015. Tooling and Development Tooling has been increasingly important when developing any kind of application for the web or other platforms. inject and other modules. Here are a few examples when considering a package manager. there are many modules to compiling your Javascript codes. you can use a particular command from a task runner to start a local server. An example of this would be using a development dependency such as TypeScript. The processes I’ve found to be ideal are the custom. native-modules are these modules. loosely enforced. agile-like processes that can be wrapped around the uniqueness of the project and team members.Planning an Aurelia Application 108 Whichever approach you take. it’s not productive. Three main types of tools (i. code style checker. formatting. and a code editor config file) aid this process and should be properly implemented and configured before coding begins.editorconfig . TSLint Code editor formatting/style . and style. Tools Examples Linters / Hinters Codelyzer.e. 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. TSLint. HTMLHint Code style checker ESLint.Planning an Aurelia Application 109 Linters and Enforcement When working on a team. ESLint. code linters/hinters. CSSLint. 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. 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. Below I detail the tools required (tasking tool facilitate all this) to do cross-browser unit and functional testing. It can be an integral part of the project’s development and continuous integration processes. The important thing is to pick a component library which is built on Aurelia. scaffold components and bundle your app for release via commands in your terminal. Textboxes.Planning an Aurelia Application 110 Aurelia CLI The Aurelia CLI will allow you to do most of the above. It’s likely the case that you’ll want to test each module or unit of code by writing unit tests. not wrapped with it. You can choose either commercial or open-source components. you’ll want to do functional end-to-end testing. there are a lot of great options. This chapter describes how to setup and run tests with karma . Using the CLI will allow you to create projects. Tools Purpose Karma The karma test runner is ideal for writing and running unit tests while developing the application. When all of the units of code unite to form a running application. labels and dropdown lists will only get you so far. all in a single environment. When it comes to UI components. For your application. reusing your existing codebase). Under the hood. It ships with an HTML test runner that executes tests in the browser Jest Delightful JavaScript Testing. We’re at the stage where. we’re writing code in a format that can run pretty much nearly anywhere. Complete and easy to set-up JavaScript testing solution. . 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. at a higher level. Browser Only If your application will only run in a browser. things like NativeScript exist to transform that AST into native code on mobile for impeccable performance and native experience. People can then (and already have for most cases) write programs that interpret these ASTs and spit them out in whatever language is needed. 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”. without necessarily knowing it. you need to consider the initial environments you’ll be deploying to. then your strategy will be fairly simple. In e2e testing. End-to-end tests explore the application as users experience it. An AST is a chain of commands that describe our code. 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. language parsers such as Babel or TypeScript convert our code into an AST (Abstract Syntax Tree). as well as any future considerations – such as NativeScript for native mobile applications (it’ll compile your Aurelia code for you. provides everything needed to write basic tests.Planning an Aurelia Application 111 Aurelia Testing Simplifies the testing of UI components by providing an elegant. This means that we’re not limited to the original language it was written in. Via an AST. 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. test it. as is deciding what architectural practices you’ll be using. build. 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. Backend API The first step assumes API-first development. This means you have a relatively stable API before you write any application code. and test your API first. Build your API first. Style guide Defining a style for your team and project is essential. 66 https://github. It is also assumed that the API developers will provide a development API to be used for development. 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. API-first development means that you document. In a nutshell.com/behzad888/Aurelia-styleguide . Note that during API construction. Document it. Using the same data API for both development and production should never be an option. 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. It’s worth noting that it may be assumed that security and authentication details will accompany the API. For this Aurelia Universal is Comming soon… or use ssr branch on aurelia- skeleton-navigation repository. which is an excellent method that I highly recom- mended.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. and then be ready to evolve it as you build out the applications that use it. offers common “gotchas” and recommendations for you. It offers a lot of help towards sensible app structure. 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. com/aurelia/benchmarks/blob/master/optimization-plan. such as our Dependency Injection library. Event Aggregator. However. Currently. Let’s investigate some approaches. These are components that are useful even in standard server-side code.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. etc. Aurelia doesn’t do server-sider rendering. Isomorphic Some of Aurelia is designed to run on the server. 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. 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.md . This is part of a plan we are working on for 2017 year.