the art and science of shipping ember apps
DESCRIPTION
As an alternative to other popular client MVC solutions like Backbone.js and Angular.js, Ember.js differs in that it provides 'Rails-like' defaults by convention to common coding patterns, intelligent memory management, built-in integration testing, and numerous, next generation client side persistence solutions. Join O'Reilly author, Jesse Cravens, as he presents information from his new book: O'Reilly's 'Building Web Apps with Ember.js’ as he takes the audience through the construction of the RocknRollCall demo application. Construct a workflow using the latest in JavaScript build and package management solutions. Use Handlebars and Ember templates.TRANSCRIPT
8/6/14
THE ART AND SCIENCE OF SHIPPING SINGLE PAGE APPS WITH EMBER.JS
UX DEVELOPMENT at frog
@jdcravens github.com/jessecravens jessecravens.com principal web architect | frog Austin
ART & SCIENCE
THE ART & SCIENCE OF SHIPPING SINGLE PAGE APPS WITH EMBER.JS
2006
2007
!WHY? !!“YOU HAVE TO KNOW THE PAST TO UNDERSTAND THE PRESENT.”
!- DR. CARL SAGAN
!"STUDY THE PAST IF YOU WOULD DEFINE THE FUTURE...."
!- CONFUCIUS
!!
!!
!
TEMPLATES
TRADITIONAL
14
ROUTER
UI HTML5/CSS3/JavaScript
Web Framework HTML5/CSS3/JavaScript
Data Services JSON/XML
Storage DBs
App Server refresh
refresh
refresh
refresh
refresh
App Server
EARLY SPA IMPLEMENTATION
15
Container App HTML5/CSS3
Web Framework HTML5/CSS3/JavaScript
Data Services JSON/XML
Render Engine JavaScript DOM creation && String templates
Gadget App iFrame
initial load
XHR RESPONSE -JSON MARKUP FORMAT
XHR REQUEST -JSON ACTION OBJECT
Gadget App iFrame
Gadget App iFrame
REQ PARAMs Iframe Refresh
BACK IN THE DAY … A NAIVE IMPLEMENTATION !
!SEGREGATED DESIGN TEAM HANDED OFF PHOTOSHOP COMPS
!ARCHITECTED SERVER IMPLEMENTATION FIRST
!LACK OF CONVENTIONS AND DEVELOPER ERGONOMICS
!YUI MODULE PATTERN
PSEUDO CLASSICAL INHERITANCE !
JSON MARK UP LANGUAGE CUSTOM RENDER ENGINE
!HTML STRINGS AND VARIABLES
DOM CREATION !
CONTINUATION PASSING AND CALLBACK HELL !
IFRAMES AND GADGET SPEC !!!!
!!
!
App Server
INITIAL LOAD
19
Container App HTML5/CSS3
Web Framework HTML5/CSS3/JavaScript
Data Services JSON/XML
Render Engine JavaScript DOM creation && String templates
Gadget App 1 iFrame
initial load
Gadget App 2 iFrame
Gadget App 3 iFrame
REQ PARAMs Iframe Refresh
INITIAL LOAD
20
Container App HTML5/CSS3
Web Framework HTML5/CSS3/JavaScript
Render Engine JavaScript DOM creation && String templates
initial load
INITIAL LOAD
21
Container App HTML5/CSS3
Web Framework HTML5/CSS3/JavaScript
Render Engine JavaScript DOM creation && String templates
initial load
{ "root": “#portal", "childrenType": "Portal", "type": "ClientCommandObject", "children": [{ "childrenType": "TabContainer", "type": "Portal", "children": [{ "childrenType": "TabPage", "type": "TabContainer", "children": [{ "children": [{ "childrenType": "Gadget", "type": "Column", "children": [{ "gadgetType": "MyAccounts", "gadgetContentType": { "type": "url" } }, { "gadgetType": "EntOffersMlp", "gadgetContentType": { "type": "url" } }, { "gadgetType": "SpendingPlan", "gadgetContentType": { "type": "url" } }, { "gadgetType": "ImcoStorefront", "gadgetContentType": { "type": "url" } },
INITIAL LOAD
22
Container App HTML5/CSS3
Web Framework HTML5/CSS3/JavaScript
Render Engine JavaScript DOM creation && String templates
initial load
Main.objects.renderObject(rootEl, configHash);
INITIAL LOAD
23
Container App HTML5/CSS3
Web Framework HTML5/CSS3/JavaScript
Render Engine JavaScript DOM creation && String templates
initial load
Main.objects.renderObject = function(root, obj){ this.rootObj = root; // here you make the call to a function to build out your proper div, // this will also append it to the root ! var currentObject = this.constructLayoutObject(this.rootObj, obj); if(obj.children == null || obj.children.length == 0){ return; } else{ // render the branches of the object tree to the root by a recursive call var i; for(i=0; i < obj.children.length; i++){ this.renderObject(currentObject, obj.children[i]); } } };
INITIAL LOAD
24
Container App HTML5/CSS3
Web Framework HTML5/CSS3/JavaScript
Render Engine JavaScript DOM creation && String templates
initial load
Main.createNode: function(type, id, classNames) { var node = document.createElement(type); node.id = id; if (typeof classNames === 'string' ) { node.className = classNames; } else if (typeof classNames === 'object' ){ var str = classNames.toString(); var classString=str.replace(/,/g,' '); node.className = classString; } return node; }
INITIAL LOAD
25
Container App HTML5/CSS3
Web Framework HTML5/CSS3/JavaScript
Render Engine JavaScript DOM creation && String templates
initial load
!gadgets.Gadget.prototype.getContent = function(continuation) { gadgets.callAsyncAndJoin( [this.getTitleBarContent, this.getUserPrefsDialogContent, this.getMainContent], function(results) {continuation(results.join(''));}, this); }; !gadgets.Gadget.prototype.render = function(chrome) { if (chrome) { this.getContent(function(content) { chrome.innerHTML = content; }); } }; !
App Server
LOADED
26
Container App HTML5/CSS3
Web Framework HTML5/CSS3/JavaScript
Data Services JSON/XML
Render Engine JavaScript DOM creation && String templates
Gadget App 1 iFrame
Gadget App 2 iFrame
Gadget App 3 iFrame
REQ PARAMs Iframe Refresh
POST LOADED
27
Container App HTML5/CSS3
Web Framework HTML5/CSS3/JavaScript
Render Engine JavaScript DOM creation && String templates
DRAG N’ DROP EVENT FIRES!
Gadget App 1 iFrame
Gadget App 2 iFrame
Gadget App 3 iFrame
REQ PARAMs Iframe Refresh
POST LOADED
28
Container App HTML5/CSS3
Web Framework HTML5/CSS3/JavaScript
Render Engine JavaScript DOM creation && String templates
Gadget App 1 iFrame
Gadget App 2 iFrame
Gadget App 3 iFrame
REQ PARAMs Iframe Refresh
x
POST LOADED
29
Container App HTML5/CSS3
Web Framework HTML5/CSS3/JavaScript
Render Engine JavaScript DOM creation && String templates
Main.moveGadget = function(obj) { var serviceUrlConfigObject = obj; serviceUrlConfigObject.position = (obj.position !== undefined)?obj.position:0; serviceUrlConfigObject.action = "moveObject"; serviceUrlConfigObject.version = 1; Main.services.takeAction('handleMoveGadget' , serviceUrlConfigObject); };
REQ PARAMs Iframe Refresh
Gadget App 1 iFrame
Gadget App 2 iFrame
Gadget App 3 iFrame
x
POST LOADED
30
Container App HTML5/CSS3
Web Framework HTML5/CSS3/JavaScript
Render Engine JavaScript DOM creation && String templates
{ action:”moveGadget", version:1, category:category, order:order };
XHR REQUEST -JSON ACTION OBJECT
Gadget App 1 iFrame
Gadget App 2 iFrame
Gadget App 3 iFrame
x
REQ PARAMs Iframe Refresh
REFRESH
31
Container App HTML5/CSS3
Web Framework HTML5/CSS3/JavaScript
Render Engine JavaScript DOM creation && String templates
{ "root": “#portal", "childrenType": "Portal", "type": "ClientCommandObject", "children": [{ "childrenType": "TabContainer", "type": "Portal", "children": [{ "childrenType": "TabPage", "type": "TabContainer", "children": [{ "children": [{ "childrenType": "Gadget", "type": "Column", "children": [{ "gadgetType": "MyAccounts", "gadgetContentType": { "type": "url" } }, { "gadgetType": "EntOffersMlp", "gadgetContentType": { "type": "url" } }, { "gadgetType": "SpendingPlan", "gadgetContentType": { "type": "url" } }, { "gadgetType": "ImcoStorefront", "gadgetContentType": { "type": "url" } },
XHR RESPONSE -JSON MARKUP FORMAT
Gadget App 1 iFrame
Gadget App 2 iFrame
Gadget App 3 iFrame
x
REQ PARAMs Iframe Refresh
App Server
LOADED
32
Container App HTML5/CSS3
Web Framework HTML5/CSS3/JavaScript
Data Services JSON/XML
Render Engine JavaScript DOM creation && String templates
Gadget App 1 iFrame
Gadget App 2 iFrame
Gadget App 3 iFrame
REQ PARAMs Iframe Refresh
MoveGadget
ChangePage
OpenPanel
AddGadget
DeleteGadget
etc.
WE REALLY NEED RAILS IN THE BROWSER!
2014
EMBER
37
Views HTML5/CSS3
Web Framework HTML5/CSS3/JavaScript Ember.App
JavaScript
Ember Data JavaScript
ROUTER JavaScript
Local Storage JavaScript
initial load
XHR -JSON
WebSocket
TEMPLATES .hbs
App Server
Data Services JSON/XML
Models JavaScript
Controllers JavaScript
Backburner JavaScript
Components JavaScript
present
NxGEN: THE NEW S&P CAPITAL IQ
Working with the S&P labs team we sketched and visualized 5 key concepts. One of the key concepts was a way to analyze and view portfolios through different lenses.
BACK IN THE DAY … A NAIVE IMPLEMENTATION !
!SEGREGATED DESIGN TEAM HANDED OFF PHOTOSHOP COMPS
!ARCHITECTED SERVER IMPLEMENTATION FIRST
!LACK OF CONVENTIONS AND DEVELOPER ERGONOMICS
!YUI MODULE PATTERN
PSEUDO CLASSICAL INHERITANCE !
JSON MARK UP LANGUAGE CUSTOM RENDER ENGINE
!HTML STRINGS AND VARIABLES
DOM CREATION !
CONTINUATION PASSING AND CALLBACK HELL !
IFRAMES AND GADGET SPEC !!!!
!!
!
PRESENT AND FUTURE !
DESIGN WITH CODE !
WORK FRONT TO BACK !
THE EMBER WAY !
THE EMBER OBJECT MODEL !
EMBER RUN LOOP AND BACKBURNER !
JS TEMPLATES W/ HANDLEBARS / HTMLBARS !
PROMISES AND THE ASYNC ROUTER !
WEB COMPONENTS !!!!
!!
!
DESIGN WITH CODE
SKETCH ADOBE EDGE REFLOW WEBFLOW MACAW !
SKETCH ADOBE EDGE REFLOW WEBFLOW MACAW !
CHROME DEV TOOLS !
WORK FRONT TO BACK
BACK END TEAM
RAPID DEV
57
Early Design: HTML, Diagram Controllers
URL Driven: State Manager and Routes First
Populate the Controllers with Dummy Data
Models w/ Fixtures FixtureAdapter
FRONT END TEAM
Models w/ RESTAdapter
Build Server-Side Routes/Endpoints
Serialization and Formatting JSON
Remote Data Store and DB Sync
!!!
FixtureAdapter RESTAdapter
RAPID DEV
REST ADAPTER
TEMPLATES
FIXTURE ADAPTER
ROUTER
ROUTE HANDLERS
CONTROLLERS
VIEWS/COMPONENTS
MODELS
SERVICES (API DESIGN)
MVC, SPA (Bootstrap Object)
SERVICES (IMPLEMENTATION)
FRONT END DEVELOPMENT
WEB/SERVICES DEVELOPMENT
DEPLOY
CONCEPTUAL
EMBER DATA ADAPTERS !
ADAPTERS
60
App.ApplicationAdapter = DS.FixtureAdapter.extend({ namespace: 'rocknrollcall' }); !App.ApplicationAdapter = DS.LSAdapter.extend({ namespace: 'rocknrollcall' }); !App.ActivityAdapter = DS.LSAdapter.extend({ namespace: 'rocknrollcall' }); !App.ApplicationAdapter = DS.RESTAdapter.extend({ namespace: 'rocknrollcall' });
NxGEN: THE NEW S&P CAPITAL IQ
API STUBS !
EMBER APP KIT
63
{ "name": "app-kit", "namespace": "appkit", "APIMethod": "stub", …
ROUTES.JS
64
module.exports = function(server) { ! // Create an API namespace, so that the root does not // have to be repeated for each end point. server.namespace("/api", function() { ! // Return fixture data for "/api/activities" server.get("/activities", function(req, res) { var activities = [ ]; }; res.send(activities); }); }); };
EMBER APP KIT
65
{ "name": "app-kit", "namespace": "appkit", "APIMethod": "stub", … !{ "name": "app-kit", "namespace": "appkit", "APIMethod": “proxy”, "proxyURL": "http://whatever.api:3232", ...
EMBRACE THE EMBER WAY
!!FRIENDS OR FOES
ACTIVE GENERATION !
NAMING CONVENTIONS !
!
ACTIVE GENERATION
EMBER APPLICATION
70
App = Ember.Application.create({ ! ENV.LOG_MODULE_RESOLVER = true; ENV.APP.LOG_RESOLVER = true; ENV.APP.LOG_ACTIVE_GENERATION = true; ENV.APP.LOG_MODULE_RESOLVER = true; ENV.APP.LOG_TRANSITIONS = true; ENV.APP.LOG_TRANSITIONS_INTERNAL = true; ENV.APP.LOG_VIEW_LOOKUPS = true; !});
NAMING CONVENTIONS
CONVENTIONS
NAMING CONVENTIONS
CONVENTIONS
THE EMBER OBJECT MODEL
YUI2
76
YAHOO.namespace(‘App’); !App.BaseClass = function(){ method: function(){} }; !App.Class = function(){ App.BaseClass.call(this); }; !App.Class.inherits(App.BaseClass); !App.Class.prototype.method = function(){ !};
2007
var object = new Base; !object.extend({ value: "some data”, ! method: function() { alert("Hello World!"); } !}); !object.method(); !// ==> Hello World!
BASE2
77200
7
EMBER OBJECT
78
App.DefaultPlayer = Em.Object.extend({ ! init: function () { this.set('imgProfilePrefix', 'default_'); }, ! name: “Steve", ! imgName: function (imgType) { return this.get('imgProfilePrefix') + this.get('name').split(' ').join('_').toLowerCase() + this.get('imgProfileSuffix') + '.' + imgType; ! } !});
GETTERS AND SETTERS
79
App.DefaultPlayer = Em.Object.extend({ name: “Steve" }); !var steve = App.DefaultPlayer.create({}); !steve.set(‘name’ , ‘Stephen’); !steve.get(‘name’); // Stephen
INHERITANCE
80
App.DefaultPlayer = Em.Object.extend({}); !App.Player = App.DefaultPlayer.extend({ … });
SUPER()
81
App.DefaultPlayer = Em.Object.extend({ init: function () { this.set('imgProfilePrefix', 'default_'); this.set('imgProfileSuffix', '_profile'); } }); !App.Player = App.DefaultPlayer.extend({ init: function () { this._super(); this.set('imgProfilePrefix', 'player_'); } });
COMPUTED PROPERTIES
82
App.DefaultPlayer = Ember.Object.extend({ … name: "Steve", baseDir: "/images", imgName: function(){ return this.get('imgProfilePrefix') + this.get('name').split(' ').join('_').toLowerCase() + this.get('imgProfileSuffix') + '.png'; }, imgPath: function(){ return this.get('baseDir') + '/profile/' + this.imgName(); }.property('baseDir', 'imgName') });
OBSERVERS
83
App.DefaultPlayer = Ember.Object.extend({ … onlineStatusChanged: function(){ console.log('onlineStatusChanged to: ' + this.get('isOnline')); }.observes('isOnline').on('init') }); !var steve = App.DefaultPlayer.create({ 'isOnline': true }); // onlineStatusChanged to true !steve.set('isOnline', false); // onlineStatusChanged to false
BINDINGS
84
App.DefaultPlayer = Ember.Object.extend({ … name: "Steve", baseDir: "/images", imgName: function(){ return this.get('imgProfilePrefix') + this.get('name').split(' ').join('_').toLowerCase() + this.get('imgProfileSuffix') + '.png'; }, imgPath: function(){ return this.get('baseDir') + '/profile/' + this.imgName(); }.property('baseDir', 'imgName') });
EMBER RUN LOOP || BACKBURNER.JS
POST LOADED
86
Container App HTML5/CSS3
Web Framework HTML5/CSS3/JavaScript
Render Engine JavaScript DOM creation && String templates
Gadget App 1 iFrame
Gadget App 2 iFrame
Gadget App 3 iFrame
REQ PARAMs Iframe Refresh
2007
POST LOADED
87
Container App HTML5/CSS3
Web Framework HTML5/CSS3/JavaScript
Render Engine JavaScript DOM creation && String templates
{ action:”moveGadget", version:1, category:category, order:order };
XHR REQUEST -JSON ACTION OBJECT
Gadget App 1 iFrame
Gadget App 2 iFrame
Gadget App 3 iFrame
x
REQ PARAMs Iframe Refresh
DRAG N’ DROP EVENT FIRES!
2007
POST LOADED
88
Container App HTML5/CSS3
Web Framework HTML5/CSS3/JavaScript
Render Engine JavaScript DOM creation && String templates
{ action:”moveGadget", version:1, category:category, order:order };
XHR REQUEST -JSON ACTION OBJECT
Gadget App 1 iFrame
Gadget App 2 iFrame
Gadget App 3 iFrame
x
REQ PARAMs Iframe Refresh
x{ action:”moveGadget", version:1, category:category, order:order };
XHR REQUEST -JSON ACTION OBJECT
2007
RUN LOOP
89
!BBone.DisplayView = Backbone.View.extend({ ! initialize: function () { this.listenTo(this.model, 'change', this.render); }, render: function() { console.log(‘render’); } }); !// render model.set('firstName', 'Erik'); // render again model.set('lastName', 'Bryn'); !!
ACTIONS ARE DEFERRED !
RUN LOOP
91
!BBone.DisplayView = Backbone.View.extend({ initialize: function () { this.listenTo(this.model, 'change', this.render); }, render: function() { backburner.deferOnce('render', this, this.actuallyRender); }, actuallyRender: function() { // do our DOM manipulations here. will only be called once. } }); backburner.run(function() { model.set('firstName', 'Erik'); model.set('lastName', 'Bryn'); }); !
!
EMBER.RUN.QUEUES !
FLUSHING ROUTER TRANSITIONS !
["SYNC", “ACTIONS", "ROUTERTRANSITIONS", "RENDER", "AFTERRENDER", "DESTROY"]
!
EMBER.RUN.QUEUES !
["SYNC", "ACTIONS", "ROUTERTRANSITIONS", "RENDER", "AFTERRENDER", "DESTROY"]
AINT’ THAT FANCY!
JS TEMPLATES
DOM
96
Container App HTML5/CSS3
Web Framework HTML5/CSS3/JavaScript
Render Engine JavaScript DOM creation && String templates
JSON MARKUP LANGUAGE
Main.createNode: function(type, id, classNames) { var node = document.createElement(type); node.id = id; if (typeof classNames === 'string' ) { node.className = classNames; } else if (typeof classNames === 'object' ){ var str = classNames.toString(); var classString=str.replace(/,/g,' '); node.className = classString; } return node; }
2007
DOM VS INNERHTML
97
<script type="text/x-handlebars" data-template-name=“application"> ! <!-- template code here --> !</script>
2007
DOM VS INNERHTML
98200
8
INNERHTML
99
Container App HTML5/CSS3
Web Framework HTML5/CSS3/JavaScript
Render Engine JavaScript DOM creation && String templates
gadgets.Gadget.prototype.render = function(chrome) { if (chrome) { this.getContent(function(content) { chrome.innerHTML = content; }); } }; !
2007
JSON MARKUP LANGUAGE
JS TEMPLATES WITH HANDLEBARS !
Ember.TEMPLATES
HANDLEBARS
101
<div {{bind-attr class=“myClass"}}> {{myValue}} </div>
Compiled JS Functions
!Em.TEMPLATES
Handlebars Compiler
Emits String
! //This is how handlebars works var output = ""; output.push("<div class=\""); output.push("<script type='text/x-‐placeholder' id='start-‐1'></script>"); // insert the value of myClass output.push("<script type='text/x-‐placeholder' id='end-‐1'></script>"); output.push("\">"); output.push("<script type='text/x-‐placeholder' id='start-‐2'></script>"); // insert the value of myValue output.push("<script type='text/x-‐placeholder' id='end-‐2'></script>"); output.push("</div>");
Output string
innerHTML
HANDLEBARS
102
<script type="text/x-handlebars" data-template-name=“application"> <!-- template code here --> </script>
grunt.initConfig({ yeoman: yeomanConfig, watch: { emberTemplates: { files: '<%= yeoman.app %>/templates/**/*.hbs', tasks: ['emberTemplates', 'livereload'] } } });
VARIABLES
103
{{App.applicationName}} <ul class="navbar artists”> {{#each item in navbar-items}} {{#if item.isAccessible}} {{#each label in items.labels}} <li> {{#linkTo ‘label’ label.id}}{{label.title}}{{/linkTo}} </li> {{/each}} {{else}} <li> <a {{bind-attr href=‘item.fullAddress’}}> {{item.name}}</a> </li> {{/if}} {{/each}} </ul>
MINIMAL LOGIC
104
{{App.applicationName}} <ul class="navbar artists”> {{#each item in navbar-items}} {{#if item.isAccessible}} {{#each label in items.labels}} <li> {{#linkTo ‘label’ label.id}}{{label.title}}{{/linkTo}} </li> {{/each}} {{else}} <li> <a {{bind-attr href=‘item.fullAddress’}}> {{item.name}}</a> </li> {{/if}} {{/each}} </ul>
LINKS
105
{{App.applicationName}} <ul class="navbar artists”> {{#each item in navbar-items}} {{#if item.isAccessible}} {{#each label in items.labels}} <li> {{#linkTo ‘label’ label.id}}{{label.title}}{{/linkTo}} </li> {{/each}} {{else}} <li> <a {{bind-attr href=‘item.fullAddress’}}> {{item.name}}</a> </li> {{/if}} {{/each}} </ul>
LISTS
106
{{App.applicationName}} <ul class="navbar artists”> {{#each item in navbar-items}} {{#if item.isAccessible}} {{#each label in item.labels}} <li> {{#linkTo ‘label’ label.id}}{{label.title}}{{/linkTo}} </li> {{/each}} {{else}} <li> <a {{bind-attr href=‘item.fullAddress’}}> {{item.name}}</a> </li> {{/if}} {{/each}} </ul>
BOUND ATTRIBUTES
107
{{App.applicationName}} <ul class="navbar artists”> {{#each item in navbar-items}} {{#if item.isAccessible}} {{#each label in items.labels}} <li> {{#linkTo ‘label’ label.id}}{{label.title}}{{/linkTo}} </li> {{/each}} {{else}} <li> <a {{bind-attr href=‘item.fullAddress’}}> {{item.name}}</a> </li> {{/if}} {{/each}} </ul>
!
HTMLBARS
!!HTMLBARS OVER HANDLEBARS
PERFORMANCE !
BIND-ATTR GONE !
METAMORPH GONE !
LOGIC IN TEMPLATES !
!
HTMLBARS
110
<div class=“{{myClass}}”> {{myValue}} </div>
Compiled JS Functions
!Em.TEMPLATES
HTMLBars Compiler Emits DOM elements
var output = dom.createDocumentFragment(); var div = dom.createElement('div'); dom.RESOLVE_ATTR(context, div, 'class', 'myClass'); var text = dom.createTextNode(); dom.RESOLVE(context, text, 'textContent', 'myValue'); div.appendChild(text); output.appendChild(div);
<div class="{{myClass}}">{{myValue}}</div>
BOUND ATTRIBUTES
111
{{App.applicationName}} <ul class="navbar artists”> {{#each item in navbar-items}} {{#if item.isAccessible}} {{#each label in items.labels}} <li> {{#linkTo ‘label’ label.id}}{{label.title}}{{/linkTo}} </li> {{/each}} {{else}} <li> <a {{bind-attr href=‘item.fullAddress’}}> {{item.name}}</a> </li> {{/if}} {{/each}} </ul>
NO MORE BIND-ATTR
112
{{App.applicationName}} <ul class="navbar artists”> {{#each item in navbar-items}} {{#if item.isAccessible}} {{#each label in items.labels}} <li> {{#linkTo ‘label’ label.id}}{{label.title}}{{/linkTo}} </li> {{/each}} {{else}} <li> <a href=“{{fullAddress}}”> {{item.name}}</a> </li> {{/if}} {{/each}} </ul>
LOGIC-LESS
113
{{App.applicationName}} <ul class="navbar artists”> {{#each item in navbar-items}} {{#if item.isAccessible}} {{#each label in items.labels}} <li> {{#linkTo ‘label’ label.id}}{{label.title}}{{/linkTo}} </li> {{/each}} {{else}} <li> <a href=“{{fullAddress}}”> {{item.name}}</a> </li> {{/if}} {{/each}} </ul>
LOGIC
114
{{App.applicationName}} <ul class="navbar artists”> {{#each item in navbar-items}} {{#if (item.type === ‘sidenavbar-item’) }} {{#each label in items.labels}} <li> {{#linkTo ‘label’ label.id}}{{label.title}}{{/linkTo}} </li> {{/each}} {{else}} <li> <a href=“{{fullAddress}}”> {{item.name}}</a> </li> {{/if}} {{/each}} </ul>
HTMLBARS
115
<script type="text/x-handlebars" data-template-name=“application"> ! <!-- template code here --> !</script>
METAMORPHS
116
PROMISES AND THE ASYNC ROUTER
!!RSVP
PROMISES/A+ !
ES6 COMPLIANT !
CONVENIENCE METHODS !!!!!
!
RSVP
120
var p = new RSVP.Promise(function(resolve, reject) { // succeed resolve(value); // or reject reject(error); }); !p.then(function(value) { // success }, function(value) { // failure });
CONTINUATION
121
Container App HTML5/CSS3
Web Framework HTML5/CSS3/JavaScript
Render Engine JavaScript DOM creation && String templates
!gadgets.Gadget.prototype.getContent = function(continuation) { gadgets.callAsyncAndJoin( [this.getTitleBarContent, this.getUserPrefsDialogContent, this.getMainContent], function(results) {continuation(results.join(''));}, this); }; !gadgets.Gadget.prototype.render = function(chrome) { if (chrome) { this.getContent(function(content) { chrome.innerHTML = content; }); } }; ! 200
7
CONTINUATION
122
!gadgets.Gadget.prototype.getContent = function(continuation) { gadgets.callAsyncAndJoin( [this.getTitleBarContent, this.getUserPrefsDialogContent, this.getMainContent], function(results) {continuation(results.join(''));}, this); }; !gadgets.Gadget.prototype.render = function(chrome) { if (chrome) { this.getContent(function(content) { chrome.innerHTML = content; }); } }; !
2007
XHR RESPONSE
123
Container App HTML5/CSS3
Web Framework HTML5/CSS3/JavaScript
Render Engine JavaScript DOM creation && String templates
{ "root": “#portal", "childrenType": "Portal", "type": "ClientCommandObject", "children": [{ "childrenType": "TabContainer", "type": "Portal", "children": [{ "childrenType": "TabPage", "type": "TabContainer", "children": [{ "children": [{ "childrenType": "Gadget", "type": "Column", "children": [{ "gadgetType": "MyAccounts", "gadgetContentType": { "type": "url" } }, { "gadgetType": "EntOffersMlp", "gadgetContentType": { "type": "url" } }, { "gadgetType": "SpendingPlan", "gadgetContentType": { "type": "url" } }, { "gadgetType": "ImcoStorefront", "gadgetContentType": { "type": "url" } },
XHR RESPONSE -JSON MARKUP FORMAT
Gadget App 1 iFrame
Gadget App 2 iFrame
Gadget App 3 iFrame
x
REQ PARAMs Iframe Refresh
2007
SUCCESS, FAILURE
124
Main.YUIConnectionManager.callback = { success: function(o) { try { var data = YAHOO.lang.JSON.parse(o.responseText); } catch (e) { Main.debug(err + " - Invalid data”); } }, failure: function(o) { } };
2007
ROUTE HANDLERS
125
App.ArtistRoute = Ember.Route.extend({ model: function(params) { ! XHR( "some URL” , {"id":params.enid}, function callback(response){ // handle response }); ! } });
PROMISES
126
RSVP.all([ afunction(), another(), yetAnother()]) ! .then(function() { ! console.log("They're all finished, success is ours!”); ! }, function() { ! console.error("One or more FAILED!”); });
PROMISES
127
var promises = { posts: getJSON("/posts.json"), users: getJSON("/users.json") }; !RSVP.hash(promises).then(function(results) { console.log(results.users) // print the users.json results console.log(results.posts) // print the posts.json results });
PRESENT AND FUTURE !
DESIGN WITH CODE !
WORK FRONT TO BACK !
THE EMBER WAY !
THE EMBER OBJECT MODEL !
EMBER RUN LOOP AND BACKBURNER !
JS TEMPLATES W/ HANDLEBARS / HTMLBARS !
PROMISES AND THE ASYNC ROUTER !
WEB COMPONENTS !!!!
!!
!
WEB COMPONENTS
IFRAMES !
TRADITIONAL WEB DEVELOPERS CAN ADD CONTENT !
SANDBOXED CONTENT / CAN LOAD FROM PROXIES !
POST MESSAGE API HAS EVOLVED / CONTAINER CAN CREAT AN INTERFACE !
DONT NEED TO LEARN CONTAINER IMPLEMENTATION !!!!!!!!!!
!!
!
THAT’S NASTY
OH WAIT, ONE MORE THING. !
- ERIK BRYN, EMBER CONF 2014
THE FUTURE IS NOW