More Related Content Similar to Complex Architectures in Ember (20) More from Matthew Beale (9) Complex Architectures in Ember9. 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
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. 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. 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. 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. 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. 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. When we pick between these
options...
Thursday, September 19, 13
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
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. 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. 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. 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. 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
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. 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. 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. 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. 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. 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
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. 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. 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. 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. 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
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. 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. โข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. 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. 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. When confused about an app, look
to the templates and the routes ๏ฌrst.
Thursday, September 19, 13
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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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
69. Look to routes and templates for
your application architecture.
Thursday, September 19, 13