beyond dom manipulations: building stateful modules with events and promises
DESCRIPTION
Presented at jQuery Conf Portland 2013TRANSCRIPT
Building Stateful Modules with Events and PromisesDOM Manipulation
patrickCAMACHO
Beyond
Friday, June 14, 13
Crashlytics for Android & iOS
Friday, June 14, 13
Friday, June 14, 13
Friday, June 14, 13
Friday, June 14, 13
Friday, June 14, 13
Rails to Backbone.
Friday, June 14, 13
What did we have?
ModelBackbone.Model
Backbone.Collection
RoutingBackbone.Router,Backbone.History
ViewsBackbone.Views
EventsBackbone.Events
MV* components
Friday, June 14, 13
What’s missing?
Friday, June 14, 13
Transitioning Pages to States.
Friday, June 14, 13
Piecemeal.
App
State
Friday, June 14, 13
Piecemeal.
App
Router
State
Friday, June 14, 13
Piecemeal.
App
Router Directors
State
Friday, June 14, 13
Piecemeal.
App
Router Directors
State
Friday, June 14, 13
Adding a modal.
Friday, June 14, 13
Friday, June 14, 13
Needed better structure.
• Built on single flow and
states
• Modal didn’t fit flow
• Back to the
drawing board
App
Router Directors
State
Friday, June 14, 13
Needed better structure.
• Built on single flow and
states
• Modal didn’t fit flow
• Back to the
drawing board
App
Router Directors
State
Settings
Friday, June 14, 13
The birth of the “module”.
• Entirely independent pieces of functionality
• It could accept events and start / stop itself
State
this.$('.settings').click(function(){ CLS.Components.Settings.trigger('start');});
Settings
this.$('.overlay').click((function(){ this.trigger('stop');}).bind(this));
Friday, June 14, 13
Async behavior.
Friday, June 14, 13
Async behavior in states.
• Fetching data, animations, etc
• Want to shut anything down when stopping
Settings
Server
(rendering)
Friday, June 14, 13
Promises.
• $.Promises and $.Deferreds
• .done, .fail, .always
• .resolve, .reject
fetch1 = $.get('data.json');fetch2 = $.get('data2.json');
fetch1.done(function(){ console.log(‘success!’); } fetch2.always(function(){ console.log(‘complete!’); }
$.when(fetch1, fetch2).fail(function(){ console.log(‘fail!’); });
Friday, June 14, 13
Using with Components.
Settings.start = function() { this.stopDeferred = $.Deferred();
fetch1 = $.get('data.json'); this.stopDeferred.done(fetch1.abort);
fetch2 = $.get('data2.json'); this.stopDeferred.done(fetch2.abort);
$.when(fetch1, fetch2).done(this.render.bind(this));}
Settings.stop = function() { this.stopDeferred.resolve();}
Friday, June 14, 13
Good in the short run.
• Only had one application
• Components lived
forever
• Singletons hid the
problems
Settings Alert Center
Real Time Analytics
Friday, June 14, 13
Multiple applications.
Friday, June 14, 13
Distinct functionality.
Friday, June 14, 13
Distinct functionality.
Friday, June 14, 13
Multiple applications.
• Lost core assumption of a page-long app
• Apps began to look more and more like modules
Onboarding
Onboarding.start : function(){ if(this._isActive) return; ... this._isActive = true;}
Onboarding.stop : function(){ if(!this._isActive) return; ... this._isActive = false;}
Friday, June 14, 13
Multiple applications.
Apps
Router Directors
State
Friday, June 14, 13
Persistent functionalty.
• Components needed to be started / stopped by apps on
start / stop
• Not all should be started or stopped
• Back to heavy conditionals
if(nextApp === 'onboard') { CLS.Components.Settings.trigger('stop'); CLS.Components.AlertCenter.trigger('stop');} else if(nextApp === 'logout') { CLS.Components.Settings.trigger('stop'); CLS.Components.AlertCenter.trigger('stop'); CLS.Components.RealTime.trigger('stop');} else if...
Friday, June 14, 13
Finding a pattern.
Friday, June 14, 13
Same problem, different levels.
• Eventing
• Start
• Stop
• Dependencies
App
State State
Component Component Component
Friday, June 14, 13
Isolating knowledge.
Friday, June 14, 13
Too many direct references.
• Don’t know outside information
• Clearest in stopping dependencies
if(nextApp === 'onboard') { CLS.Components.Settings.trigger('stop'); CLS.Components.AlertCenter.trigger('stop');} else if(nextApp === 'logout') { CLS.Components.Settings.trigger('stop'); CLS.Components.AlertCenter.trigger('stop'); CLS.Components.RealTime.trigger('stop');} else if...
Friday, June 14, 13
Eventing with arguments.
Onboarding Dashboard
Dashboard.start = function() { Onboarding.trigger('stop', this.dependencies);}
Onboarding.stop = function(dependencies) { if(dependencies == null) { dependencies = [] } this.dependencies.forEach(function(dependencies){ if(dependencies.indexOf(dependency) < 0) { dependency.trigger('stop', dependencies); } });}
Friday, June 14, 13
• Wanted events, but not the knowledge
Still tightly coupled.
Dashboard.start = function() { Onboarding.trigger('stop', this.dependencies); LoggedOut.trigger('stop', this.dependencies);}
Friday, June 14, 13
Simplify functionality.
Friday, June 14, 13
Using a vent.
• All of these pieces are using events
• Isolate that functionality to a single unit
Vent
Vent = function() {...}Vent.prototype.on = function() {...}Vent.prototype.one = function() {...}Vent.prototype.off = function() {...}Vent.prototype.trigger = function() {...}
Friday, June 14, 13
Sharing a vent.
OnboardingDashboard Vent
Onboarding.start = function() { this.vent.trigger('app:dashboard:stop', this.dependencies); this.vent.trigger('app:loggedOut:stop', this.dependencies);}
Friday, June 14, 13
Smart subscriptions.
Dashboard.start = function(vent) { this.vent.trigger('app:onBeforeStart', this.dependencies); this.vent.one('app:onBeforeStart', this.stop.bind(this));}
Onboarding.start = function(vent) { this.vent.trigger('app:onBeforeStart', this.dependencies); this.vent.one('app:onBeforeStart', this.stop.bind(this));}
OnboardingDashboard Vent
Friday, June 14, 13
Sharing information.
Friday, June 14, 13
• Share data between modules
• Use vent to register responses and request
Synchronous data returns.
VentDashboard Settings
Friday, June 14, 13
Dashboard.start = function() { this.currentApplication = ‘foo bar’ this.vent.setResponse( 'current_application', (function(){ return this.currentApplication; }).bind(this); );}
Settings.start = function() { app = this.vent.requestResponse('current_application');}
Synchronous data returns.
Friday, June 14, 13
Tying it all together.
Friday, June 14, 13
Modularize all the things!
• Isolated functionality
• Start / stop
• Managing dependencies
• Eventing
• Async behavior
Friday, June 14, 13
Modularize all the things!
Component
App
State
• Isolated functionality
• Start / stop
• Managing dependencies
• Eventing
• Async behavior
Friday, June 14, 13
Vent
Rethinking the flow.
Friday, June 14, 13
Vent
Rethinking the flow.
Router
Friday, June 14, 13
ComponentsVent
Rethinking the flow.
Apps
Router
Friday, June 14, 13
Vent
ComponentsVent
Rethinking the flow.
Apps
Router
States
Friday, June 14, 13
Vent
Vent
ComponentsVent
Rethinking the flow.
Apps
Router
States
States
Friday, June 14, 13
Making it yours
• Manage complexity and scale
• Isolate functionality into modules
• Manage dependencies
• Allow modules to communicate through a vent
Friday, June 14, 13
YOUpatrickCAMACHO
Thank
try.crashlytics.com/jobs
Friday, June 14, 13