Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Ember.js - More than Meets the Eye

tomdale
June 14, 2012

Ember.js - More than Meets the Eye

Many developers know that Ember.js is designed to solve the problems that every web developer faces. They might even be familiar with some of its most popular features, like computed properties, bindings, and observers. But the real power of Ember.js is in a set of conventions for common problems that other frameworks force you to decide for each new project.

In this talk, Tom will illustrate why having common conventions and vocabulary increases productivity and makes it easier for developers to collaborate. He'll also demonstrate some of the powerful new features in Ember.js that make managing complexity in large web applications easy—and even fun.

From the 2012 TXJS conference.

tomdale

June 14, 2012
Tweet

More Decks by tomdale

Other Decks in Technology

Transcript

  1. HAVE SIMPLE, EASY TO UNDERSTAND, AND WRONG ANSWERS. COMPLEX PROBLEMS

    Attributed to H.L. Mencken. But the internet is often a liar.
  2. “As soon as you build an interesting application in Backbone,

    one of the challenges you are likely to encounter is wanting to have composite views, or views that are contained within a larger view. I’ve solved this problem several ways in different projects…” http://blog.gaslightsoftware.com/post/24538291598/ backbone-js-views-done-the-right-way
  3. ▪ Maintains Hierarchy ▪ Parent View ▪ Child Views ▪

    Delegates Events ▪ Renders DOM Representation EMBER.VIEW
  4. ▪ Developer is responsible for rendering child views and inserting

    into parent element ▪ Developer is responsible for maintaining reference to child views SIMPLE SOLUTION
  5. Alex’s Texas-Style BBQ & Co-Working Space Hours: 8am - 11pm

    • Heart-Attack Burger • Semicolon Consultation AppView MenuView What happens if we want to change the close time?
  6. Alex’s Texas-Style BBQ & Co-Working Space Hours: 8am - 10pm

    • Heart-Attack Burger • Semicolon Consultation AppView MenuView
  7. Alex’s Texas-Style BBQ & Co-Working Space Hours: 8am - 10pm

    • Heart-Attack Burger • Semicolon Consultation AppView MenuView
  8. Alex’s Texas-Style BBQ & Co-Working Space Hours: 8am - 10pm

    • Heart-Attack Burger • Semicolon Consultation AppView MenuView
  9. ▪ Every connection is ad hoc ▪ The more child

    views, the more chances for making a mistake ▪ Lots of boilerplate ▪ Implementation must be perfect from top- most view to child-most view, or else: ▪ Weird rendering bugs ▪ Event listeners do not get torn down VIEW HIERARCHY
  10. ▪ Guarantees: ▪ One parent view (except root view) ▪

    Zero or more child views ▪ Re-rendering a view renders child views in correct location automatically ▪ Predictable across all Ember.js apps ▪ Views highly re-usable ADVANTAGES
  11. ▪ Automatic cleanup on removal ▪ Unbinds all bindings and

    observers registered by child views ▪ Unbinds all bindings and observers registered on child views ▪ Removes all internal references to eliminate many sources of leaks ▪ Lifecycle events ▪ didInsertElement, willRerender, willRemoveElement, willDestroy ADVANTAGES
  12. ▪ Routing is a state management problem ▪ The URL

    is just application state, serialized ▪ If we bring convention to state management, serializing and deserializing URLs becomes relatively trivial ROUTING Because we expect Ember developers to follow conventions, we can make assumptions about how apps are built, and bring new features “for free.”
  13. ▪ One object per conceptual application state ▪ States are

    modeled hierarchically ▪ Respond to events ▪ Can optionally transition to a different state STATES
  14. ▪ Impossible to be in two states at once ▪

    Events sent to a state that does not handle that event causes exception ▪ Failing fast is better than subtly broken behavior ▪ Requires you to reason about what events are valid in which states BENEFITS
  15. ▪ Log user’s movement throughout your app ▪ Transmit to

    server in case of error ▪ Isolates application logic from presentation ▪ Free routing! BENEFITS
  16. Ember.Router.extend({ root: Ember.State.extend({ posts: Ember.State.extend({ route: '/posts', index: Ember.State.extend({ route:

    '/', showPost: Ember.State.transitionTo('show'), connectOutlets: function(router, post) { router.get('applicationController') .connectOutlet(App.PostsView, App.Post.all()); } }), show: Ember.State.extend({ route: '/:post_id', connectOutlets: function(router, post) { router.get('applicationController') .connectOutlet(App.PostView, post); } }) }) }) }); ROUTER
  17. Ember.Router.extend({ root: Ember.State.extend({ posts: Ember.State.extend({ route: '/posts', index: Ember.State.extend({ route:

    '/', showPost: Ember.State.transitionTo('show'), connectOutlets: function(router, post) { router.get('applicationController') .connectOutlet(App.PostsView, App.Post.all()); } }), show: Ember.State.extend({ route: '/:post_id', connectOutlets: function(router, post) { router.get('applicationController') .connectOutlet(App.PostView, post); } }) }) }) }); ROUTER <ul> {{#each}} <li><a {{action showPost href=true}}> {{title}} </a></li> {{/each}} </ul>
  18. Ember.Router.extend({ root: Ember.State.extend({ posts: Ember.State.extend({ route: '/posts', index: Ember.State.extend({ route:

    '/', showPost: Ember.State.transitionTo('show'), connectOutlets: function(router, post) { router.get('applicationController') .connectOutlet(App.PostsView, App.Post.all()); } }), show: Ember.State.extend({ route: '/:post_id', connectOutlets: function(router, post) { router.get('applicationController') .connectOutlet(App.PostView, post); } }) }) }) }); ROUTER User clicks link
  19. Ember.Router.extend({ root: Ember.State.extend({ posts: Ember.State.extend({ route: '/posts', index: Ember.State.extend({ route:

    '/', showPost: Ember.State.transitionTo('show'), connectOutlets: function(router, post) { router.get('applicationController') .connectOutlet(App.PostsView, App.Post.all()); } }), show: Ember.State.extend({ route: '/:post_id', connectOutlets: function(router, post) { router.get('applicationController') .connectOutlet(App.PostView, post); } }) }) }) }); ROUTER Transition to show state, passing current post
  20. Ember.Router.extend({ root: Ember.State.extend({ posts: Ember.State.extend({ route: '/posts', index: Ember.State.extend({ route:

    '/', showPost: Ember.State.transitionTo('show'), connectOutlets: function(router, post) { router.get('applicationController') .connectOutlet(App.PostsView, App.Post.all()); } }), show: Ember.State.extend({ route: '/:post_id', connectOutlets: function(router, post) { router.get('applicationController') .connectOutlet(App.PostView, post); } }) }) }) }); ROUTER Serialize the post via post.get('id') and set URL to /posts/51
  21. Ember.Router.extend({ root: Ember.State.extend({ posts: Ember.State.extend({ route: '/posts', index: Ember.State.extend({ route:

    '/', showPost: Ember.State.transitionTo('show'), connectOutlets: function(router, post) { router.get('applicationController') .connectOutlet(App.PostsView, App.Post.all()); } }), show: Ember.State.extend({ route: '/:post_id', connectOutlets: function(router, post) { router.get('applicationController') .connectOutlet(App.PostView, post); } }) }) }) }); ROUTER Call connectOutlets with the post
  22. Ember.Router.extend({ root: Ember.State.extend({ posts: Ember.State.extend({ route: '/posts', index: Ember.State.extend({ route:

    '/', showPost: Ember.State.transitionTo('show'), connectOutlets: function(router, post) { router.get('applicationController') .connectOutlet(App.PostsView, App.Post.all()); } }), show: Ember.State.extend({ route: '/:post_id', connectOutlets: function(router, post) { router.get('applicationController') .connectOutlet(App.PostView, post); } }) }) }) }); ROUTER Create new App.PostView instance bound to postController (conventions!)
  23. Ember.Router.extend({ root: Ember.State.extend({ posts: Ember.State.extend({ route: '/posts', index: Ember.State.extend({ route:

    '/', showPost: Ember.State.transitionTo('show'), connectOutlets: function(router, post) { router.get('applicationController') .connectOutlet(App.PostsView, App.Post.all()); } }), show: Ember.State.extend({ route: '/:post_id', connectOutlets: function(router, post) { router.get('applicationController') .connectOutlet(App.PostView, post); } }) }) }) }); ROUTER Replace main outlet with the new view
  24. Ember.Router.extend({ root: Ember.State.extend({ posts: Ember.State.extend({ route: '/posts', index: Ember.State.extend({ route:

    '/', showPost: Ember.State.transitionTo('show'), connectOutlets: function(router, post) { router.get('applicationController') .connectOutlet(App.PostsView, App.Post.all()); } }), show: Ember.State.extend({ route: '/:post_id', connectOutlets: function(router, post) { router.get('applicationController') .connectOutlet(App.PostView, post); } }) }) }) }); ROUTER User enters at /posts/51
  25. Ember.Router.extend({ root: Ember.State.extend({ posts: Ember.State.extend({ route: '/posts', index: Ember.State.extend({ route:

    '/', showPost: Ember.State.transitionTo('show'), connectOutlets: function(router, post) { router.get('applicationController') .connectOutlet(App.PostsView, App.Post.all()); } }), show: Ember.State.extend({ route: '/:post_id', connectOutlets: function(router, post) { router.get('applicationController') .connectOutlet(App.PostView, post); } }) }) }) }); ROUTER Router transitions to root.posts.show
  26. Ember.Router.extend({ root: Ember.State.extend({ posts: Ember.State.extend({ route: '/posts', index: Ember.State.extend({ route:

    '/', showPost: Ember.State.transitionTo('show'), connectOutlets: function(router, post) { router.get('applicationController') .connectOutlet(App.PostsView, App.Post.all()); } }), show: Ember.State.extend({ route: '/:post_id', connectOutlets: function(router, post) { router.get('applicationController') .connectOutlet(App.PostView, post); } }) }) }) }); ROUTER Router nds post = Post.find(51)
  27. Ember.Router.extend({ root: Ember.State.extend({ posts: Ember.State.extend({ route: '/posts', index: Ember.State.extend({ route:

    '/', showPost: Ember.State.transitionTo('show'), connectOutlets: function(router, post) { router.get('applicationController') .connectOutlet(App.PostsView, App.Post.all()); } }), show: Ember.State.extend({ route: '/:post_id', connectOutlets: function(router, post) { router.get('applicationController') .connectOutlet(App.PostView, post); } }) }) }) }); ROUTER Router calls connectOutlets, and behavior continues as before
  28. Controller View Router ▪ Stateless ▪ Responds to events from

    view ▪ Transitions to new states ▪ Saves state on controllers ▪ Very little logic ▪ Container for information needed to render bound views ▪ One or more views bound to one controller ▪ Turns primitive (click) events into semantic events (showPost) ▪ Sends events to router ▪ Renders representation of bound controller