IOC + Javascript Brian Cavalier @briancavalier / John Hann @unscriptable
Who? Two back-end engineers who defected to the front-end The main characters behind cujo.js
What? cujo.js Spring-like concepts, but not a port of Spring to Javascript Embraces Javascript's functional and prototypal roots Provides architectural tools for next-generation JavaScript applications http://cujojs.com/
How? Less slides, please! Moar awesum codez, plz! Q&A at the end and during code demos! Code demos: Monty Hall UI && TodoMVC
Recent project stats 6 "HTML Pages" 300+ Javascript modules 100+ "View modules" each of which has: HTML templates CSS files i18n bundles test harnesses Not including 3rd party modules!
Recent project stats Manual dependency management is just not feasible at this scale
Help! Larger, more complex apps require carefully crafted rigorous architecture, patterns, and code organization. -- Brian
IOC We know from Spring, good architectural plumbing helps to manage complexity Javascript is no exception In the browser and on the server
Can we ...? Build smaller modules that are easier to maintain and test Separate configuration and connection from application logic Glue it all back together
Apply IOC concepts in Javascript Declarative component creation Lifecycle management Configuration Dependency injection AOP
IOC for front-end Javascript What might it look like? XML? Not very Javascript-ish Annotations? No existing infrastructure, need to parse source Javascript is flexible: Can we work with the language?
Components The first thing we need is a way to build components
AMD Asynchronous Module Definition
Three main parts Module format Run-time loader Build-time compiler (recommended for production)
AMD Designed with browser environments in mind Loads asynchronously No parsing or transpiling needed Built-in closure Loads other resource types via plugins
Who supports it? dojo 1.7+ cujo.js jQuery 1.7+ MooTools 2+ Lodash and many, many others
define() AMD mandates a single, standardized global function. define();
define() define(factory); define(dependencyList, factory);
AMD Module variants "Standard" AMD define(['when', 'pkg/mod'], function (when, mod) { // use the dependencies, return your module: return {}; });
AMD Module variants AMD-wrapped CommonJS /* no deps, factory params != 0 */ define(function (require, exports, module) { // sync, r-value require var when = require('when'); // decorate your exports object exports.bestUtilEver = function (stuff) { return when(stuff); }; });
AMD Module variants AMD-wrapped Node /* no deps, factory params != 0 */ define(function (require, exports, module) { // sync, r-value require var when = require('when'); // declare your exports module.exports = function bestUtilEver (stuff) { return when(stuff); }; });
UMD Universal Module Format
UMD Boilerplate to sniff environment and export module correctly AMD + legacy globals AMD + CommonJS AMD + Node AMD + Node + legacy globals ... etc.
UMD Module variants AMD + Node (our favorite) (function (define) { define(function (require) { // module code goes here }); })( typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); } );
UMD: AMD + Node app/game/controller (code demo)
Bootstrapping an AMD app "The other global" curl(); requirejs(); require(); (global require is problematic!)
Bootstrapping an AMD app run.js (code demo)
AMD Plugins Same dependency mechanism Non-AMD resources text! - HTML Templates and other text css! and link! - stylesheets i18n! - localization Google Maps JSON data, etc.
AMD Plugins Can do even more powerful things wire! - wire.js IOC container integration has! - has.js feature detection and conditional module loading cs! - loads and transpiles Coffeescript
Plugins app/main (code demo)
CommonJS Module format Every file is a module with its own scope No closure, no factory, no define() require, exports, and module are “free variables”
curl.js <3 CJS! "Compile to AMD"
Node !== CommonJS exports === this exports === module.exports
WTF I know what you’re thinking Which one?!? Why are there 2 (3?) module formats, and how am I supposed to know which one to pick?!?!!1
It gets worse ES Harmony modules are coming Problem: Harmony is an authoring format. It doesn't handle: Dependency management Packaging, version management Compiling, concatenation Non-harmony resources (CSS, HTML, JSONP, etc.)
Relax
Relax Your code is safe! AMD consumes CJS and (soon) Harmony modules
Evolution // curl.js config (coming soon!) packages: [ { name: 'node-thing', location: 'lib/node/thing', main: './main', transform: ['curl/transform/cjsm11'] }, { name: 'cs-thing', location: 'lib/cs/thing', main: './init', transform: ['curl/transform/coffee'] }, { name: 'future-thing', location: 'lib/harmony/stuff', main: './main', transform: ['curl/transform/es7'] }, ... ],
CommonJS Modules today monty-hall-ui/cjsm (branch) Same modules written as CommonJS Modules/1.1, but unwrapped! (code demo)
Micro-modules Smaller is better!
Micro-modules Single-function modules are more reusable easier to test easier to discover
Hazard! https://github.com/dojo/dijit/blob/ef9e7bf5df60a8a74f7e7a7eeaf859b9df3b0
Hazard! How do we avoid dependency hell when using micro-modules?
Connections The lines in your box-and-line diagrams Can be just as important as the stuff you put inside the boxes Unfortunately, we end up putting the lines inside the boxes
AMD Maintains good separation of concerns But more like Java import, which isn't necessarily right for all situations.
Example define(['dojo/store/JsonRest'], function(JsonRest) { function Controller() { this.datastore = new JsonRest({ target: "mycart/items/" }); } Controller.prototype = { addItem: function(thing) { return this.datastore.put(thing); }, // ... } return Controller; });
What's that smell? this.datastore = new JsonRest(..) is essentially a line inside our Controller box How would you unit test it? Could you use this with another type of data store? Multiple instances, each with a different type of store? different target URL?
Refactor define(function() { // No AMD deps! function Controller(datastore) { this.datastore = datastore; } Controller.prototype = { addItem: function(thing) { return this.datastore.put(thing); }, // ... } return Controller; });
Or Similarly define(function() { // No AMD deps! // Rely on the IOC Container to beget new instances return { datastore: null, addItem: function(thing) { return this.datastore.put(thing); }, // ... }; });
What did we do? Decoupled the concrete JsonRest implementation Refactored to rely on a datastore interface Even though the interface is implicit
What did we accomplish? Moved the responsibility of drawing the line out of the Controller. Made Controller more flexible and easier to test
But we created a question Who provides the datastore?
We know what to do Dependency Injection in the Application Composition Layer
DI & Application Composition define({ controller: { create: 'myApp/controller', properties: { datastore: { $ref: 'datastore' } } }, datastore: { create: 'dojo/store/JsonRest', properties: { target: 'things/' } } });
The DOM Obviously, working with the DOM is a necessity in front-end Javascript Similar problems: lines inside the boxes
Example define(['some/domLib'], function(domLib) { function ItemView() { this.domNode = domLib.byId('item-list'); } ItemView.prototype = { render: function() { // Render into this.domNode } } return ItemView; });
That same smell Depends on an HTML id, and a DOM selector library Changing the HTML could break the JS Have to mock the DOM selector lib
Refactor define(function() { // No AMD deps! function ItemView(domNode) { this.domNode = domNode; } ItemView.prototype = { render: function() { // Render into this.domNode } } return ItemView; });
Better Decouples DOM selection mechanism and HTML: Can inject a different DOM node w/o changing ItemView's source.
DOM & Application Composition define({ itemView: { create: { module: 'myApp/ItemView', args: { $ref: 'dom!item-list' } } }, plugins: [ { module: 'wire/dom' } // or { module: 'wire/sizzle' } // or { module: 'wire/dojo/dom' } // or { module: 'wire/jquery/dom' } ] });
DOM Events define(['some/domLib', some/domEventsLib'], function(domLib, domEventsLib) { function Controller() { domEventsLib.on('click', domLib.byId('the-button'), this.addItem.bind(this)); } Controller.prototype = { addItem: function(domEvent) { // Add the item to the cart } } return Controller; });
That same smell, only worse! Depends on: hardcoded event type, HTML id, DOM selection lib DOM events lib More mocking
Refactor define(function() { // No AMD deps! function Controller() {} Controller.prototype = { addItem: function(domEvent) { // Update the thing } } return Controller; });
Better Only cares about a general event: "Now it's time to add the item to the cart" Different/multiple event types on multiple DOM nodes No hardcoded DOM selector: multiple Controller instances Only have to mock the domEvent, then call addItem
DOM Events & App Composition itemViewRoot: { $ref: 'dom.first!.item-view'}, controller: { create: 'myApp/Controller', on: { itemViewRoot: { 'click:button.add': 'addItem' } } }, plugins: [ { module: 'wire/on' } // or { module: 'wire/dojo/on' } // or { module: 'wire/jquery/on' }, { module: 'wire/dom' } ]
JS-to-JS Connections Can components collaborate in a more loosely coupled way than DI?
Synthetic events Javascript methods act like events "Connect" methods together Neither component has knowledge of the other
Example Controller.prototype.addItem = function(domEvent) {...} CartCountView.prototype.incrementCount = function() {...}
Using DI controller: { create: 'myApp/cart/Controller', properties: { cartCountView: { $ref: 'cartCountView' } } }, cartCountView: { create: 'myApp/cart/CartCountView' }
Things we can improve Controller now dependent on CartCountView interface Have to mock CartCountView to unit test Controller What if there are other times we'd like to update the cart count?
Synthetic event connection controller: { create: 'myApp/cart/Controller' }, cartCountView: { create: 'myApp/cart/CartCountView', connect: { 'controller.addItem': 'incrementCount' } }
Better Application Composition layer makes the connection Controller no longer dependent on CartCountView Neither component needs to be re-unit tested when making this connection Nor if the connection is removed later Only need to re-run functional tests Could completely remove CartCountView simply by cutting it out of the Application Composition spec
Still not perfect What if addItem throws or fails in some way?
AOP Connections controller: { create: 'myApp/cart/Controller' }, cartCountView: { create: 'myApp/cart/CartCountView', afterReturning: { 'controller.addItem': 'incrementCount' } }
Closer Only increment count on success What about failures?
AOP Connections controller: { create: 'myApp/cart/Controller', afterReturning: { 'addItem': 'cartCountView.incrementCount' }, afterThrowing: { 'addItem': 'someOtherComponent.showError' } }, cartCountView: { create: 'myApp/cart/CartCountView' }, someOtherComponent: // ...
Better! But not quite there More decoupled, testable, refactorable Still a level of coupling we can remove
Coupled parameters function Controller() {} Controller.prototype = { addItem: function(domEvent) { // How to find the item data, in order to add it? } }
Coupled parameters Controller receives a domEvent, but must locate the associated data to update Need DOM traversal, and understand the DOM structure data id or hash key hiding in a DOM attribute? Have to mock for unit testing
Coupled parameters Controller only really cares about the item
Refactor function Controller() {} Controller.prototype = { addItem: function(item) { // Just add it } }
Transform connections Connections that can, um, transform data!
Transform function define(function() { // Encapsulate the work of finding the item return function findItemFromEvent(domEvent) { // Find the item, then return item; } });
App Composition itemList: { $ref: 'dom.first!.item-list'}, findItem: { module: 'myApp/data/findItemFromEvent' } controller: { create: 'myApp/Controller', on: { itemList: { 'click:button.add': 'findItem | addItem' } } }
Ahhh, at last Controller is easier to unit test Algorithm for finding the thing can also be unit tested separately and more easily can be changed separately from Controller can be reused in other parts of the app
Awesome, we're done, right? Not quite ...
What about asynchrony? Occurs most often at component and system boundaries Hence, connections often need to be asynchronous Canonical example: XHR
Example Controller.prototype.addItem = function(item, callback) {...} CartCountView.prototype.incrementCount = function() {...}
Example controller: { create: 'myApp/cart/Controller', afterReturning: { 'addItem': 'cartCountView.incrementCount' }, afterThrowing: { 'addItem': 'someOtherComponent.showError' } }, cartCountView: { create: 'myApp/cart/CartCountView' }, someOtherComponent: // ...
Uh oh Moved the function result from the return value to the parameter list Since addItem can't return anything, afterReturning doesn't work! And how do we provide the callback?
Brief, asynchronous detour Javascript is designed around a single-threaded event loop Browser DOM events and network I/O are async SSJS platforms (Node, RingoJS, etc) are built around async I/O AMD module loading is async--the A in AMD!
Callbacks The typical solution is callbacks, aka "Continuation Passing"
Example // You wish! var content = xhr('GET', '/stuff');
Add callback and error handler xhr('GET', '/stuff', function(content) { // do stuff }, function(error) { // handle error } );
Callback infestation // It's turtles all the way *up* function getStuff(handleContent, handleError) { xhr('GET', '/stuff', function(content) { // transform content somehow, then // (what happens if this throws?) handleContent(content); }, function(error) { // Maybe parse error, then // (what happens if THIS throws?!?) handleError(error); } ); }
Async is messy Code quickly becomes deeply nested and harder to reason about Familiar programming idioms don't work It's upside-down: Values and errors flow down the stack now rather than up. Functions are no longer easily composable: g(f(x)) doesn't work anymore try/catch/finally, or something reasonably similar is impossible Callback and errback parameters must be added to every function signature that might eventually lead to an asynchronous operation Coordinating multiple async tasks is a pain
Promises Synchronization construct Not a new idea Similar to java.util.concurrent.Future Placeholder for a result or error that will materialize later.
Example Return a promise, into which the content, or an error, will materialize. function getStuff() { var promise = xhr('GET', '/stuff'); return promise; }
Promises Restore call-and-return semantics Move function results back to the return value Remove callback function signature pollution Provide an async analog to exception propagation It's right-side up
More about Promises http://en.wikipedia.org/wiki/Futures_and_promises http://blog.briancavalier.com/async-programming-part-1-its- messy http://blog.briancavalier.com/async-programming-part-2- promises http://github.com/cujojs/when/wiki http://wiki.commonjs.org/wiki/Promises/A
Promises Several proposed standards Promises/A defacto standard cujo.js: when.js Dojo: dojo/Deferred jQuery: $.Deferred (well, close enough) Q soon YUI, Ember
IOC + Promises Promises/A is an integration standard for asynchrony IOC is about gluing components together so they can collaborate Sounds like a match!
Refactor to return a promise Controller.prototype.addItem = function(item) { // Asynchronously add the item, then return promise; }
Promise-aware AOP controller: { create: 'myApp/cart/Controller', afterResolving: { 'addItem': 'cartCountView.incrementCount' }, afterRejecting: { 'addItem': 'someOtherComponent.showError' } }, cartCountView: { create: 'myApp/cart/CartCountView' }, someOtherComponent: // ...
Win Count will only be incremented after the item has been added successfully! If adding fails, show the error
Async without async Promise-aware AOP for async connections AMD loaders manage async module loading and dependency graph resolution. Promise-aware IOC container: Integrate with AMD loader to load modules used in application composition specs. Async component creation: constructor or plain function can return a promise Async DI: component references are injected as promises for the components resolve Component startup/shutdown methods can return a promise
Connections Implement application logic in components Connect components non-invasively via Application Composition DI, events (DOM and JS), AOP, Promise-aware AOP Adapt APIs by transforming data along connections Enjoy the easier testing and refactoring :)
Organize! Components, components, components the "file tree on the left" actually became useful! -- Brian
Organize! Divide your app into feature areas What are the things you talk about when you talk about the app?
Organize! app/ (code demo)
Case: View-component View-components consist of HTML(5) template (keep it simple!) CSS file (structural bits of OOCSS/SMACCS) i18n file(s) javascript controller (optional) test harness (also for design) any assets necessary for rendering any view-specific data transforms, validations, etc. wire spec (optional)
Case: View-component app/instructions (code demo)
Testing visual components How?????
Testing visual components Double-duty test harnesses Fixture for creating HTML and designing CSS Harness for user-driven tests Harness for unit tests
Testing visual components Double-duty test harnesses (code demo)
Unit tests Smaller is better Fewer dependencies means fewer mocks!
Unit tests (code demo)
cujo.js AMD Modules - curl & cram IOC & Application Composition - wire Promises/A - when AOP - meld ES5 - poly Data binding - cola (alpha)
Alternatives AMD Modules - RequireJS, Dojo, lsjs, BravoJS IOC - AngularJS Promises - Q, Dojo AOP - Dojo ES5 - es5shim Data binding - Backbone, and everyone else
cujo.js Get it at http://cujojs.com Discussions, Announcements, Questions, etc. https://groups.google.com/d/forum/cujojs

IOC + Javascript

  • 1.
    IOC + Javascript BrianCavalier @briancavalier / John Hann @unscriptable
  • 2.
    Who? Two back-endengineers who defected to the front-end The main characters behind cujo.js
  • 3.
    What? cujo.js Spring-like concepts, but not a port of Spring to Javascript Embraces Javascript's functional and prototypal roots Provides architectural tools for next-generation JavaScript applications http://cujojs.com/
  • 4.
    How? Less slides, please! Moar awesum codez, plz! Q&A at the end and during code demos! Code demos: Monty Hall UI && TodoMVC
  • 5.
    Recent project stats 6 "HTML Pages" 300+ Javascript modules 100+ "View modules" each of which has: HTML templates CSS files i18n bundles test harnesses Not including 3rd party modules!
  • 6.
    Recent project stats Manualdependency management is just not feasible at this scale
  • 7.
    Help! Larger, more complex apps require carefully crafted rigorous architecture, patterns, and code organization. -- Brian
  • 8.
    IOC We knowfrom Spring, good architectural plumbing helps to manage complexity Javascript is no exception In the browser and on the server
  • 9.
    Can we ...? Build smaller modules that are easier to maintain and test Separate configuration and connection from application logic Glue it all back together
  • 10.
    Apply IOC conceptsin Javascript Declarative component creation Lifecycle management Configuration Dependency injection AOP
  • 11.
    IOC for front-endJavascript What might it look like? XML? Not very Javascript-ish Annotations? No existing infrastructure, need to parse source Javascript is flexible: Can we work with the language?
  • 12.
    Components The first thingwe need is a way to build components
  • 13.
  • 14.
    Three main parts Module format Run-time loader Build-time compiler (recommended for production)
  • 15.
    AMD Designed with browserenvironments in mind Loads asynchronously No parsing or transpiling needed Built-in closure Loads other resource types via plugins
  • 16.
    Who supports it? dojo 1.7+ cujo.js jQuery 1.7+ MooTools 2+ Lodash and many, many others
  • 17.
    define() AMD mandates asingle, standardized global function. define();
  • 18.
  • 19.
    AMD Module variants "Standard"AMD define(['when', 'pkg/mod'], function (when, mod) { // use the dependencies, return your module: return {}; });
  • 20.
    AMD Module variants AMD-wrappedCommonJS /* no deps, factory params != 0 */ define(function (require, exports, module) { // sync, r-value require var when = require('when'); // decorate your exports object exports.bestUtilEver = function (stuff) { return when(stuff); }; });
  • 21.
    AMD Module variants AMD-wrappedNode /* no deps, factory params != 0 */ define(function (require, exports, module) { // sync, r-value require var when = require('when'); // declare your exports module.exports = function bestUtilEver (stuff) { return when(stuff); }; });
  • 22.
  • 23.
    UMD Boilerplate to sniffenvironment and export module correctly AMD + legacy globals AMD + CommonJS AMD + Node AMD + Node + legacy globals ... etc.
  • 24.
    UMD Module variants AMD+ Node (our favorite) (function (define) { define(function (require) { // module code goes here }); })( typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); } );
  • 25.
    UMD: AMD +Node app/game/controller (code demo)
  • 26.
    Bootstrapping an AMDapp "The other global" curl(); requirejs(); require(); (global require is problematic!)
  • 27.
    Bootstrapping an AMDapp run.js (code demo)
  • 28.
    AMD Plugins Same dependency mechanism Non-AMD resources text! - HTML Templates and other text css! and link! - stylesheets i18n! - localization Google Maps JSON data, etc.
  • 29.
    AMD Plugins Can doeven more powerful things wire! - wire.js IOC container integration has! - has.js feature detection and conditional module loading cs! - loads and transpiles Coffeescript
  • 30.
  • 31.
    CommonJS Module format Every file is a module with its own scope No closure, no factory, no define() require, exports, and module are “free variables”
  • 32.
  • 33.
    Node !== CommonJS exports === this exports === module.exports
  • 34.
    WTF I know whatyou’re thinking Which one?!? Why are there 2 (3?) module formats, and how am I supposed to know which one to pick?!?!!1
  • 35.
    It gets worse ESHarmony modules are coming Problem: Harmony is an authoring format. It doesn't handle: Dependency management Packaging, version management Compiling, concatenation Non-harmony resources (CSS, HTML, JSONP, etc.)
  • 36.
  • 37.
    Relax Your code issafe! AMD consumes CJS and (soon) Harmony modules
  • 38.
    Evolution // curl.js config(coming soon!) packages: [ { name: 'node-thing', location: 'lib/node/thing', main: './main', transform: ['curl/transform/cjsm11'] }, { name: 'cs-thing', location: 'lib/cs/thing', main: './init', transform: ['curl/transform/coffee'] }, { name: 'future-thing', location: 'lib/harmony/stuff', main: './main', transform: ['curl/transform/es7'] }, ... ],
  • 39.
    CommonJS Modules today monty-hall-ui/cjsm(branch) Same modules written as CommonJS Modules/1.1, but unwrapped! (code demo)
  • 40.
  • 41.
    Micro-modules Single-function modules are more reusable easier to test easier to discover
  • 42.
  • 43.
    Hazard! How do weavoid dependency hell when using micro-modules?
  • 44.
    Connections Thelines in your box-and-line diagrams Can be just as important as the stuff you put inside the boxes Unfortunately, we end up putting the lines inside the boxes
  • 45.
    AMD Maintains goodseparation of concerns But more like Java import, which isn't necessarily right for all situations.
  • 46.
    Example define(['dojo/store/JsonRest'], function(JsonRest) { function Controller() { this.datastore = new JsonRest({ target: "mycart/items/" }); } Controller.prototype = { addItem: function(thing) { return this.datastore.put(thing); }, // ... } return Controller; });
  • 47.
    What's that smell? this.datastore = new JsonRest(..) is essentially a line inside our Controller box How would you unit test it? Could you use this with another type of data store? Multiple instances, each with a different type of store? different target URL?
  • 48.
    Refactor define(function() { //No AMD deps! function Controller(datastore) { this.datastore = datastore; } Controller.prototype = { addItem: function(thing) { return this.datastore.put(thing); }, // ... } return Controller; });
  • 49.
    Or Similarly define(function() {// No AMD deps! // Rely on the IOC Container to beget new instances return { datastore: null, addItem: function(thing) { return this.datastore.put(thing); }, // ... }; });
  • 50.
    What did wedo? Decoupled the concrete JsonRest implementation Refactored to rely on a datastore interface Even though the interface is implicit
  • 51.
    What did weaccomplish? Moved the responsibility of drawing the line out of the Controller. Made Controller more flexible and easier to test
  • 52.
    But we createda question Who provides the datastore?
  • 53.
    We know whatto do Dependency Injection in the Application Composition Layer
  • 54.
    DI & ApplicationComposition define({ controller: { create: 'myApp/controller', properties: { datastore: { $ref: 'datastore' } } }, datastore: { create: 'dojo/store/JsonRest', properties: { target: 'things/' } } });
  • 55.
    The DOM Obviously,working with the DOM is a necessity in front-end Javascript Similar problems: lines inside the boxes
  • 56.
    Example define(['some/domLib'], function(domLib) { function ItemView() { this.domNode = domLib.byId('item-list'); } ItemView.prototype = { render: function() { // Render into this.domNode } } return ItemView; });
  • 57.
    That same smell Depends on an HTML id, and a DOM selector library Changing the HTML could break the JS Have to mock the DOM selector lib
  • 58.
    Refactor define(function() { //No AMD deps! function ItemView(domNode) { this.domNode = domNode; } ItemView.prototype = { render: function() { // Render into this.domNode } } return ItemView; });
  • 59.
    Better DecouplesDOM selection mechanism and HTML: Can inject a different DOM node w/o changing ItemView's source.
  • 60.
    DOM & ApplicationComposition define({ itemView: { create: { module: 'myApp/ItemView', args: { $ref: 'dom!item-list' } } }, plugins: [ { module: 'wire/dom' } // or { module: 'wire/sizzle' } // or { module: 'wire/dojo/dom' } // or { module: 'wire/jquery/dom' } ] });
  • 61.
    DOM Events define(['some/domLib', some/domEventsLib'],function(domLib, domEventsLib) { function Controller() { domEventsLib.on('click', domLib.byId('the-button'), this.addItem.bind(this)); } Controller.prototype = { addItem: function(domEvent) { // Add the item to the cart } } return Controller; });
  • 62.
    That same smell,only worse! Depends on: hardcoded event type, HTML id, DOM selection lib DOM events lib More mocking
  • 63.
    Refactor define(function() { //No AMD deps! function Controller() {} Controller.prototype = { addItem: function(domEvent) { // Update the thing } } return Controller; });
  • 64.
    Better Onlycares about a general event: "Now it's time to add the item to the cart" Different/multiple event types on multiple DOM nodes No hardcoded DOM selector: multiple Controller instances Only have to mock the domEvent, then call addItem
  • 65.
    DOM Events &App Composition itemViewRoot: { $ref: 'dom.first!.item-view'}, controller: { create: 'myApp/Controller', on: { itemViewRoot: { 'click:button.add': 'addItem' } } }, plugins: [ { module: 'wire/on' } // or { module: 'wire/dojo/on' } // or { module: 'wire/jquery/on' }, { module: 'wire/dom' } ]
  • 66.
    JS-to-JS Connections Can componentscollaborate in a more loosely coupled way than DI?
  • 67.
    Synthetic events Javascript methods act like events "Connect" methods together Neither component has knowledge of the other
  • 68.
    Example Controller.prototype.addItem = function(domEvent){...} CartCountView.prototype.incrementCount = function() {...}
  • 69.
    Using DI controller: { create: 'myApp/cart/Controller', properties: { cartCountView: { $ref: 'cartCountView' } } }, cartCountView: { create: 'myApp/cart/CartCountView' }
  • 70.
    Things we canimprove Controller now dependent on CartCountView interface Have to mock CartCountView to unit test Controller What if there are other times we'd like to update the cart count?
  • 71.
    Synthetic event connection controller:{ create: 'myApp/cart/Controller' }, cartCountView: { create: 'myApp/cart/CartCountView', connect: { 'controller.addItem': 'incrementCount' } }
  • 72.
    Better ApplicationComposition layer makes the connection Controller no longer dependent on CartCountView Neither component needs to be re-unit tested when making this connection Nor if the connection is removed later Only need to re-run functional tests Could completely remove CartCountView simply by cutting it out of the Application Composition spec
  • 73.
    Still not perfect Whatif addItem throws or fails in some way?
  • 74.
    AOP Connections controller: { create: 'myApp/cart/Controller' }, cartCountView: { create: 'myApp/cart/CartCountView', afterReturning: { 'controller.addItem': 'incrementCount' } }
  • 75.
    Closer Onlyincrement count on success What about failures?
  • 76.
    AOP Connections controller: { create: 'myApp/cart/Controller', afterReturning: { 'addItem': 'cartCountView.incrementCount' }, afterThrowing: { 'addItem': 'someOtherComponent.showError' } }, cartCountView: { create: 'myApp/cart/CartCountView' }, someOtherComponent: // ...
  • 77.
    Better! But notquite there More decoupled, testable, refactorable Still a level of coupling we can remove
  • 78.
    Coupled parameters function Controller(){} Controller.prototype = { addItem: function(domEvent) { // How to find the item data, in order to add it? } }
  • 79.
    Coupled parameters Controller receives a domEvent, but must locate the associated data to update Need DOM traversal, and understand the DOM structure data id or hash key hiding in a DOM attribute? Have to mock for unit testing
  • 80.
    Coupled parameters Controller onlyreally cares about the item
  • 81.
    Refactor function Controller() {} Controller.prototype= { addItem: function(item) { // Just add it } }
  • 82.
  • 83.
    Transform function define(function() { // Encapsulate the work of finding the item return function findItemFromEvent(domEvent) { // Find the item, then return item; } });
  • 84.
    App Composition itemList: {$ref: 'dom.first!.item-list'}, findItem: { module: 'myApp/data/findItemFromEvent' } controller: { create: 'myApp/Controller', on: { itemList: { 'click:button.add': 'findItem | addItem' } } }
  • 85.
    Ahhh, at last Controller is easier to unit test Algorithm for finding the thing can also be unit tested separately and more easily can be changed separately from Controller can be reused in other parts of the app
  • 86.
    Awesome, we're done,right? Not quite ...
  • 87.
    What about asynchrony? Occurs most often at component and system boundaries Hence, connections often need to be asynchronous Canonical example: XHR
  • 88.
    Example Controller.prototype.addItem = function(item,callback) {...} CartCountView.prototype.incrementCount = function() {...}
  • 89.
    Example controller: { create: 'myApp/cart/Controller', afterReturning: { 'addItem': 'cartCountView.incrementCount' }, afterThrowing: { 'addItem': 'someOtherComponent.showError' } }, cartCountView: { create: 'myApp/cart/CartCountView' }, someOtherComponent: // ...
  • 90.
    Uh oh Moved the function result from the return value to the parameter list Since addItem can't return anything, afterReturning doesn't work! And how do we provide the callback?
  • 91.
    Brief, asynchronous detour Javascript is designed around a single-threaded event loop Browser DOM events and network I/O are async SSJS platforms (Node, RingoJS, etc) are built around async I/O AMD module loading is async--the A in AMD!
  • 92.
    Callbacks The typical solutionis callbacks, aka "Continuation Passing"
  • 93.
    Example // You wish! varcontent = xhr('GET', '/stuff');
  • 94.
    Add callback anderror handler xhr('GET', '/stuff', function(content) { // do stuff }, function(error) { // handle error } );
  • 95.
    Callback infestation // It'sturtles all the way *up* function getStuff(handleContent, handleError) { xhr('GET', '/stuff', function(content) { // transform content somehow, then // (what happens if this throws?) handleContent(content); }, function(error) { // Maybe parse error, then // (what happens if THIS throws?!?) handleError(error); } ); }
  • 96.
    Async is messy Code quickly becomes deeply nested and harder to reason about Familiar programming idioms don't work It's upside-down: Values and errors flow down the stack now rather than up. Functions are no longer easily composable: g(f(x)) doesn't work anymore try/catch/finally, or something reasonably similar is impossible Callback and errback parameters must be added to every function signature that might eventually lead to an asynchronous operation Coordinating multiple async tasks is a pain
  • 97.
    Promises Synchronizationconstruct Not a new idea Similar to java.util.concurrent.Future Placeholder for a result or error that will materialize later.
  • 98.
    Example Return a promise,into which the content, or an error, will materialize. function getStuff() { var promise = xhr('GET', '/stuff'); return promise; }
  • 99.
    Promises Restorecall-and-return semantics Move function results back to the return value Remove callback function signature pollution Provide an async analog to exception propagation It's right-side up
  • 100.
    More about Promises http://en.wikipedia.org/wiki/Futures_and_promises http://blog.briancavalier.com/async-programming-part-1-its- messy http://blog.briancavalier.com/async-programming-part-2- promises http://github.com/cujojs/when/wiki http://wiki.commonjs.org/wiki/Promises/A
  • 101.
    Promises Severalproposed standards Promises/A defacto standard cujo.js: when.js Dojo: dojo/Deferred jQuery: $.Deferred (well, close enough) Q soon YUI, Ember
  • 102.
    IOC + Promises Promises/A is an integration standard for asynchrony IOC is about gluing components together so they can collaborate Sounds like a match!
  • 103.
    Refactor to returna promise Controller.prototype.addItem = function(item) { // Asynchronously add the item, then return promise; }
  • 104.
    Promise-aware AOP controller: { create: 'myApp/cart/Controller', afterResolving: { 'addItem': 'cartCountView.incrementCount' }, afterRejecting: { 'addItem': 'someOtherComponent.showError' } }, cartCountView: { create: 'myApp/cart/CartCountView' }, someOtherComponent: // ...
  • 105.
    Win Count willonly be incremented after the item has been added successfully! If adding fails, show the error
  • 106.
    Async without async Promise-aware AOP for async connections AMD loaders manage async module loading and dependency graph resolution. Promise-aware IOC container: Integrate with AMD loader to load modules used in application composition specs. Async component creation: constructor or plain function can return a promise Async DI: component references are injected as promises for the components resolve Component startup/shutdown methods can return a promise
  • 107.
    Connections Implementapplication logic in components Connect components non-invasively via Application Composition DI, events (DOM and JS), AOP, Promise-aware AOP Adapt APIs by transforming data along connections Enjoy the easier testing and refactoring :)
  • 108.
    Organize! Components, components, components the "file tree on the left" actually became useful! -- Brian
  • 109.
    Organize! Divide your appinto feature areas What are the things you talk about when you talk about the app?
  • 110.
  • 111.
    Case: View-component View-components consistof HTML(5) template (keep it simple!) CSS file (structural bits of OOCSS/SMACCS) i18n file(s) javascript controller (optional) test harness (also for design) any assets necessary for rendering any view-specific data transforms, validations, etc. wire spec (optional)
  • 112.
  • 113.
  • 114.
    Testing visual components Double-dutytest harnesses Fixture for creating HTML and designing CSS Harness for user-driven tests Harness for unit tests
  • 115.
    Testing visual components Double-dutytest harnesses (code demo)
  • 116.
    Unit tests Smaller is better Fewer dependencies means fewer mocks!
  • 117.
  • 118.
    cujo.js AMDModules - curl & cram IOC & Application Composition - wire Promises/A - when AOP - meld ES5 - poly Data binding - cola (alpha)
  • 119.
    Alternatives AMDModules - RequireJS, Dojo, lsjs, BravoJS IOC - AngularJS Promises - Q, Dojo AOP - Dojo ES5 - es5shim Data binding - Backbone, and everyone else
  • 120.
    cujo.js Get it athttp://cujojs.com Discussions, Announcements, Questions, etc. https://groups.google.com/d/forum/cujojs