Complex Architectures in Ember

13,958 views
13,834 views

Published on

In Ember.js, routes and templates dictate the architecture of your app. This presentation will talk about why this is, and what tools Ember provides to manage architectural complexity.

Published in: Entertainment & Humor, Sports
0 Comments
20 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
13,958
On SlideShare
0
From Embeds
0
Number of Embeds
7,683
Actions
Shares
0
Downloads
92
Comments
0
Likes
20
Embeds 0
No embeds

No notes for slide

Complex Architectures in Ember

  1. 1. Hi. I’m Matthew. I build spiffy apps for clients in NYC Thursday, September 19, 13
  2. 2. @mixonic httP://madhatted.com matt.beale@madhatted.com Thursday, September 19, 13
  3. 3. Let’s go single page Thursday, September 19, 13
  4. 4. AMBITIOUS Thursday, September 19, 13
  5. 5. COMPLEX Thursday, September 19, 13
  6. 6. Start simple. Thursday, September 19, 13
  7. 7. title body preview submit preview Thursday, September 19, 13
  8. 8. title body preview submit preview Thursday, September 19, 13
  9. 9. 1 <div style="clear:both"> 2 <div style="float:left"> 3 {{input value=title}} 4 {{textarea value=body}} 5 </div> 6 <div style="float:left"> 7 {{post-preview markdown=body}} 8 </div> 9 </div> 10 11 <div style="clear:both"> 12 <button style="float:left" {{action "preview"}}>Preview</button> 13 <button style="float:left" {{action "submit"}}>Submit</button> 14 </div> Thursday, September 19, 13
  10. 10. Great! HTML. Thursday, September 19, 13
  11. 11. HTML dictates layout. Thursday, September 19, 13
  12. 12. HTML dictate layout. Templates Thursday, September 19, 13
  13. 13. AND YOUR ARCHITECTURE Thursday, September 19, 13
  14. 14. 1 <div style="clear:both"> 2 <div style="float:left"> 3 {{input value=title}} 4 {{textarea value=body}} 5 </div> 6 <div style="float:left"> 7 {{post-preview markdown=body}} 8 </div> 9 </div> 10 11 <div style="clear:both"> 12 <button style="float:left" {{action "preview"}}>Preview</button> 13 <button style="float:left" {{action "submit"}}>Submit</button> 14 </div> ACTION DOESNT TALK TO COMPONENTS Thursday, September 19, 13
  15. 15. 1 <div style="clear:both"> 2 <div style="float:left"> 3 {{input value=title}} 4 {{textarea value=body}} 5 </div> 6 <div style="float:left"> 7 {{post-preview markdown=body}} 8 </div> 9 </div> 10 11 <div style="clear:both"> 12 <button style="float:left" {{action "preview"}}>Preview</button> 13 <button style="float:left" {{action "submit"}}>Submit</button> 14 </div> CHASM OF DOM Thursday, September 19, 13
  16. 16. 1 <div style="clear:both"> 2 <div style="float:left"> 3 {{input value=title}} 4 {{textarea value=body}} 5 </div> 6 <div style="float:left"> 7 {{post-preview markdown=body viewName="preview"}} 8 </div> 9 </div> 10 11 <div style="clear:both"> 12 <button style="float:left" {{action "preview" target=view.preview}}>Preview</button> 13 <button style="float:left" {{action "submit"}}>Submit</button> 14 </div> WORKAROUND 1 Thursday, September 19, 13
  17. 17. 1 <div style="clear:both"> 2 <div style="float:left"> 3 {{input value=title}} 4 {{textarea value=body}} 5 </div> 6 <div style="float:left"> 7 {{view App.PostPreview markdownBinding=body viewName="preview"}} 8 </div> 9 </div> 10 11 <div style="clear:both"> 12 <button style="float:left" {{action "preview" target=view.preview}}>Preview</button> 13 <button style="float:left" {{action "submit"}}>Submit</button> 14 </div> WORKAROUND 2 Thursday, September 19, 13
  18. 18. 1 <div style="clear:both"> 2 <div style="float:left"> 3 {{input value=title}} 4 {{textarea value=body}} 5 </div> 6 <div style="float:left"> 7 {{render "post_preview" body}} 8 </div> 9 </div> 10 11 <div style="clear:both"> 12 <button style="float:left" {{action "preview"}}>Preview</button> 13 <button style="float:left" {{action "submit"}}>Submit</button> 14 </div> 1 App.ApplicationRoute = Ember.Route.extend({ 2 actions: { 3 preview: function(){ 4 var controller = this.controllerFor('post_preview'); 5 controller.updatePreview(); 6 } 7 } 8 }); WORKAROUND 3 Thursday, September 19, 13
  19. 19. WORKAROUND 4 1 App.ApplicationRoute = Ember.Route.extend({ 2 renderTemplate: function(){ 3 this._super.apply(this, arguments); 4 this.render('post_preview', { 5 into: 'application', 6 outlet: 'preview', 7 controller: 'post_preview' 8 }); 9 }, 10 actions: { 11 preview: function(){ 12 var app = this.controllerFor('application'); 13 var preview = this.controllerFor('post_preview'); 14 preview.set('markdown', app.get('body')); 15 } 16 } 17 }); 18 19 App.PostPreviewController = Ember.Controller.extend(); 1 <div style="clear:both"> 2 <div style="float:left"> 3 {{input value=title}} 4 {{textarea value=body}} 5 </div> 6 <div style="float:left"> 7 {{outlet "preview"}} 8 </div> 9 </div> 10 11 <div style="clear:both"> 12 <button style="float:left" {{action "preview"}}>Preview</button> 13 <button style="float:left" {{action "submit"}}>Submit</button> 14 </div> Thursday, September 19, 13
  20. 20. When we pick between these options... Thursday, September 19, 13
  21. 21. We make design decisions. Thursday, September 19, 13
  22. 22. •Re-usability as ui component •re-usability as action •If an action fires •Where the action is handled •The internals of preview Thursday, September 19, 13
  23. 23. ARCHITECTURE Thursday, September 19, 13
  24. 24. Architecture in Ember apps is dictated by routes and templates. Thursday, September 19, 13
  25. 25. Routes and templates decide how actions propagate the controller/route tree, scope access to dependencies, and are most subject to external constraints. Thursday, September 19, 13
  26. 26. Routes and templates decide how actions propagate the controller/route tree, scope access to dependencies, and are most subject to external constraints. Thursday, September 19, 13
  27. 27. Routes and templates decide how actions propagate the controller/route tree, scope access to dependencies, and are most subject to external constraints. Thursday, September 19, 13
  28. 28. Routes and templates decide how actions propagate the controller/route tree, scope access to dependencies, and are most subject to external constraints. Thursday, September 19, 13
  29. 29. Routes and templates decide how actions propagate the controller/route tree, scope access to dependencies, and are most subject to external constraints. Thursday, September 19, 13
  30. 30. “Understanding actions in two easy steps” Thursday, September 19, 13
  31. 31. #1: Bubbling Thursday, September 19, 13
  32. 32. WARNING THEse slides describe the behavior IN rc8 and beyond. behavior before the changes in rc8 is very similar to what is described here, but not identical. Thursday, September 19, 13
  33. 33. ACTION BUBBLING 1 var Actionable = Ember.Object.extend(Ember.ActionHandler); 2 3 var firstTarget = Actionable.create(); 4 5 firstTarget.send("Wie Geht's"); 6 7 // Nothing! Thursday, September 19, 13
  34. 34. ACTION BUBBLING 1 var Actionable = Ember.Object.extend(Ember.ActionHandler); 2 3 var firstTarget = Actionable.extend({ 4 actions: { 5 "Wie Geht's": function(){ 6 console.log("Danke gut"); 7 } 8 } 9 }).create(); 10 11 firstTarget.send("Wie Geht's"); 12 13 // Danke gut Thursday, September 19, 13
  35. 35. ACTION BUBBLING 1 var Actionable = Ember.Object.extend(Ember.ActionHandler); 2 3 var secondTarget = Actionable.extend({ 4 actions: { 5 "Wie Geht's": function(){ 6 console.log("Und dir?"); 7 } 8 } 9 }).create(); 10 11 var firstTarget = Actionable.extend({ 12 target: secondTarget, 13 actions: { 14 "Wie Geht's": function(){ 15 console.log("Danke gut"); 16 } 17 } 18 }).create(); 19 20 firstTarget.send("Wie Geht's"); 21 22 // Danke gut Thursday, September 19, 13
  36. 36. ACTION BUBBLING 1 var Actionable = Ember.Object.extend(Ember.ActionHandler); 2 3 var secondTarget = Actionable.extend({ 4 actions: { 5 "Wie Geht's": function(){ 6 console.log("Und dir?"); 7 } 8 } 9 }).create(); 10 11 var firstTarget = Actionable.extend({ 12 target: secondTarget, 13 actions: { 14 "Wie Geht's": function(){ 15 console.log("Danke gut"); 16 return true; 17 } 18 } 19 }).create(); 20 21 firstTarget.send("Wie Geht's"); 22 23 // Danke gut 24 // Und dir? RETUrn true to continue bubbling the action Thursday, September 19, 13
  37. 37. ACTION BUBBLING 1 var Actionable = Ember.Object.extend(Ember.ActionHandler); 2 3 var secondTarget = Actionable.extend({ 4 actions: { 5 "Wie Geht's": function(){ 6 console.log("Und dir?"); 7 } 8 } 9 }).create(); 10 11 var firstTarget = Actionable.extend({ 12 target: secondTarget, 13 actions: { 14 "Wie Geht's": null, // Or just don't define actions 15 } 16 }).create(); 17 18 firstTarget.send("Wie Geht's"); 19 20 // Und dir? Thursday, September 19, 13
  38. 38. Controllers, Routes, Views, and Components handle actions. Thursday, September 19, 13
  39. 39. Only Controllers and Routes have targets for bubbling. Thursday, September 19, 13
  40. 40. #1: ROUTES ARE stateS. Kind of. Thursday, September 19, 13
  41. 41. ROUTES ARE STATES1 var moodManager = Ember.StateManager.create({ 2 initialState: 'good', 3 good: Ember.State.create({ 4 gut: function(){ 5 console.log('Ja'); 6 } 7 }), 8 bad: Ember.State.create({ 9 gut: function(){ 10 console.log('Nein'); 11 } 12 }), 13 quiet: Ember.State.create() 14 }); 15 16 moodManager.send('gut'); 17 // Ja 18 19 moodManager.transitionTo('bad'); 20 moodManager.send('gut'); 21 // Nein 22 23 moodManager.transitionTo('quiet'); 24 moodManager.send('gut'); 25 // Uncaught Error: <Ember.StateManager:ember138> could not respond to event gut in state quiet. Thursday, September 19, 13
  42. 42. ROUTES ARE STATES1 App = Ember.Application.create(); 2 3 App.Router.map(function(){ 4 this.route('good'); 5 this.route('bad'); 6 this.route('quiet'); 7 }); 8 9 App.GoodRoute = Ember.Route.extend({ 10 actions: { gut: function(){ console.log('Ja'); } } 11 }); 12 13 App.BadRoute = Ember.Route.extend({ 14 actions: { gut: function(){ console.log('Nein'); } } 15 }); 16 17 App.QuietRoute = Ember.Route.extend(); 1 {{! application.hbs }} 2 <a {{action "gut"}}>Gut?</a> 3 {{#link-to "good"}}Get happy{{/link-to}} 4 {{#link-to "bad"}}Get sour{{/link-to}} 5 {{#link-to "quiet"}}Get quiet{{/link-to}} Thursday, September 19, 13
  43. 43. ROUTES ARE STATES1 App = Ember.Application.create(); 2 3 App.Router.map(function(){ 4 this.route('good'); 5 this.route('bad'); 6 this.route('quiet'); 7 }); 8 9 App.GoodRoute = Ember.Route.extend({ 10 actions: { gut: function(){ console.log('Ja'); } } 11 }); 12 13 App.BadRoute = Ember.Route.extend({ 14 actions: { gut: function(){ console.log('Nein'); } } 15 }); 16 17 App.QuietRoute = Ember.Route.extend(); 1 {{! application.hbs }} 2 <a {{action "gut"}}>Gut?</a> 3 {{#link-to "good"}}Get happy{{/link-to}} 4 {{#link-to "bad"}}Get sour{{/link-to}} 5 {{#link-to "quiet"}}Get quiet{{/link-to}} ACTIOns always hit the leaf route, regardless of where they fire from Thursday, September 19, 13
  44. 44. ROUTES ARE STATES 1 App = Ember.Application.create(); 2 3 App.Router.map(function(){ 4 this.route('good'); 5 this.route('bad'); 6 this.route('quiet'); 7 }); 8 9 App.GoodRoute = Ember.Route.extend({ 10 actions: { gut: function(){ console.log('Ja'); } } 11 }); 12 13 App.GoodController = Ember.Controller.extend({ 14 actions: { gut: function(){ console.log('ignored :-('); } } 15 }); 1 {{! application.hbs }} 2 <a {{action "gut"}}>Gut?</a> 3 {{#link-to "good"}}Get happy{{/link-to}} 4 {{#link-to "bad"}}Get sour{{/link-to}} 5 {{#link-to "quiet"}}Get quiet{{/link-to}} BUT the TEMPLATE DECIDES WHICH CONTROLLERS SEE THAT ACTION Thursday, September 19, 13
  45. 45. ACTIONS ON CONTROLLERS •couples template to controller •should not force use of NEEDs ACTIONS ON routes •have access to all controllers •handled from any template on a page Thursday, September 19, 13
  46. 46. Choose where to put an action. Thursday, September 19, 13
  47. 47. Routes and templates decide how actions propagate the controller/route tree, scope access to dependencies, and are most subject to external constraints. Thursday, September 19, 13
  48. 48. 1 <div style="clear:both"> 2 <div style="float:left"> 3 {{input value=title}} 4 {{textarea value=body}} 5 </div> 6 <div style="float:left"> 7 {{post-preview markdown=body viewName="preview"}} 8 </div> 9 </div> 10 11 <div style="clear:both"> 12 <button style="float:left" {{action "preview" target=view.preview}}>Preview</button> 13 <button style="float:left" {{action "submit"}}>Submit</button> 14 </div> setting viewName causes a property named “preview” to be added on the parentview of that view with it’s own instance that “preview” property can be set as a target Thursday, September 19, 13
  49. 49. •Components access nothing •views access parentView, controller •controllers access A target (the route or a parent controller) AND OTHER CONTROLLERS via needs •routes access all controllers and models •CHEAT WITH REGISTER/INJECT Thursday, September 19, 13
  50. 50. CHEAT WITH REGISTER/INJECT 1 App = Ember.Application.create(); 2 3 App.inject('route', 'session', 'controller:session'); 4 5 App.IndexRoute = Ember.Route.extend({ 6 beforeModel: function(){ 7 console.log( this.get('session.user.name') ); 8 } 9 }); 10 11 App.SessionController = Ember.Controller.extend({ 12 user: { name: 'Bob' } 13 }); Thursday, September 19, 13
  51. 51. 1 {{render "post"}} 2 {{render "post" post}} 3 {{component content=post}} 4 {{view App.PostView contentBinding="post"}} 5 {{template "post"}} THIS dictates part of your architecture Thursday, September 19, 13
  52. 52. When confused about an app, look to the templates and the routes first. Thursday, September 19, 13
  53. 53. “Controllers have lots of features!” AKA BUT PLEASE DON’t MAKE THEM CLEVER Thursday, September 19, 13
  54. 54. “I AM SO CLEVER THAT SOMETIMES I DON’T UNDERSTAND A SINGLE WORD OF WHAT I AM SAYING” Oscar Wilde Thursday, September 19, 13
  55. 55. Clever Controller 1 1 App.BooksController = Ember.ArrayController.extend({ 2 needs: ['library'], 3 content: Em.computed.alias('controllers.library.books') 4 }); Thursday, September 19, 13
  56. 56. Clever Controller 1 1 App.BooksController = Ember.ArrayController.extend({ 2 needs: ['library'], 3 content: Em.computed.alias('controllers.library.books') 4 }); assumption about library controller Assumed to only be attached to a route (no item controller) assumes books belong to a single library Thursday, September 19, 13
  57. 57. LESS Clever Controller 1 1 App.BooksRoute = Ember.Route.extend({ 2 model: function(){ 3 this.modelFor('library').get('books') 4 } 5 }); 6 7 // The Ember controller provided by convention is sufficient. Thursday, September 19, 13
  58. 58. 1 <div> 2 <ul class="tabs"> 3 <li {{action "openSignIn"}}>Sign In</li> 4 <li {{action "openSignUp"}}>Sign Up</li> 5 <li {{action "openForgotPassword"}}>Forgot Password</li> 6 </ul> 7 {{#if isSignInOpen}}{{template "sign_in"}}{{/if}} 8 {{#if isSignUpOpen}}{{template "sign_up"}}{{/if}} 9 {{#if isForgotPasswordOpen}}{{template "forgot_password"}}{{/if}} 10 </div> Clever Controller 2 1 App.SessionController = Em.Controller.extend({ 2 isSignInOpen: false, 3 isSignUpOpen: false, 4 isForgotPasswordOpen: false, 5 6 actions: { 7 closeOptions: function(){ 8 this.setProperties({ 9 isSignInOpen: false, 10 isSignUpOpen: false, 11 isForgotPasswordOpen: false 12 }); 13 }, 14 openSignIn: function(){ this.closeOptions(); this.set('isSignUpOpen', true); }, 15 openSignUp: function(){ this.closeOptions(); this.set('isSignUpOpen', true); }, 16 openForgotPassword: function(){ this.closeOptions(); this.set('isForgotPasswordOpen', true); } 17 } 18 }); Thursday, September 19, 13
  59. 59. 1 <div> 2 <ul class="tabs"> 3 <li {{action "openSignIn"}}>Sign In</li> 4 <li {{action "openSignUp"}}>Sign Up</li> 5 <li {{action "openForgotPassword"}}>Forgot Password</li> 6 </ul> 7 {{#if isSignInOpen}}{{template "sign_in"}}{{/if}} 8 {{#if isSignUpOpen}}{{template "sign_up"}}{{/if}} 9 {{#if isForgotPasswordOpen}}{{template "forgot_password"}}{{/if}} 10 </div> Clever Controller 2 1 App.SessionController = Em.Controller.extend({ 2 isSignInOpen: false, 3 isSignUpOpen: false, 4 isForgotPasswordOpen: false, 5 6 actions: { 7 closeOptions: function(){ 8 this.setProperties({ 9 isSignInOpen: false, 10 isSignUpOpen: false, 11 isForgotPasswordOpen: false 12 }); 13 }, 14 openSignIn: function(){ this.closeOptions(); this.set('isSignUpOpen', true); }, 15 openSignUp: function(){ this.closeOptions(); this.set('isSignUpOpen', true); }, 16 openForgotPassword: function(){ this.closeOptions(); this.set('isForgotPasswordOpen', true); } 17 } 18 }); NOT. VERY. DRY. NEW PANELS MUST BE ADDED ON COnTROLLER NEW PANELS MUST BE ADDED IN TEMPLATE Thursday, September 19, 13
  60. 60. 1 <div> 2 <ul class="tabs"> 3 <li {{action "openSignIn"}}>Sign In</li> 4 <li {{action "openSignUp"}}>Sign Up</li> 5 <li {{action "openForgotPassword"}}>Forgot Password</li> 6 </ul> 7 {{#if isSignInOpen}}{{template "sign_in"}}{{/if}} 8 {{#if isSignUpOpen}}{{template "sign_up"}}{{/if}} 9 {{#if isForgotPasswordOpen}}{{template "forgot_password"}}{{/if}} 10 </div> Clever Controller 2 1 App.SessionController = Em.Controller.extend({ 2 isSignInOpen: false, 3 isSignUpOpen: false, 4 isForgotPasswordOpen: false, 5 6 actions: { 7 closeOptions: function(){ 8 this.setProperties({ 9 isSignInOpen: false, 10 isSignUpOpen: false, 11 isForgotPasswordOpen: false 12 }); 13 }, 14 openSignIn: function(){ this.closeOptions(); this.set('isSignUpOpen', true); }, 15 openSignUp: function(){ this.closeOptions(); this.set('isSignUpOpen', true); }, 16 openForgotPassword: function(){ this.closeOptions(); this.set('isForgotPasswordOpen', true); } 17 } 18 }); NAV & DISPLAYED PANEL TIGHTLY COUPLED panels cannot be controlled from other scopes Thursday, September 19, 13
  61. 61. LESS Clever Controller 2 1 <div> 2 <ul class="tabs"> 3 <li {{bind-attr class="isSignInOpen:active"}}{{action "openPanel" "signIn"}}>Sign In</li> 4 <li {{bind-attr class="isSignUpOpen:active"}}{{action "openPanel" "signUp"}}>Sign Up</li> 5 <li {{bind-attr class="isForgotPasswordOpen:active"}}{{action "openPanel" "forgotPassword"}}>Forgot Passw 6 </ul> 7 {{outlet "panel"}} 8 </div> 1 App.SessionRoute = Ember.Router.extend({ 2 setupController: function(){ 3 this._super.apply(this, arguments); 4 Em.run.once(this, function(){ 5 this.send('openPanel', 'signIn'); 6 }); 7 }, 8 actions: { 9 openPanel: function(panel){ 10 this.controller.set('openPanel', panel); 11 this.render('panels/'+panel, { 12 into: 'session', 13 outlet: 'panel' 14 }); 15 } 16 } 17 }); 18 19 App.SessionController = Ember.Controller.extend({ 20 isSignInOpen: Em.computed.equal('openPanel', 'signIn'), 21 isSignUpOpen: Em.computed.equal('openPanel', 'signUp'), 22 isForgotPasswordOpen: Em.computed.equal('openPanel', 'forgotPassword') 23 }); Thursday, September 19, 13
  62. 62. 1 {{#each controller}} 2 {{name}} 3 {{/each}} Clever Controller 3 1 App = Ember.Application.create(); 2 3 App.IndexRoute = Ember.Route.extend({ 4 model: function(){ 5 return Ember.A([ 6 { name: 'Munster' }, 7 { name: 'Alpine Lace' }, 8 { name: 'Tome' } 9 ]); 10 } 11 }); Thursday, September 19, 13
  63. 63. 1 {{#each controller}} 2 {{name}} 3 {{/each}} Clever Controller 3 1 App = Ember.Application.create(); 2 3 App.IndexRoute = Ember.Route.extend({ 4 model: function(){ 5 return Ember.A([ 6 { name: 'Munster' }, 7 { name: 'Alpine Lace' }, 8 { name: 'Tome' } 9 ]); 10 } 11 }); APPle invasion! apple invasion! apple invasion! Thursday, September 19, 13
  64. 64. 1 {{#each controller}} 2 {{name}} 3 {{/each}} Clever Controller 31 App = Ember.Application.create(); 2 3 App.IndexRoute = Ember.Route.extend({ 4 model: function(){ 5 return Ember.A([ 6 { name: 'Munster' }, 7 { name: 'Alpine Lace' }, 8 { name: 'Tome' } 9 ]); 10 } 11 }); 12 13 App.IndexController = Ember.ArrayController.extend({ 14 itemController: 'fruitInvasion' 15 }); 16 17 App.FruitInvasionController = Ember.ObjectController.extend({ 18 name: function(){ 19 return 'Apple invasion!'; 20 }.property() 21 }); APPle invasion! apple invasion! apple invasion! Changes context in the template from outside the template. Thursday, September 19, 13
  65. 65. 1 {{#each controller itemController='fruitInvasion'}} 2 {{name}} 3 {{/each}} LESS Clever Controller 3 1 App = Ember.Application.create(); 2 3 App.IndexRoute = Ember.Route.extend({ 4 model: function(){ 5 return Ember.A([ 6 { name: 'Munster' }, 7 { name: 'Alpine Lace' }, 8 { name: 'Tome' } 9 ]); 10 } 11 }); 12 13 App.FruitInvasionController = Ember.ObjectController.extend({ 14 name: function(){ 15 return 'Apple invasion!'; 16 }.property() 17 }); Thursday, September 19, 13
  66. 66. “TOO CLEVER IS DUMB” Ogden Nash Thursday, September 19, 13
  67. 67. TL;DR Thursday, September 19, 13
  68. 68. Don’t work so hard in controllers. Thursday, September 19, 13
  69. 69. Look to routes and templates for your application architecture. Thursday, September 19, 13
  70. 70. Thanks! @mixonic httP://madhatted.com matt.beale@madhatted.com Thursday, September 19, 13

×