Meet Ember.js

Not just another js framework.

Ember's logo

Miguel Camba

kerad-logo
kerad-logo
kerad-logo kerad-logo kerad-logo kerad-logo

What is ember-word-logo



  • A new MVC framework designed to build ambitious web apps.
  • It does a lot of job for you, so you can focus on what really matters.
  • Reduces boilerplate code.
  • Keeps your views updated with a powerful binding mechanism.

Oh really?

true-story

Options in the wild.

User interfaces

(Scary words that deserve a scary font)


User interfaces are hard


  • Display data from a source.
  • Keep that information updated when source changes.
  • React to user interaction.
  • Store contextual state.

A bit of history

tomster-release
  • 2007 - SproutCore is released.
  • 2008 - Apple announces MobileMe & iWork (built with sproutcore)
  • 2010 - SproutCore team leaves Apple and starts Strobe Inc.
  • 2011 (May) - SproutCore 2.0 announced
  •           (Nov) - Apple builds Strobe in a talent adquisition.
  • 2012 - Tom Dale (ex-apple) and Yehuda Kats leave Strobe, start Tilde and made a fork of SproutCore that is later renamed to Ember.
  • 7 months ago - 1.5 Years later Ember hits 1.0

ember-eiffel-tower Ember's Architecture


  • MVC framework (with Ember's own flavour)
  • The URL is foundational for Ember
  • Convention over configuration. Highly opinionated à la rails
  • Enforces simple UI design

Ember MVC

API
Store
Router
Route
Model
Model
Model
Model
Controller
Controller
View
View
Template
Template

ember-handlebars Handlebars templates

Handlebars.js is an extension to the Mustache templating language. Both are logicless templating languages that keep the view and the code separated like we all know they should be.

Yehuda Katz


Example:

<div class="player-profile">
  <img alt="player-image" {{bind-attr src="imageUrl"}}>
  <p class="name">{{name}}</p>
</div>
					

name and the src attribute are bound!

Live example

Convention over configuration

  • URL: /messages
  • Will be routed to the App.MessagesRoute
  • that populates the model of the App.MessagesController
  • which has a view named App.MessagesView
  • and renders a templated named messages.hbs

You don't have to write them!


If any of these elements is not defined, ember creates it for us.


Those anonymous elements have many default behaviors built in.


You only need to write what makes your app special.

The router


Foundational URL: Each unique resource has a uniq identifier.


The router links that URL with the state hander reponsable of that section.

App.Router.map(function(){
  this.resource('posts', function() {
    // Url: NA              Route name: "posts"          Route: App.PostsRoute
    // and also..
    // Url: "/posts"        Route name: "posts.index"    Route: App.PostsIndexRoute

    this.route('new');
    // Url: "/posts/new"    Route name: "posts.new"      Route: App.PostsNewRoute
  });

  this.route('about');
  // Url: "/about"          Route name: "about"          Route: App.AboutRoute

});
					

The Routes


Populate and setup the controllers of the current section of the application


Manage the transition between the different routes (the top-level state)


Fortunatelly, 99% percent of the time, ember does all the hard work for you


Route's full behavior

App.MessagesRoute = Ember.Router.extend({
  activate: function(){
    /* Hook called when entering the route. */
  },
  redirect: function(){
    /* Hook for redirect to another route right away. */
  },
  beforeModel: function(transition){
    /* Hook usefull to perform any async operation needed before loading the model. */
  },
  model: function(params, transition){
    /* Hook to fetch/build the content of the route's controller. */
  },
  afterModel: function(resolvedModel, transition){
    /* Hook to perform any operation with the model before it is injected in the controller. */
  },
  setupController: function(controller, model){
    /* By default, injected the given model in the given controller. */
  },
  renderTemplate: function(controller, model){
    /* By default renders the route's template with the route's controller as context. */
  },
  deactivate: function(){
    /* Hook for perform any teardown if needed. */
  }
});
					

Average route code

95% of the time, the average route has 5 lines that look like this:

App.MessagesRoute = Ember.Router.extend({
  model: function(params, transition){
    return this.get('store').find('message');
  }
});
					

The rest of the default behavior is just fine.

Controllers


Expose and decorate the underlying data with contextual information.


React to user actions modifying data and store contextual state.


You can extend 3 kinds of controllers depending on your needs:

Ember.Controller

Ember.ObjectController

Ember.ArrayController

Decorate models depending on application state

App.QuestionController = Ember.ObjectController.extend({
  needs: ['currentUser'],

  own: function(){ /* Application state. Depends on the current user being logged */
    return this.get('creator.id') == this.get('controllers.currentUser.id');
  }.property('creator.id', 'controllers.currentUser')
});
					
{{#if own}}<button {{action markAsSolved}}>Mark as solved</button>{{/if}}
{{#unless own}}
  <textarea class="answer"></textarea>
  <button {{action sendAnswer}}></button>
{{/unless}}
{{#link-to lastSeenQuestion}}Back to last seen question.{{/link-to}}

					

The Views


Handle view state/rendering and transform browser meaningless avents into business-logic related events.


View

App.KudosView = Ember.View.extend({
  mouseleaveAt: null,

  // Capture browser generic events
  mouseenter: function(){
    this.set('mouseleaveAt', null);
    Ember.run.later(function(){
      // Send a meaningful event to the controller
      if (!this.get('mouseleaveAt')) this.get('controller').send('like');
    }, 1000);
  },

  mouseleave: function(){
    this.set('mouseleaveAt', new Date());
  }
});

					

Controller

App.PostController = Ember.ObjectController.extend({
  actions: {
    like: function(){
      this.set('liked', true);
      // Persist the like forever if needed.
    }
  }
});

					

Let's see all this in real world

kerad-logo

Ember basics: Design choices

UAP: Uniform access principle


“All services offered by a module should be available through a uniform notation, which does not betray whether they are implemented through storage or through computation”

Bertrand Meyer


Template:
{{user.name}}
{{user.aproximateLocation}}
Somewhere in the app:
App.User = Ember.Object.extend({
  aproximateLocation: function(){
    return someExpensiveCalculation();
  }.property()
});

var user = App.User.create({name: "Wilson"});
					

Generated HTML:

Wilson
BCN, Spain

No matter if name or aproximateLocation are a properties that are just read or function that perform complex calculations, they are accessed in the same way.


The object model: Going beyond the POJO


Use high level abstractions instead of stick to regular JS structures.


Good: Higher level abstractions means less boilerplate.


Good: Allows complex patterns: Class extension, mixin inheritance, proxies, method missing...


Bad: Bigger API, bigger functionality, bigger size: 2x the size of angular.


Bad: Steeper learning curve.


Examples:

// Class extension
App.Cat = Ember.Object.extend({
  legs: 4,
  talk: function(){ alert("meeeaau!"); }
});
App.GrumpyCat = App.Dog.extend({
  talk: function(){ alert("meea....NO") }
});

// Mixins
App.Domesticable = Ember.Mixin.create({
  owner: null,
  domesticate: function(owner){
    console.log("Now " + owner + " and " + this.get("name") + " are friends!");
    this.set("owner", owner);
  }
})
App.Dog = App.Animal.extend(App.Domesticable, {carnivore: true});

// Proxies
var rex = App.Dog.create({name: "Rex"});
var pet = Ember.ObjectProxy.create({content: rex, age: 4});
pet.get("name") // => "Rex"
pet.get("age")  // => 4

  				

State. Machine. All. The. Things.


Express your business logic declaratively.


Define dependencies and calculations between properties.


React to user actions modifying some value in your underlying data.


All properties that had dependencies on it gets recomputed automatically.

Example.


StackOverflows questions:


  • A question is answered if has at least one answer.
  • A question is popular if has more than 5 answers.
  • A question is solved if any answer has been accepted.
  • A question is hot if its popular but still not solved.


How do we express that business logic?

Computed properties to the rescue.

App.Question = Ember.Object.extend({
  solutions: [],
  answered: function(){
    return this.get('answers.length') > 0;
  }.property('answers.length'),

  popular: function(){
    return this.get('answers.length') > 5;
  }.property('answers.length'),

  solved: function(){
    return this.get('answers').some(function(answer){
      return answer.get('accepted');
    });
  }.property('answers.@each.accepted'),

  hot: function(){
    return this.get('popular') && !this.get('solved');
  }.property('popular', 'solved')
});
					

Computed properties to the rescue.

question.get('answers')  // => []
question.get('answered') // => false
question.get('hot')      // => false

question.get('answers').pushObject(Answer.create())

question.get('answers')  // => [Answer]
question.get('answered') // => true
question.get('hot')      // => false

question.get('answers').pushObjects([Answer.create(), Answer.create(), Answer.create(), Answer.create(), Answer.create()])

question.get('answers')  // => [Answer, Answer, Answer, Answer, Answer, Answer]
question.get('answered') // => true
question.get('hot')      // => true

question.get('answers.lastObject').set('accepted', true)

question.get('hot')      // => false
question.get('solved')   // => true
					

question.hbs

<article {{bind-attr class=":question answered hot solved"}}>
  <div class="excerpt">{{excerpt text}}</div><span class="answers-counter">{{answers.length}}</span>
</article>
					

When it receives 6 answers the html will look like this:

Lorem ipsum dolor sit a...
6
question.get('answers.firstObject').set("accepted", true);
Lorem ipsum dolor sit a...
6

And much much more...

  • Extensive use of promises.
  • Properties observers.
  • The Run loop.
  • Isolated components.
  • Testing tools.
  • Ember extension for chrome and firefox.

Bonus points: Ember data


Disclaimer. Still rolling. Not stable yet.

running-tomster

Ember's "official" persistence layer.

  • In memory store.
  • Canonical records.
  • Customizable adapters/serializers.
  • Type casting.
  • Rich DSL.
  • Identity map.
  • HasMany/BelongsTo associations.
  • Errors and dirty tracking.
  • Out-of-the-box integration with ActiveModel::Serializers.

Defining your model

App.Team = DS.Model.extend({
  name:      DS.attr('string'),
  birthDate: DS.attr('date'),
  weight:    DS.attr('number'),
  links:     DS.attr(), // Raw data, no casting. p.e. { tw: "http...", fb: "http..." }

  // Relations
  league:    DS.belongsTo('league'),
  matches:   DS.hasMany('match', {async: true});
});
					

Query for records


Find all records.

store.find('match');

Find a record.

store.find('match', 123);

Filter records.

store.find('match', { played: false });

DSL


Create a new record

var match = store.createRecord('match', { stadium: "Camp Now" });

Dirty tracking

match.set('played', true);
match.get('isDirty'); // true

						

Saving with promises.

match.save().then(function(){
  alert('Match saved!');
}).catch(function(errors){
	alert('Ups, your match has some errors: ' + errors);
});

						

campster Resources to learn more:

Thanks

thanks

Q & A

Why did we choose ember?



Read: Why did we choose it over Angular?

trollface

Another day.....