Slide 1

Slide 1 text

More than Meets the Eye EMBER.JS

Slide 2

Slide 2 text

COMPLEX PROBLEMS

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

HAVE SIMPLE, EASY TO UNDERSTAND, AND WRONG ANSWERS. COMPLEX PROBLEMS Attributed to H.L. Mencken. But the internet is often a liar.

Slide 6

Slide 6 text

CORRECTNESS EASE OF USE PRODUCTIVITY > FILE SIZE

Slide 7

Slide 7 text

CORRECTNESS EASE OF USE PRODUCTIVITY > FILE SIZE Important More Important

Slide 8

Slide 8 text

No content

Slide 9

Slide 9 text

0 2250 4500 6750 9000 Backbone.js Ember.js+Ember Data Lines of Code

Slide 10

Slide 10 text

0 2250 4500 6750 9000 Backbone.js Ember.js+Ember Data Lines of Code What’s here?

Slide 11

Slide 11 text

No content

Slide 12

Slide 12 text

FEATURES SYNTACTIC SUGAR ABSTRACTIONS CONVENTION OVER CONFIGURATION

Slide 13

Slide 13 text

CONVENTIONS

Slide 14

Slide 14 text

WHY ARE CONVENTIONS GOOD?

Slide 15

Slide 15 text

ELIMINATE TRIVIAL CHOICES

Slide 16

Slide 16 text

REDUCE CODE

Slide 17

Slide 17 text

INCREASE PREDICTABILITY

Slide 18

Slide 18 text

“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

Slide 19

Slide 19 text

VIEW HIERARCHY

Slide 20

Slide 20 text

■ Maintains Hierarchy ■ Parent View ■ Child Views ■ Delegates Events ■ Renders DOM Representation EMBER.VIEW

Slide 21

Slide 21 text

■ Composable ■ Reusable EMBER.VIEW

Slide 22

Slide 22 text

■ Developer is responsible for rendering child views and inserting into parent element ■ Developer is responsible for maintaining reference to child views SIMPLE SOLUTION

Slide 23

Slide 23 text

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?

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

■ 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

Slide 28

Slide 28 text

■ 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

Slide 29

Slide 29 text

■ 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

Slide 30

Slide 30 text

THIS IS A COMMON PROBLEM, SO LET’S SOLVE IT COMPLETELY.

Slide 31

Slide 31 text

ROUTING

Slide 32

Slide 32 text

ROUTING IS NOT REALLY A ROUTING PROBLEM.

Slide 33

Slide 33 text

■ 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.”

Slide 34

Slide 34 text

HOW DO WE BRING CONVENTION TO STATE MANAGEMENT?

Slide 35

Slide 35 text

STATE CHARTS

Slide 36

Slide 36 text

AD-HOC STATE VS. EXPLICIT STATE OBJECTS

Slide 37

Slide 37 text

■ One object per conceptual application state ■ States are modeled hierarchically ■ Respond to events ■ Can optionally transition to a different state STATES

Slide 38

Slide 38 text

■ 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

Slide 39

Slide 39 text

■ Log user’s movement throughout your app ■ Transmit to server in case of error ■ Isolates application logic from presentation ■ Free routing! BENEFITS

Slide 40

Slide 40 text

application.handlebars

My Blog

{{outlet}} OUTLETS

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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!)

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

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)

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

Model Controller View

Slide 54

Slide 54 text

Model Controller View Router

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

CONVENTIONS

Slide 57

Slide 57 text

ELIMINATE TRIVIAL CHOICES

Slide 58

Slide 58 text

REDUCE CODE

Slide 59

Slide 59 text

INCREASE PREDICTABILITY

Slide 60

Slide 60 text

@TOMDALE THANK YOU