Upload
duongkhanh
View
215
Download
2
Embed Size (px)
Citation preview
20TH CENTURY
Put the server in charge of everything.
<form action="post" url="path/to/endpoint.cgi"> <input type="submit" value="Click Me"> </form>
JQUERY
Enhanced static HTML returned from the server.
<form> <button id="submit">Click Me</button> </form>
$("button#submit").on("click", function (event) { $.post(/* Define an AJAX Request */); });
EXT.JS / SENCHA TOUCH
Made HTML a byproduct of JavaScript.
Ext.create('Ext.Button', { text: 'Click Me', renderTo: Ext.getBody(), listeners: { click: function () { /* Define a REST Request */ } } });
BACKBONE.JS
Offered rich data modeling, but poor presentation layer.
<form> <button id="submit">Click Me</button> </form>
var model = Backbone.Model.extend({ /* Define data model */ });
var MyView = Backbone.View.extend({ el: '#my-form', events: { 'click #submit' : 'submit' }, submit: function () { model.set( /* Define data to sync */ ) } });
ANGULAR.JS
Introduced dependency injection to the client.
<form ng-controller="myFormController as controller"> <button ng-click="submit">Click Me</button> </form>
angular.module('myModule', []) .service('mySubmitService', function ($http) { this.sendData = function (data) { $http.post(/* Define REST Request */); } }) .controller('myFormController', function (mySubmitService) { this.submit = function (data) { mySubmitService.submit(data); } });
LINE COMPARISONS: AN EXAMPLE WITH APP.JSAdapted from http://blog.durandal.io/2015/03/16/aurelia-and-angular-2-code-side-by-side/
LINE COMPARISONS: AN EXAMPLE WITH APP.JS
ANGULAR 2 AURELIA
Adapted from http://blog.durandal.io/2015/03/16/aurelia-and-angular-2-code-side-by-side/
import {Component, Template} from 'angular2/angular2';
@Component({selector: 'my-app'}) @Template({url: 'app.html'})
class MyApp { constructor() { this.firstName = 'John'; this.lastName = 'Doe'; this.updateFullname(); } updateFullname() { this.fullName = this.firstName + ' ' + this.lastName; } firstNameChanged($event, first) { this.firstName = first.value; this.updateFullname(); } lastNameChanged($event, last) { this.lastName = last.value; this.updateFullname(); } }
export class MyApp{ constructor(){ this.firstName = 'John'; this.lastName = 'Doe'; } get fullName(){ return this.firstName + ' ' + this.lastName; } }
LINE COMPARISONS: AN EXAMPLE WITH APP.JS
ANGULAR 2 AURELIA
No Dependency
Adapted from http://blog.durandal.io/2015/03/16/aurelia-and-angular-2-code-side-by-side/
import {Component, Template} from 'angular2/angular2';
@Component({selector: 'my-app'}) @Template({url: 'app.html'})
class MyApp { constructor() { this.firstName = 'John'; this.lastName = 'Doe'; this.updateFullname(); } updateFullname() { this.fullName = this.firstName + ' ' + this.lastName; } firstNameChanged($event, first) { this.firstName = first.value; this.updateFullname(); } lastNameChanged($event, last) { this.lastName = last.value; this.updateFullname(); } }
export class MyApp{ constructor(){ this.firstName = 'John'; this.lastName = 'Doe'; } get fullName(){ return this.firstName + ' ' + this.lastName; } }
LINE COMPARISONS: AN EXAMPLE WITH APP.JS
ANGULAR 2 AURELIA
Convention
No Dependency
Adapted from http://blog.durandal.io/2015/03/16/aurelia-and-angular-2-code-side-by-side/
import {Component, Template} from 'angular2/angular2';
@Component({selector: 'my-app'}) @Template({url: 'app.html'})
class MyApp { constructor() { this.firstName = 'John'; this.lastName = 'Doe'; this.updateFullname(); } updateFullname() { this.fullName = this.firstName + ' ' + this.lastName; } firstNameChanged($event, first) { this.firstName = first.value; this.updateFullname(); } lastNameChanged($event, last) { this.lastName = last.value; this.updateFullname(); } }
export class MyApp{ constructor(){ this.firstName = 'John'; this.lastName = 'Doe'; } get fullName(){ return this.firstName + ' ' + this.lastName; } }
LINE COMPARISONS: AN EXAMPLE WITH APP.JS
ANGULAR 2 AURELIA
Convention
No Dependency
Not needed; using getter
Adapted from http://blog.durandal.io/2015/03/16/aurelia-and-angular-2-code-side-by-side/
import {Component, Template} from 'angular2/angular2';
@Component({selector: 'my-app'}) @Template({url: 'app.html'})
class MyApp { constructor() { this.firstName = 'John'; this.lastName = 'Doe'; this.updateFullname(); } updateFullname() { this.fullName = this.firstName + ' ' + this.lastName; } firstNameChanged($event, first) { this.firstName = first.value; this.updateFullname(); } lastNameChanged($event, last) { this.lastName = last.value; this.updateFullname(); } }
export class MyApp{ constructor(){ this.firstName = 'John'; this.lastName = 'Doe'; } get fullName(){ return this.firstName + ' ' + this.lastName; } }
LINE COMPARISONS: AN EXAMPLE WITH APP.JS
ANGULAR 2 AURELIA
Convention
No Dependency
Not needed; using getter
Not needed; using getter
Adapted from http://blog.durandal.io/2015/03/16/aurelia-and-angular-2-code-side-by-side/
import {Component, Template} from 'angular2/angular2';
@Component({selector: 'my-app'}) @Template({url: 'app.html'})
class MyApp { constructor() { this.firstName = 'John'; this.lastName = 'Doe'; this.updateFullname(); } updateFullname() { this.fullName = this.firstName + ' ' + this.lastName; } firstNameChanged($event, first) { this.firstName = first.value; this.updateFullname(); } lastNameChanged($event, last) { this.lastName = last.value; this.updateFullname(); } }
export class MyApp{ constructor(){ this.firstName = 'John'; this.lastName = 'Doe'; } get fullName(){ return this.firstName + ' ' + this.lastName; } }
AURELIA ESSENTIALS
• Aurelia packages
• Setting up an Aurelia project
• Using dependency injection in Aurelia
• Creating templates
• Choosing a data binding mode
• Capturing events
• Creating custom attributes and custom elements
• Defining application states
• Test-driven development
AURELIA FRAMEWORK
TEMPLATING http://github.com/aurelia/templating
An extensible HTML templating engine.
TASK QUEUE http://github.com/aurelia/task-queue
A simple task queue for the browser.
PATH http://github.com/aurelia/path
Utilities for path manipulation.
METADATA http://github.com/aurelia/metadata
Utilities for reading and writing the metadata.
LOGGING http://github.com/aurelia/logging
A minimal but effective logging mechanism.
LOADER http://github.com/aurelia/loader
An interface for loading modules and view templates.
DEPENDENCY INJECTION http://github.com/aurelia/dependency-injection
An extensible dependency injection container.
BINDING http://github.com/aurelia/binding
A modern databinding library for JavaScript and HTML.
SOME RECOMMENDED OPTIONS
HTTP CLIENT http://github.com/aurelia/http-client
A simple, restful, message-based wrapper around XMLHttpRequest.
ROUTER http://github.com/aurelia/router
A powerful client-side router.
EVENT AGGREGATOR http://github.com/aurelia/event-aggregator
A lightweight pub/sub messaging system.
VIEW ALL PUBLIC REPOS (33) https://github.com/aurelia
INSTALL GULP AND JSPM npm install -g gulp jspm
CONFIGURE JSPM FOR GITHUB jspm registry config github
INSTALL YEOMAN AND THE AURELIA GENERATOR npm install -g yo generator-aurelia
RUN THE AURELIA GENERATOR yo aurelia
START THE SERVER gulp watch
THE ENTRY POINT TO AN AURELIA APPLICATION<!doctype html> <html> <head> <!-- Load assets not loaded through SystemJS --> </head>
</html>
<body aurelia-app="animation-main">
<div class="splash"></div>
<script src="jspm_packages/system.js"></script> <script src="config.js"></script>
<script> System.import('aurelia-bootstrapper'); </script>
</body>
THE ENTRY POINT TO AN AURELIA APPLICATION
<body aurelia-app="animation-main">
<div class="splash"></div>
<script src="jspm_packages/system.js"></script> <script src="config.js"></script>
<script> System.import('aurelia-bootstrapper'); </script>
</body>
THE ENTRY POINT TO AN AURELIA APPLICATION
<body aurelia-app="animation-main">
<div class="splash"></div>
<script src="jspm_packages/system.js"></script> <script src="config.js"></script>
<script> System.import('aurelia-bootstrapper'); </script>
</body>
THE ENTRY POINT TO AN AURELIA APPLICATION
<body aurelia-app="animation-main">
<div class="splash"></div>
<script src="jspm_packages/system.js"></script> <script src="config.js"></script>
<script> System.import('aurelia-bootstrapper'); </script>
</body>
THE ENTRY POINT TO AN AURELIA APPLICATION
<body
System
</body>
<script src="jspm_packages/system.js"></script> <script src="config.js"></script>
THE ENTRY POINT TO AN AURELIA APPLICATION
<body aurelia-app="animation-main">
<div class="splash"></div>
<script src="jspm_packages/system.js"></script> <script src="config.js"></script>
<script> System.import('aurelia-bootstrapper'); </script>
</body>
THE ENTRY POINT TO AN AURELIA APPLICATION
<body
System
</body>
<script> System.import('aurelia-bootstrapper'); </script>
THE ENTRY POINT TO AN AURELIA APPLICATION
<body aurelia-app="animation-main">
<div class="splash"></div>
<script src="jspm_packages/system.js"></script> <script src="config.js"></script>
<script> System.import('aurelia-bootstrapper'); </script>
</body>
DEPENDENCY INJECTION
import {inject} from 'aurelia-framework'; import {HttpClient} from 'aurelia-http-client';
@inject(HttpClient) export class MyView{ constructor(http){ this.http = http; }
instanceMethod() { /* Use this.http here */ } }
import {inject} from 'aurelia-framework';
@inject( )
DEPENDENCY INJECTION
importimport
@injectexport class constructor }
instanceMethod }}
import {inject} from 'aurelia-framework';
@inject( )
STEP 1 Import the dependency injection decorator.
DEPENDENCY INJECTION
import {inject} from 'aurelia-framework'; import {HttpClient} from 'aurelia-http-client';
@inject(HttpClient) export class MyView{ constructor(http){ this.http = http; }
instanceMethod() { /* Use this.http here */ } }
DEPENDENCY INJECTION
importimport
@injectexport class constructor }
instanceMethod }}
import {HttpClient} from 'aurelia-http-client';
@inject(HttpClient)
STEP 2 Import the dependency.
DEPENDENCY INJECTION
import {inject} from 'aurelia-framework'; import {HttpClient} from 'aurelia-http-client';
@inject(HttpClient) export class MyView{ constructor(http){ this.http = http; }
instanceMethod() { /* Use this.http here */ } }
DEPENDENCY INJECTION
importimport
@injectexport class constructor }
instanceMethod }}
@inject(HttpClient)
constructor(http){
}
STEP 3 Inject the dependency as an argument in the constructor.
DEPENDENCY INJECTION
import {inject} from 'aurelia-framework'; import {HttpClient} from 'aurelia-http-client';
@inject(HttpClient) export class MyView{ constructor(http){ this.http = http; }
instanceMethod() { /* Use this.http here */ } }
DEPENDENCY INJECTION
importimport
@injectexport class constructor }
instanceMethod }}
this.http = http;
/* Use this.http here */
STEP 4 Use the dependency in the instance methods of the class.
DEPENDENCY INJECTION
import {inject} from 'aurelia-framework'; import {HttpClient} from 'aurelia-http-client';
@inject(HttpClient) export class MyView{ constructor(http){ this.http = http; }
instanceMethod() { /* Use this.http here */ } }
TEMPLATES
<template>
<require from="./nav-bar"></require>
<nav-bar router.bind="router"></nav-bar>
</template>
TEMPLATES
<template>
</template>
<template>
</template>
STEP 1 Author the template inside a <template> tag.
TEMPLATES
<template>
<require from="./nav-bar"></require>
<nav-bar router.bind="router"></nav-bar>
</template>
TEMPLATES
<template>
</template>
<require from="./nav-bar"></require>
STEP 2 Require dependencies with a <require> tag.
TEMPLATES
<template>
<require from="./nav-bar"></require>
<nav-bar router.bind="router"></nav-bar>
</template>
TEMPLATES
<template>
</template>
<nav-bar router.bind="router"></nav-bar>
STEP 3 Use the custom behavior or custom element.
TEMPLATES
<template>
<require from="./nav-bar"></require>
<nav-bar router.bind="router"></nav-bar>
</template>
DATA BINDING: FLEXIBILITY AND SMART DEFAULTS
.two-way .one-way .one-time
Default for form elementsDefault for everything else
DATA BINDING: FLEXIBILITY AND SMART DEFAULTS
.two-way .one-way .one-time
Default for form elementsDefault for everything elseMust be explicitly specified
UI EVENTS WITH TRIGGER
.trigger="sayHello()"
export class MyViewModel{
sayHello(){ /* Do something... */ }
}
UI EVENTS WITH DELEGATION
<ul> <li repeat.for="item of items"> <button click.trigger="handleClicks($index)"> ${item.name} </button> </li> </ul>
UI EVENTS WITH DELEGATION
<ul click.delegate="handleClicks($event)"> <li repeat.for="item of items"> <button> ${item.name} </button> </li> </ul>
UI EVENTS WITH DELEGATION
.delegate="handleClicks($event)"
export class MyViewModel{
handleClicks(event){ /* Do something... */ }
}
APPLICATION EVENTS — PUB/SUB
@inject(EventAggregator) export class Example { constructor(eventAggregator){ this.eventAggregator = eventAggregator; }}
import {inject} from 'aurelia-framework'; import {EventAggregator} from 'aurelia-event-aggregator';
APPLICATION EVENTS — PUB/SUB
@inject(EventAggregator) export class Example { constructor(eventAggregator){ this.eventAggregator = eventAggregator; }
}
APPLICATION EVENTS — PUB/SUB
@inject(EventAggregator) export class Example { constructor(eventAggregator){ this.eventAggregator = eventAggregator; }
}
publish(){ var payload = {}; //any object this.eventAggregator.publish('channel', payload); }
APPLICATION EVENTS — PUB/SUB
@inject(EventAggregator) export class Example { constructor(eventAggregator){ this.eventAggregator = eventAggregator; }
}
publish(){ var payload = {}; //any object this.eventAggregator.publish('channel', payload); } subscribe(){ this.eventAggregator.subscribe('channel', (payload) => { /* Do something... */ }); }
APPLICATION EVENTS — PUB/SUB
publish(){ var payload = {}; //any object this.eventAggregator.publish('channel', payload); }
subscribe(){ this.eventAggregator.subscribe('channel', (payload) => { /* Do something... */ }); }
APPLICATION EVENTS — PUB/SUB
.publish('channel', payload);
var payload = new SpecialObject();
.subscribe(SpecialObject, (payload) => {});
CUSTOM ATTRIBUTES — DEFINITION
import {inject, customAttribute} from 'aurelia-framework';
@customAttribute('show') @inject(Element) export class Show { constructor(element) { this.element = element; }
valueChanged(newValue){ if (newValue) { this.element.classList.remove('aurelia-hide'); } else { this.element.classList.add('aurelia-hide'); } } }
CUSTOM ATTRIBUTES — DEFINITION
import
@customAttribute@injectexport class constructor }
valueChanged } }
STEP 1 Import the inject and customAttribute decorators.
import {inject, customAttribute} from 'aurelia-framework';
CUSTOM ATTRIBUTES — DEFINITION
import {inject, customAttribute} from 'aurelia-framework';
@customAttribute('show') @inject(Element) export class Show { constructor(element) { this.element = element; }
valueChanged(newValue){ if (newValue) { this.element.classList.remove('aurelia-hide'); } else { this.element.classList.add('aurelia-hide'); } } }
CUSTOM ATTRIBUTES — DEFINITION
import
@customAttribute@injectexport class constructor }
valueChanged } }
@customAttribute('show')
STEP 2 Name the custom attribute using the @customAttribute decorator.
CUSTOM ATTRIBUTES — DEFINITION
import {inject, customAttribute} from 'aurelia-framework';
@customAttribute('show') @inject(Element) export class Show { constructor(element) { this.element = element; }
valueChanged(newValue){ if (newValue) { this.element.classList.remove('aurelia-hide'); } else { this.element.classList.add('aurelia-hide'); } } }
CUSTOM ATTRIBUTES — DEFINITION
import
@customAttribute@injectexport class constructor }
valueChanged } }
@inject(Element)
STEP 3 Inject the element on which the attribute appears in the DOM.
CUSTOM ATTRIBUTES — DEFINITION
import {inject, customAttribute} from 'aurelia-framework';
@customAttribute('show') @inject(Element) export class Show { constructor(element) { this.element = element; }
valueChanged(newValue){ if (newValue) { this.element.classList.remove('aurelia-hide'); } else { this.element.classList.add('aurelia-hide'); } } }
CUSTOM ATTRIBUTES — DEFINITION
import
@customAttribute@injectexport class constructor }
valueChanged } }
export class Show { constructor(element) { this.element = element; }
}
STEP 4 Add a reference to the element onto the custom attribute object.
CUSTOM ATTRIBUTES — DEFINITION
import {inject, customAttribute} from 'aurelia-framework';
@customAttribute('show') @inject(Element) export class Show { constructor(element) { this.element = element; }
valueChanged(newValue){ if (newValue) { this.element.classList.remove('aurelia-hide'); } else { this.element.classList.add('aurelia-hide'); } } }
CUSTOM ATTRIBUTES — DEFINITION
import
@customAttribute@injectexport class constructor }
valueChanged } }
valueChanged(newValue){ if (newValue) { this.element.classList.remove('aurelia-hide'); } else { this.element.classList.add('aurelia-hide'); } }
STEP 5 Watch for changes in the bound data.
CUSTOM ATTRIBUTES — DEFINITION
import {inject, customAttribute} from 'aurelia-framework';
@customAttribute('show') @inject(Element) export class Show { constructor(element) { this.element = element; }
valueChanged(newValue){ if (newValue) { this.element.classList.remove('aurelia-hide'); } else { this.element.classList.add('aurelia-hide'); } } }
CUSTOM ELEMENTS — USAGE
<template>
<require from="./say-hello"></require>
<input type="text" ref=“name"> <say-hello to.bind=“name.value"></say-hello>
</template>
CUSTOM ELEMENTS — DEFINITION
import {customElement, bindable} from 'aurelia-framework';
@customElement('say-hello') export class SayHello { @bindable to;
speak(){ alert('Hello ${this.to}!'); } }
CUSTOM ELEMENTS — DEFINITION
import
@customElementexport class @bindable to
speak } }
STEP 1 Import the bindable and customElement decorators.
import {customElement, bindable} from 'aurelia-framework';
CUSTOM ELEMENTS — DEFINITION
import {customElement, bindable} from 'aurelia-framework';
@customElement('say-hello') export class SayHello { @bindable to;
speak(){ alert('Hello ${this.to}!'); } }
CUSTOM ELEMENTS — DEFINITION
import
@customElementexport class @bindable to
speak } }
STEP 2 Name the custom element using the @customElement decorator.
@customElement('say-hello')
CUSTOM ELEMENTS — DEFINITION
import {customElement, bindable} from 'aurelia-framework';
@customElement('say-hello') export class SayHello { @bindable to;
speak(){ alert('Hello ${this.to}!'); } }
CUSTOM ELEMENTS — DEFINITION
import
@customElementexport class @bindable to
speak } }
STEP 3 Declare bindable variables using the @bindable decorator.
@bindable to;
CUSTOM ELEMENTS — DEFINITION
import {customElement, bindable} from 'aurelia-framework';
@customElement('say-hello') export class SayHello { @bindable to;
speak(){ alert('Hello ${this.to}!'); } }
CUSTOM ELEMENTS — DEFINITION
import
@customElementexport class @bindable to
speak } }
STEP 4 Provide any methods that should be available in the element’s template.
speak(){ alert('Hello ${this.to}!'); }
CUSTOM ELEMENTS — DEFINITION
import {customElement, bindable} from 'aurelia-framework';
@customElement('say-hello') export class SayHello { @bindable to;
speak(){ alert('Hello ${this.to}!'); } }
CUSTOM ELEMENTS — TEMPLATE
<template> <button click.trigger="speak()">Say Hello To ${to}</button> </template>
APPLICATION STATES
export class MyView{
configureRouter(config, router) { config.map([ { route: ['url'], moduleId: 'path/to/module', nav: true, title:’Page Title’ } ]); }
}
TEST-DRIVEN DEVELOPMENT
• Easiest experience I have had writing tests.
• Simple because of ES6 modularity and dependency injection. Framework stays out of the way wherever possible.
• Process:
• Mock dependencies.
• Inject mocks into module under test.
• Test module behavior.
UNIT TESTING EXAMPLE — MOCK BACK END
class HttpStub { jsonp(url) { var response = this.itemStub; this.url = url; return new Promise((resolve) => { resolve({ content: { items: response } }); }) } }
UNIT TESTING EXAMPLE — TESTS
import {MyView} from '../../src/myview';
describe('the MyView module', () => {
it('sets response to items', (done) => { var http = new HttpStub(), sut = new MyView(http), itemStubs = [1], itemFake = [2];
http.itemStub = itemStubs; sut.activate().then(() => { expect(sut.items).toBe(itemStubs); expect(sut.items).not.toBe(itemFake); done(); }); });
});
http://linkedin.com/in/morrissinger
http://morrissinger.com
@morrissinger
!
"
GET IN TOUCH
Thanks for listening!