Slide 1

Slide 1 text

And why should you care? WHAT IS EMBER?

Slide 2

Slide 2 text

■ A standard library for JavaScript ■ Why Ember's bindings are special ■ The view hierarchy ■ Structure and Widgets ■ Routing in Ember 1.0 ■ Managing external data ■ Bringing it all together with a demo EXPECTATIONS.

Slide 3

Slide 3 text

A STANDARD LIBRARY

Slide 4

Slide 4 text

JavaScript Language Standard Library (Date, RegExp, etc.) DOM, HTML5, WebGL, etc. jQuery

Slide 5

Slide 5 text

JavaScript Language Standard Library (Date, RegExp, etc.) DOM, HTML5, WebGL, etc. jQuery Underscore

Slide 6

Slide 6 text

No content

Slide 7

Slide 7 text

Ember.Object Events and Observability Inheritance: Mixins and super Getters and Setters

Slide 8

Slide 8 text

Ember.Object Events and Observability Inheritance: Mixins and super Getters and Setters Proxies (Object and Array)

Slide 9

Slide 9 text

Ember.Object Events and Observability Inheritance: Mixins and super Getters and Setters Proxies (Object and Array) Enumerables Map and Set

Slide 10

Slide 10 text

Ember.Object Events and Observability Inheritance: Mixins and super Getters and Setters Proxies (Object and Array) Enumerables Map and Set Array-Likes Enumerable + Indexes (#each)

Slide 11

Slide 11 text

EMBER-METAL. EMBER-RUNTIME.

Slide 12

Slide 12 text

EMBER'S BINDINGS

Slide 13

Slide 13 text

BAKED INTO THE OBJECT MODEL.

Slide 14

Slide 14 text

Person = Ember.Object.extend({ fullName: function() { return this.get('firstName') + ' ' + this.get('lastName'); }.property('firstName', 'lastName') }); BINDINGS.

Slide 15

Slide 15 text

Person = Ember.Object.extend({ fullName: function() { return this.get('firstName') + ' ' + this.get('lastName'); }.property('firstName', 'lastName') }); leah = Person.create({ firstName: "Leah", lastName: "Silber" }); BINDINGS.

Slide 16

Slide 16 text

leah = Person.create({ firstName: "Leah", lastName: "Silber" }); yehuda = Person.create({ firstName: "Yehuda", lastName: "Katz", spouse: leah, spouseNameBinding: 'spouse.fullName' }); BINDINGS.

Slide 17

Slide 17 text

leah = Person.create({ firstName: "Leah", lastName: "Silber" }); yehuda = Person.create({ firstName: "Yehuda", lastName: "Katz", spouse: leah, spouseNameBinding: 'spouse.fullName' }); leah.set('lastName', "Silber-Katz"); yehuda.get('spouseName'); // "Leah Silber-Katz" BINDINGS.

Slide 18

Slide 18 text

ALWAYS ASYNC.

Slide 19

Slide 19 text

ALWAYS ASYNC. WHY?

Slide 20

Slide 20 text

SIDE-EFFECTS. It guarantees you the ability to make all necessary changes before side-effects occur.

Slide 21

Slide 21 text

yehuda.addObserver('spouseName', function() { $("#me div.spouse span.name") .html(yehuda.get('spouseName')); }); BASIC EXAMPLE. spouseName changes recalculate the new name update the

Slide 22

Slide 22 text

yehuda.addObserver('spouseName', function() { $("#me div.spouse span.name") .html(yehuda.get('spouseName')); }); // later leah.set('firstName', "Léah"); leah.set('lastName', "Silber-Katz"); BASIC EXAMPLE.

Slide 23

Slide 23 text

leah.set('firstName', "Léah")

Slide 24

Slide 24 text

yehuda.spouseName changes recalculate the name update the leah. rstName changes leah.fullName changes leah.set('firstName', "Léah")

Slide 25

Slide 25 text

yehuda.spouseName changes recalculate the name update the leah. rstName changes leah.fullName changes leah.set('firstName', "Léah") leah.set('lastName', "Silber-Katz")

Slide 26

Slide 26 text

yehuda.spouseName changes recalculate the name update the leah. rstName changes leah.fullName changes yehuda.spouseName changes recalculate the name update the leah.lastName changes leah.fullName changes leah.set('firstName', "Léah") leah.set('lastName', "Silber-Katz")

Slide 27

Slide 27 text

leah.set('firstName', "Léah"); // is this even a valid state? leah.set('lastName', "Silber-Katz"); IN-BETWEEN STATE.

Slide 28

Slide 28 text

REPEATED WORK. recalculate the name update the recalculate the name update the

Slide 29

Slide 29 text

REPEATED WORK. recalculate the name update the recalculate the name update the 2x

Slide 30

Slide 30 text

initialize: function() { this.model.on('change', this.render, this); }, render: function() { this.$el.html(this.model.fullName()); } SAME THING.

Slide 31

Slide 31 text

leah.set({ firstName: "Léah", lastName: "Silber-Katz" }); DO ONE SET? This solves this problem, but it's not always possible. The more you use events, the harder it is to enforce atomic changes.

Slide 32

Slide 32 text

EMBER? leah.set('firstName', "Léah")

Slide 33

Slide 33 text

EMBER? leah. rstName changes leah.set('firstName', "Léah")

Slide 34

Slide 34 text

EMBER? leah. rstName changes leah.set('firstName', "Léah") leah.set('lastName', "Silber-Katz")

Slide 35

Slide 35 text

EMBER? leah. rstName changes leah.lastName changes leah.set('firstName', "Léah") leah.set('lastName', "Silber-Katz")

Slide 36

Slide 36 text

EMBER? leah. rstName changes yehuda.spouseName changes recalculate the name update the leah.lastName changes leah.fullName changes leah.set('firstName', "Léah") leah.set('lastName', "Silber-Katz") later...

Slide 37

Slide 37 text

COLLECTIONS.

Slide 38

Slide 38 text

App.StatisticsView = Backbone.View.create({ initialize: function() { this.collection.on('all', this.updateRemaining, this); this.collection.on('all', this.updateTotal, this); } render: function() { var list = this.collection; var remaining = "" + list.remaining() + " remaining"; var total = " and " + list.done() + " total"; this.$el.html("

" + remaining + done + "

"); }, updateRemaining: function() { this.$el.find("span.remaining").html(this.collection.remaining()); }, updateTotal: function() { this.$el.find("span.done").html(this.collection.done()); } }); App.TodoCollection = Backbone.Collection.extend({ remaining: function() { return this.filter(function() { return this.get('isDone') === false; }).length; }, done: function() { return this.length - this.remaining(); } }); COLLECTION VIEW.

Slide 39

Slide 39 text

collection.forEach(function(todo) { todo.set('isDone', true); }); MULTIPLE SETS.

Slide 40

Slide 40 text

REPEATED WORK.

Slide 41

Slide 41 text

REPEATED WORK. recalculate remaining update the recalculate total update the recalculate remaining update the recalculate total update the recalculate remaining update the recalculate total update the recalculate remaining update the recalculate total update the Nx

Slide 42

Slide 42 text

EMBER.

Slide 43

Slide 43 text

statistics.handlebars

{{remaining}} remaining and {{done}} done

TEMPLATE.

Slide 44

Slide 44 text

App.TodosController = Ember.ArrayController.extend({ remaining: function() { return this .filterProperty('isDone', false) .get('length'); }.property('@each.isDone'), done: function() { return this.get('length') - this.get('remaining'); }.property('@each.isDone') }); CONTROLLER. An Ember ArrayController serves a similar purpose to a Backbone Collection

Slide 45

Slide 45 text

{{template "statistics"}}
TODOS TEMPLATE.

Slide 46

Slide 46 text

■ Automatic render method ■ Always reuses compiled template ■ Makes the current controller the context ■ Sets up observers on remaining and done properties on the controller ■ Guarantees a single DOM update per turn in the browser's event loop ■ Manages the view hierarchy... STATS TEMPLATE.

Slide 47

Slide 47 text

VIEW HIERARCHY

Slide 48

Slide 48 text

    AD-HOC. AppView TodoView TodoView TodoView
  • TodoView
  • TodoView
  • TodoView
  • Views DOM

Slide 49

Slide 49 text

YOU END UP WANTING AN ENFORCED HIERARCHY.

Slide 50

Slide 50 text

Support.CompositeView = function(options) { this.children = _([]); Backbone.View.apply(this, [options]); }; _.extend(Support.CompositeView.prototype, Backbone.View.prototype, { leave: function() { this.unbind(); this.remove(); this._leaveChildren(); this._removeFromParent(); }, renderChild: function(view) { view.render(); this.children.push(view); view.parent = this; }, appendChild: function(view) { this.renderChild(view); $(this.el).append(view.el); }, renderChildInto: function(view, cont) { this.renderChild(view); $(cont).empty().append(view.el); }, _leaveChildren: function() { this.children.chain().clone() .each(function(view) { if (view.leave) view.leave(); }); }, _removeFromParent: function() { if (this.parent) this.parent._removeChild(this); }, _removeChild: function(view) { var index = this.children.indexOf(view); this.children.splice(index, 1); } }); Support.CompositeView.extend = Backbone.View.extend; COMPOSITEVIEW.

Slide 51

Slide 51 text

JUST THERE.

Slide 52

Slide 52 text

■ Cleanup on removal ■ Unbinding all bindings and observers registered by child views ■ Unbinding all bindings and observers registered on child views ■ Remove all internal references to eliminate many sources of leaks ■ Lifecycle events: willInsertElement, didInsertElement, willRerender, willRemoveElement, willDestroy WHAT IT BUYS YOU.

Slide 53

Slide 53 text

ELIMINATION OF BUSYWORK.

Slide 54

Slide 54 text

{{category}}

    {{#each}}
  • {{name}} More details

    {{#unless readOnly}} {{view App.EditPerson}} {{/unless}}
  • {{/each}}
EXAMPLE TEMPLATE.

Slide 55

Slide 55 text

{{category}}

    {{#each}}
  • {{name}} More details

    {{#unless readOnly}} {{view App.EditPerson}} {{/unless}}
  • {{/each}}
EXAMPLE TEMPLATE. append controller.category and register observer on controller.category

Slide 56

Slide 56 text

{{category}}

    {{#each}}
  • {{name}} More details

    {{#unless readOnly}} {{view App.EditPerson}} {{/unless}}
  • {{/each}}
EXAMPLE TEMPLATE. iterate over people array changes rerender item added add to DOM item removed remove from DOM

Slide 57

Slide 57 text

{{category}}

    {{#each}}
  • {{name}} More details

    {{#unless readOnly}} {{view App.EditPerson}} {{/unless}}
  • {{/each}}
EXAMPLE TEMPLATE. people can be any object that mixes in Ember.Array

Slide 58

Slide 58 text

{{category}}

    {{#each}}
  • {{name}} More details

    {{#unless readOnly}} {{view App.EditPerson}} {{/unless}}
  • {{/each}}
EXAMPLE TEMPLATE. append person.name and register observer on person.name

Slide 59

Slide 59 text

{{category}}

    {{#each}}
  • {{name}} More details

    {{#unless readOnly}} {{view App.EditPerson}} {{/unless}}
  • {{/each}}
EXAMPLE TEMPLATE. when the link is clicked, trigger the showPerson event on the router also generate a URL

Slide 60

Slide 60 text

{{category}}

    {{#each}}
  • {{name}} More details

    {{#unless readOnly}} {{view App.EditPerson}} {{/unless}}
  • {{/each}}
EXAMPLE TEMPLATE. only render this area if person.readOnly is false. if that changes add or remove as appropriate

Slide 61

Slide 61 text

{{category}}

    {{#each}}
  • {{name}} More details

    {{#unless readOnly}} {{view App.EditPerson}} {{/unless}}
  • {{/each}}
EXAMPLE TEMPLATE. insert an App.EditPerson child view its context property will be the current person

Slide 62

Slide 62 text

RESULT. App.ApplicationView {{controller.category}} {{#each}} {{person.name}} {{#unless person.readOnly}} App.EditPersonView

Slide 63

Slide 63 text

{{unbound category}}

    {{#each}}
  • {{name}} More details

    {{#unless readOnly}} {{view App.EditPerson}} {{/unless}}
  • {{/each}}
MORE CONTROL.

Slide 64

Slide 64 text

{{category}}

    {{#each group=true}}
  • {{name}} More details

    {{#unless readOnly}} {{view App.EditPerson}} {{/unless}}
  • {{/each}}
SOON.

Slide 65

Slide 65 text

DOES THE RIGHT THING BY DEFAULT.

Slide 66

Slide 66 text

ROUTING

Slide 67

Slide 67 text

STATE MANAGEMENT.

Slide 68

Slide 68 text

App.PostsController = Ember.ArrayController.extend(); App.PostController = Ember.ObjectController.extend(); App.PostsView = Ember.View.extend(); App.PostView = Ember.View.extend(); OBJECTS.

Slide 69

Slide 69 text

application.handlebars

My Blog

{{outlet}} posts.handlebars post.handlebars

{{title}}

{{intro}}

{{body}}
TEMPLATES.

Slide 70

Slide 70 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 71

Slide 71 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 72

Slide 72 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. 1. User clicks link 2. Transition to show state, passing current post 3. Serialize the post via .get('id'), set URL /posts/51 4. Call connectOutlets with the post 5. Replace the main outlet with the PostView, with its controller set to PostController with content of post

Slide 73

Slide 73 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. 1. User enters at /posts/51 2. The router transitions into root.posts.show 3. The router nds post = App.Post.find(51) 4. The router calls connectOutlets with post 5. Same as before

Slide 74

Slide 74 text

STATE MANAGER GOODIES.

Slide 75

Slide 75 text

LOGGING.

Slide 76

Slide 76 text

DEBUGGING PRODUCTION APPS.

Slide 77

Slide 77 text

AVOID IMPOSSIBLE CASES.

Slide 78

Slide 78 text

DEMO.