Writing Big (ambitious) Apps Using
Just Got Easier

Oren Rubin


Applitools                         Director of R&D

Wix                                      Web Architect

IBM                                      Distributed Computing

Spring Diagnostics         Machine Learning

Cadence                             Compiler Engineer

Ember history


Started by Apple, for Mobile as SpoutCore (2008) 

Refactored to MVC as SproutCore II (early 2011)

SproutCore UI (July 2012)

Renamed Amber (Dec 2011)

Renamed Ember  (few days later)

Yehuda Katz and Tom Dale opened Tilde (2012)

Ember currently


Many active developers

1.0 comprising new features. currently RC1.0

Documentation rewritten

Why ember?


Which of the following is different from the others?

1) A Framework's performance.

2) A Framework's preexisting components. 

3) A Framework's syntax.

4) Philip's pizza

why ember?

Which of the following is different from the others?

1) A Framework's Performance.

2) A Framework's Pre-existing Components. 

3) A Framework's syntax.

4) Philip's Pizza

Answer: 3

Cause I really think 1, 2, and 4 make me code faster!

Why ember?

How to judge an MVC framework?

  • Fast development
        - Existing components (e.g. Router, Model, Views)
        - Integrating 3rd party code (e.g. jQuery plugins)
        - Convention Over Configuration (with overrides)

  • Performance, optimizations for DOM updates

  • Debugging tools

  • Useful helpers (Array's filterProperty, Em.K, etc.)



What is a typical Ember Application?



AKA: What will We talk about?





Router 


State according to the URL

Loads Model objects 


Models


Your Domain Specific Data 


(serialized and saved in some DB)


Served to the Controllers


CONTROLLERS


The Business Logic

Take some Model object

And show it using Views


Views


Business Logic

to

UI Interaction 


Generates HTML using Templates


Templates



Responsible for low level UI


Both appearance and events




EMBER CLASSES 


Comprises the Magic 

Bird View







classes

Hello Web


<html>
  <head></head>
  <body>

     Hello Web

  </body>
</html>    
=
Hello Web

hello ember

    <body>
        <script src="jquery.js"/>
        <script src="handlebars.js"/>
        <script src="ember.js"/>

        <script>
            var App = Ember.Application.create();
        </script>

        <script type="text/x-handlebars">
            Hello Ember!
        </script>
    <body>
=
Hello Ember!


Tip: use zepto for mobile

Binding


   var App = Ember.Application.create({             
       name: "Ember App!"
   })                  

+

<script type="text/x-handlebars"> Hello {{App.name}} </script>
=
Hello Ember!


Tip: naming convention #1: uppercase means global (App)

Classes & instances



 var MyClass = Ember.Object.extend({
                    
     x: 5,
     bestMethodEva: function() {
        return this._super() + ":" + this.get('x')
     }
 })


 var myInstance = MyClass.create()

                
 console.log(myInstance.get('x'))

 console.log( myInstance.bestMethodEva() );
    

Tip: naming convention #2: An application extends Namespace, which is why it's uppercase

CLASSES & INSTANCES


    var obj = Ember.Object.create({    
       a: { 
          b: {
            c: 5
          }
       }
    })
    
    var c = obj.get('a.b.c');  // getPath in ember < 1.0
    
    if (c) {...} // c is undefined if 'a' or 'b' are falsy

    // never again!
    if (obj.a && obj.a.b && obj.a.b.c) {
       ...
    }

    
          




              

array


    App.TodosController = Ember.Object.extend({
        todos: []
    })

    App.todosController = App.TodosController.create()
+
    {{App.todosController.todos.length}} TODOs
=
                           0 TODOs

Array

    setTimeout(function(){
        var newTodo = Ember.Object.create({
            description: "best todo ever"
        })
        var todos = App.todosController.get('todos')
        todos.pushObject(newTodo)

        // console.log(todos.get('lastObject'))
    }, 5000)
+
        {{App.todosController.todos.length}} TODOs
=
1 TODOs

Binding

 
  App.TodosController = Ember.Object.extend({
      todos: [],
      appNameBinding: 'App.name'

      // appName field will be added "in-memory", and updated
  }) 
    
use with either
                {{App.todosController.appName}}
or
{{App.name}}




Tip: Ember generates objects in-memory, rather than files

computed properties

  App.TodosController = Ember.Object.extend({
      appNameBinding: 'App.name',

      todosName: function() {
          return 'todos controller in ' + this.get('appName') 
      }.property('appName')

  })
+
    {{App.todosController.todosName}}    
=
               todos controller in Ember App!            



Tip: Add console.log/alert to check it only runs once.

computed properties

    App.TodosController = Ember.Object.extend({
        todos: [],
    
        remaining: function() {
            var todos = this.get('todos');
            var completed = todos.filterProperty('isDone');
            return todos.length - completed.length;
        }.property('todos.@each.isDone')
    
    });
+
        {{App.todosController.remaining}} remaining 
=
2 remaining


Tip: 'remaining' field  will be recalculated only if a todo's isDone is changed, todo is added/removed, or the entire array replaced.

but there's more!


Observers


  App.TodosController = Ember.Object.extend({
      selectedTodo: undefined,

      todosChanged: function() {
          var selected = this.get('selectedTodo')

          if (!todos.contains(selected)) {
             this.set('selected', undefined)
          }
          
      }.observes('todos.@each') //.beforeObserver()

  })  



Tip: can be notified for only removal

Riddle

   
   var MyClass = Ember.Object.Extend({
        people: [],
    
        add: function(name) {
            var people = this.get('people')
            people.push(name)
           console.log(people)
        }    
   });

   var obj1 = MyClass.create()

   var obj2 = MyClass.create()

   obj1.add('Ran Tavory')
   obj2.add('Ori Lahav')
       
   



Tip: javascript uses prototype inheritance





Templates

Handlebars

HTML
  <div>
     {{name}} is fastest programmer eva!
  </div>
Handlebars
  <div>
     <script id="metamorph-3-start" type="text/placeholder"/>
     Itay Maman
     <script id="metamorph-3-end" type="text/placeholder"/>
     is fastest programmer eva!
  </div>
Humans
           Itay Maman is fastest programmer eva!

Handlebars


 Loops in HTML

  <ul id="todo-list">
    <li>
        Take over the universe
    </li>
            
    <li>
        Have the penne arrabiata (without a tray)
    </li>
  </ul>      

handlebars


Loops in Ember

  <ul id="todo-list">
      {{#each todo in App.todosController.todos}}
          <li>
              {{todo.description}}
          </li>
      {{/each}}
  </ul>

Logic-less



* Doesn't mean no {{if}}


* Does mean no {{if X > Y +5}}  


Think CSS!


Logic-less

In CSS, all states in one place. 

section {
    background: rgba(0,0,0, 0.5);
}

section:hover {
    background: #BADA55;
}    

Same in ember

{{#if todos.isLoaded}}
    <div> spinner </div>
{{#else}}
    <ul>
       <!-- some list -->
    </ul> 
{{end}}



Tip: using ember-data, you'll get the 'isLoaded' for free!

Templates


Compiled to javascript

Client side (run-type)

       Using <script>  code </script>

       Using Em.Handlebars.compile(code)

Server side
    
       Saves traffic and compilation

Template is a template


* Anonymous templates are instantiated at the same
   place (so does a template named application).

* Named templates are instantiated by Ember with 
   some context (javascript class).
<script type="text/x-handlebars" template-name="todo">
   todo description: {{description}}
</script>    
* Comprise special command called "helpers"
{{helperName}} or {{helperName X}} or {{helperName X=Y A=B}}

Tip: You can write custom helpers



Views

Views


  Business Logic  <=> UI/UX

     
      => Render HTML Elements


     <= Handle DOM Events

   

View


* A View is an object of class Ember.View (or extends)

* Instantiated in 2 way
     code: 
MyView.create().append('#container')
     In a template
{{view path.to.class}}
* Has a template

* Context in template is the View's instance


View

Definitions
App.TodoView = Ember.View.extend({
    templateName: 'todo',
    description: "best TODO eva!"
});    
<script type="text/x-handlebars" template-name="todo">
   todo description: {{description}}
</script>    
+
Instantiation
                    {{view App.TodoView}}
=
todo description: best TODO eva!

handle appearance


App.TodoView = Ember.View.create({
    todo: {..},
    a: false,

    f: function() {  
        if(sky.contains('sun') { 
           this.set('a', true)
        }
    }

    isSelected: {
        var selectedTodods = App.todoController.selected
        return selected Todos.contains( this.get('todo') )
    }.property('App.todosController.selected'),

});    

handle appearance

<div {{bindAttr class="isSelected"}}>
    <div {{bindAttr class="a:a-is-true:a-is-false"}}/>
</div>    
=
<div class="is-selected">
   <div class="a-is-false">
   </div>
 </div>    
or
<div>  
   <div class="a-is-true">
   </div>
 </div>
2 more options, depending on the weather
(and if the todo is selected)

handle appearance

In reality
<div class="is-selected" bind-attr-id="18">  
   <div class="a-is-false" bind-attr-id="53">
   </div>
</div>    
or
<div bind-attr-id="18">  
   <div class="a-is-true" bind-attr-id="53">
   </div>
</div>




Top Secret: Ember uses "bind-attr-id" to find what should be updated

HANDLE DOM EVENT

     
 Your template is always wrapped by an element

  <div ember-id="18">
<div>
    <div class="inner">
        {{todo.description}}
    </div>
</div>    
  </div>




Tip: choose tag using "tagName" field

Handle DOM Events

     
Wrapping-Element Events


  App.TodoView = Ember.View.create({
      templateName: 'todo',
      description: "best TODO eva!",

      click: function() {       
         alert('someone clicked inside somewhere')
      }
  });
    




HANDLE DOM EVENTs

  
 Built-in supported events



HANDLE DOM EVENTs


  Ember uses jQuery, so add custom events


 var App = Em.Application.create({
      customEvents: {
          // jquery-event-name: method-name-in-view

          mousewheel: 'mouseWheel'
      }
  });
   




Top  Secret: Ember captures the events on root

HANDLE DOM EVENT

     
 Template parts: use {{action}} helper

  <div>
<div>
    <div class="inner" {{action methodName on="click"}}>
        {{todo.description}}
    </div>
</div>    
  </div>
                          


Tip: click is default, remove redundant code (on="click")

Handle dom event


Add it yourself (jQuery) when View is in the DOM

App.TodoView = Ember.View.create({
    templateName: 'todo',

    didInsertElement: function() {
       var wrappingElement = this.$()
       var elementInside = this.$('.inner')
       elementInside.click(function(){
           alert('yay')
       })
    })
)}        



Tip: perfect for jQuery plugins!

life cycle



life cycle

No more pain like:
destroy: function () {
  var c = this;
  this.unbind();
  try {
     var elem = this._element
     elem.pause(),
     elem.removeEvent("error", this._triggerError),
     elem.removeEvent("ended", this._triggerEnd),
     elem.removeEvent("canplay", this._triggerReady),
     elem.removeEvent("loadedmetadata",this._onLoadedMetadata)
     _.each(b, function (a) {
        c._element.removeEvent(a, c._bubbleProfilingEvent)
     }),
     _.each(a, function (a) {
        c._element.removeEventListener(a, c._logEvent)
     }),
     elem = null,
     this.trigger("destroy")
  } catch (d) {}
}
    

Real code.. google it!

life cycle



Life cycle


Views are added and removed all the time!


{{#if todo.empty}} <div> no todos.. call luke </div> {{#else}} <ul> <--loop and print them--> </ul> {{end}}







SAy what?!


Life cycle


c'tor is for initialization, not DOM

App.TodoView = Ember.View.create({
    templateName: 'todo',

    init: function() {
       // constructor
       console.log( this.get('state') ); // "preRender"
    });
});
        

Views


Field members are set in the Class or per usage

In Class:
App.TodoView = Ember.View.extend({
    tagName: 'li',
    classNames: [a, b]
});
Per usage:
{{view TodoView appNameBinding="App.name" classNames="a b"}}
var view App.TodoView.create({
   classNames: "a b"
})

debugging Views

     
     * Reference in console (Em.Views.view[<id>])

     * debugger and break on uncaught exceptions

     * Set Em.LOG_BINDING to true

     * Redefine (reopen) $ in Em.View

     * Chrome dev tool plugin (sets to _V)

Inline template


Use one specified in Class
   {{view DeathStar}} 

Override template and use inline

   {{#view DeathStar}}
        Don't use DeathStar's template, use the one of
        rebel alliance
   {{/view}}
    





Tip: inner components are specified in the template. Great for unit tests isolation.

Views


Can be seen as Components*:

- Todo  (folder)
        - TodoView.js
        - TodoView.Test.js
        - TodoView.Template.handlerbars
        - TodoView.scss
        - TodoView.css


Top Secret: *Personal recommendation

Views - summary

* Data is always synced (binding)

* Appearance is in template 
                       (no more addClass, removeClass)

* Binding Actions is in the template

 
                    View is left with Business Logic!



Tip:  perfect for Unit Testing!



Controllers

Controllers


Philosophy:  They are the logic divider in the app.
                        Structure of app should be known

Example:
     *  One controller responsible for list of emails.

     *  Another controller responsible a specific email.

      * An App can have either one, or either both in 
         same page

Controllers

Implementation: 

      Ember Controllers are proxies, delegating stuff

      Ember provides 2 types of controllers:           
            - ObjectController  -  simple

            - ArrayController    -  can be iterated upon
                                                using {{#each controller}} 

Both search in the next order
 
          model     ->     content     ->       this   




Controllers

Controllers live forever (Views come and go).
      Save session state (e.g., selected-todos)

Have views too, so as your application!

SoundController
<script type="x-handlebars" data-template-name="sound">
    <p>
         <strong>Song</strong>: {{name}} by {{artist}}
    </p>
    <p>
         <strong>Duration</strong>: {{duration}}
    </p>
 </script>

Top Secret: named and artist are proxied from the model



models 

models


Run by a DataStore under the namespace DS
    
App.Store = DS.Store.extend({
    revision: 11, // let you know if breaking changes happen.

    adapter: 'DS.RESTAdapter', // built-in REST adapter

 // adapter: 'DS.FixtureAdapter' // run solo
 // adapter: 'App.MyCustomAdapter' // custom
});

Support for transactions

models


RESTAdapter supports primitives:
        number, string, boolean and dates
var attr = DS.attr;

App.Person = DS.Model.extend({
    firstName: attr('string'),
    lastName: attr('string'),
    birthday: attr('date'),

    fullName: function() {
      return this.get('firstName') +' '+ this.get('lastName');
    }.property('firstName', 'lastName')
});


Tip: Computed properties won't be serialized

fixtures


Use App.<model-name>.FIXTURES

App.Person.FIXTURES = [
   {
     id: 1,  // this is a MUST
     firstName: 'Ferris',
     lastName, 'Bueller'
   },
   {
     id: 2,
     firsName: 'Moshe',
     lastName: 'Oofnik',
   }
]    


Relationship


One to one
var attr = DS.attr;
App.User = DS.Model.extend({
  profile: DS.belongsTo('App.Profile')
}); 
One to Many
App.Post = DS.Model.extend({
  comments: DS.hasMany('App.Comment')
}); 
Many to Many
App.BlogPost = DS.Model.extend({
  tags: DS.hasMany('App.Tag')
});   

models


Use the find() method
  var posts  = App.Post.find();
  var post   = App.Post.find(1);
  var people = App.Person.find({ name: "Peter"});
Extend Object with important properties

    person.get('isDirty');
    //=> false

    person.set('isAdmin', true);

    person.get('isDirty');
    //=> true
    
Also: isLoaded, isSaving, isDeleted, isNew, isValid ...

Model

Supports side-loading (fewer HTTP requests)
{
    "post": {
        "id": 1,
        "title": "Rails is omakase",
        "comments": [1, 2, 3]
    },

    "comments": [{
        "id": 1,
        "body": "But is it _lightweight_ omakase?"
    },
    {
        "id": 2,
        "body": "I for one welcome our new omakase overlords"
    }]
}



router 

Router

Manages state

Comprises Route handlers

Can be affected by 

    * Manual setting or URL (back button)

    * Template -> View -> Controller -> Router
    

Router


The router itself comprises only a set of Routes, each 
        relating to "partial" path
App.Router.map(function() {
    this.resource("about"); // { path: "/about" }); redundant
    this.resource("favorites", { path: "/favs" });
})


The Routes are always connected to a controller

App.AboutRoute = Ember.Route.extend({
    setupController: function(controller) {
        // Set the AboutController's 'title'
        controller.set('title', "My App")
    }
})

Router


If you don't explicitly create a Controller, one will be 
   created for you implicitly

App.IndexController is also implicit for path '/'

Summary
URL Route Name Controller Route Template / index IndexController IndexRoute index /about about AboutController AboutRoute about /favs favorites FavoritesController FavoritesRoute favorites

Load data


The Route is responsible to load the models (data). This is done using the model method
App.UsersRoute = Ember.Route.extend({
   model: function(){ 
       return App.User.find()
   }
})

Nested routes


The routes can be nested, and can have arguments
App.Router.map(function() {
    this.resource("users", function () {
        this.resource("user", {path: ':user_id'})
    })
})


Example path: #/users/5

"If your UI is nested, your routes should be nested"
  - Yehuda Katz

Router - extras


Supports 'Redirects'

Supports HTML links {{#linkTo route-name model}}
          e.g.  {{#linkTo "User" user}} 
                            <h2> {{user.name}} </h2>
                   {{/linkTo}}




Tip: adds css-class 'active' on any link that matches current Route





finishing


outlet

Recall App's template is instantiated automatically.
<script type="..." template-name="application">
   kiss my shiny metal ass!
</script>    

Basic Router, IndexController and IndexRoute are instantiated for us.
We specify the IndexController template and position
<script type="..." template-name="application">
   kiss my shiny metal {{outlet}}
</script> 

<script type="..." template-name="index">
   index template
</script>    

outlet

The way Ember knows which Controller/Template to replace with the outlet, is by the State (and Route)

Add a 'users' Template
<script type="..." template-name="users">
   all users will be shown here
</script> 
Add a 'users' Route
App.UsersRoute = Ember.Route.extend({
   model: function(){  return App.User.find()  }
}) 



Tip: This is the default implementation

show all users


Add some fake data
App.Person.FIXTURES = [
   {
     id: 1,
     firstName: ferris,
     lastName, bueller
   }
]    
Let's revise our 'users' Template
<script type="..." template-name="users">
   {{#each user in controller}}
       {{user.name}}   
   {{/each}}
</script> 


Tip: Remember that the Controller proxies request to the model

Show user details

More than one controller can be active
App.Router.map(function() {
    this.resource("users", function () {
        this.resource("user", {path: ':user_id'})
    })
})
Add a template for a specific user  (singular)
<script type="..." template-name="user">
   <header> {{firstName}} </header>
   <section>  {{firstName}} {{lastName}} is .. </section>
</script> 

All that's left is to specify where to put it (next page)


show specific user II



<script type="..." template-name="users">
   {{#each user in controller}}
       {{user.name}}   
   {{/each}}
    
   <section>
        {{outlet}}
   </section>

</script>    

Extras


Partials

Don't use minified in dev (verbose errors)

Helpful functions
    Em.K - empty function
    Em.none - falsy, but not 0 or ''

The run loop
    Em.run.sync()   - propagate class changes now
    Em.run(func)    - propagate DOM changes now

Extras


Ember.TextView  -  View adding binding to <input>  
                                   handle one-key update,copy/paste
                                   context-menu, etc..

Ember twitter bootstrap - don't reinvent


that's all folks



Thank you

Oren Rubin | shexman@gmail | @shexman

ember

By shex

ember

  • 6,420