Organizing the concerns in an application into models, views, controllers and routers, templates, presenters or ViewModels.
Model: is an object that stores persistent state
Template: describes the user interface of the application. View: for sophisticated handling of user events or to create a re-usable component
Router: translates a URL into a series of nested templates, each backed by a model. Controllers: is an object that stores application state
Bindings: changes to one variable will propagate its value into other variables
Computed Properties: That will automatically update along with the properties they are reliant upon
Automatically Updated Templates: to keep up-to-date the WUI whenever changes happen to the underlying data
Ember.js has strong assumptions regarding the names of the different objects in the application, based on those conventions Ember.js frecuently generates code on the fly in memory:
@resource "posts", -> # Route definition for the Post resource
App.PostsRoute # Route
App.PostsController # Controller
App.Post # Model
App.PostsView # View
posts # Template
App.ApplicationRoute # Route Ember will invoke the router's
# hooks first before rendering the template
App.ApplicationController # Controller Ember will set an instance of this
# as the controller for the template
application # Template (main) its properties <- controller
App.ApplicationRoute = Ember.Route.extend(setupController: (controller) ->
controller.set "title", "Hello world!"
)
App.ApplicationController = Ember.Controller.extend(appName: "My First Example")
<h1>{{appName}}</h1>
<h2>{{title}}</h2>
/posts
, Ember will look for these objects:App.PostsRoute # Route Ember will invoke the router's hooks first before rendering
# the template
App.PostsController # Controller Ember will set an instance of this as the controller for
# the posts template
posts # Template that will be render in the {{outlet}} in the application template
App.PostsRoute = Ember.Route.extend(model: ->
# the model is an Array of all of the posts
App.Post.find()
)
Because the model is an Array, Ember.js will automatically supply an instance of Ember.ArrayController
, which will present the backing Array as its model
<ul>
{{#each controller}}
<li>{{title}}</li>
{{/each}}
</ul>
App.Router.map ->
@resource "posts",
path: "/posts/:post_id"
App.PostRoute # Route handler's model hook converts the dynamic :post_id
# parameter into a model
App.PostController # Controller Ember will set an instance of this as the controller for
# the post template
post # Template that will be render in the {{outlet}} in the posts template
Because the model is an Object, Ember.js will automatically supply an instance of Ember.ObjectController
, which will present the backing Object as its model
App.PostRoute = Ember.Route.extend(
model: (params) ->
App.Post.find params.post_id
serialize: (post) ->
post_id: post.get("id")
)
window.App = Ember.Application.create()
This give you:
App
document
and delegates events to the viewsapplication
templateget
and set
are use for accessing and assigning values of the model attributesApp.Store = DS.Store.extend
revision: 13
adapter: DS.RESTAdapter.create
namespace: 'api/v1'
App.Person = DS.Model.extend(
firstName: DS.attr("string")
lastName: DS.attr("string")
birthday: DS.attr("date")
fullName: ->
@get("firstName") + " " + @get("lastName")
.property("firstName", "lastName")
posts: DS.hasMany('App.Post')
)
App.Post = DS.Model.extend(
title: DS.attr("string")
intro: DS.attr("string")
content: DS.attr("string")
author: DS.belongsTo("App.Person")
)
{"person": {
"id": "2",
"first_name": "Jeff",
"last_name": "Atwood",
"birthday": "1993-06-05T22:24:03Z",
"post_ids": [4, 9, 13]}
}
{"people": [{"id": "1",
"first_name": "Ron",
"last_name": "Jeffries",
"birthday": "1978-08-15T12:14:53Z",
"post_ids": [1, 2, 5, 7]
},{"id": "2",
"firstName": "Jeff",
"lastName": "Atwood",
"birthday": "1993-06-05T22:24:03Z",
"post_ids": [4, 9, 13]}]
}
post = App.Post.find(1) # Finding an specific model record
If a record with that ID has already been created, it will be returned immediately. This feature is sometimes called an identity map
post = App.Post.find() # Finding all the model records
This returns an instance of DS.RecordArray
. The record array will start in a loading state with a length of 0, but can immediately be use it in templates. When the server responds with results, the templates will watch for changes in the length and update themselves automatically. DS.RecordArray
is not a JavaScript array,implements Ember.Enumerable. For retrieve records by index the []
notation will not work, use the objectAt(index)
method.
people = App.Person.find(firstName: "Peter") # Finding all the model records
# that satisfy the query criteria
Ember.ObjectController
to represent a single modelEmber.ArrayController
to represent an array of modelsController
which model to represent that not conform with the name convention, set its model
property in the route's setupController
method.App.PostController = Ember.ObjectController.extend(isExpanded: false)
App.PostRoute = Ember.Route.extend(setupController: (controller, post) ->
controller.set "model", post
)
<h1>{{title}}</h1>
<h2>by {{author}}</h2>
<div class='intro'>
{{intro}}
</div>
<hr>
{{#if isExpanded}}
<button {{action toggleProperty 'isExpanded'}}>Hide Content</button>
<div class='content'>
{{content}}
</div>
{{else}}
<button {{action toggleProperty 'isExpanded'}}>Show Content</button>
{{/if}}
App.PostsController = Ember.ArrayController.extend(tooManyWordsPost: ->
tooManyWordsPost = @filter((post) ->
post.get("words") > 3000
)
tooManyWordsPost.get "words"
.property("@each.words"))
App.PostsRoute = Ember.Route.extend(setupController: (controller, person) ->
controller.set "model", person.get("posts")
)
<h1>Author: {{fullName}}</h1>
<ul>
{{#each controller}}
<li>{{title}}</li>
{{/each}}
</ul>
{{tooManyWordsPost}} posts over 3000 words.
If your User Interface is nested, your routes should be nested - Yehuda Katz
App.Router.map ->
@resource "posts", ->
@route "new"
This router creates three routes:
URL
Route Name
Controller
Route
Template
/
index
IndexController
IndexRoute
index
N/A
posts
PostsController
PostsRoute
posts
/posts
posts.index
PostsController
↳PostsIndexController
PostsRoute
↳PostsIndexRoute
posts
↳posts/index
/posts/new
posts.new
PostsController
↳PostsNewController
PostsRoute
↳PostsNewRoute
posts
↳posts/new
A way to see the routes defined in an Emberjs App is in the javascript console use: App.Router.router.recognizer.names
NOTE: You should use this.resource
for URLs that represent a noun,
and this.route
for URLs that represent adjectives or verbs
modifying those nouns.
App.Router.map ->
@resource "people",
path: "/people/:person_id"
, ->
@route "edit"
@resource "posts", ->
@route "new"
This nested router creates the following routes:
URL
Route Name
Controller
Route
Template
/
index
App.IndexController
App.IndexRoute
index
N/A
person
App.PersonController
App.PersonRoute
person
/people/:person_id
person.index
App.PersonIndexController
App.PersonIndexRoute
person/index
/people/:person_id/edit
person.edit
App.PersonEditController
App.PersonEditRoute
person/edit
N/A
posts
App.PostsController
App.PostsRoute
posts
/people/:person_id/posts
posts.index
App.PostsIndexController
App.PostsIndexRoute
posts/index
/people/:person_id/posts/new
posts.new
App.PostsNewController
App.PostsNewRoute
posts/new
yield
but {{outlet}}
is a placeholder that the router will fill in with the appropriate template, based on the current URL<script type="text/x-handlebars" data-template-name="your-template-name">
{{#if expression}}...{{else}}...{{/if}}
{{#unless expression}}...{{/unless}}
{{#each member in enumerable}}...{{else}}...{{/each}}
{{#with scope}}...{{/with}}
<html_element {{bindAttr attribute="expression"}} other_attr>
<div {{bindAttr class=":new isEnabled:enabled:disabled"}}>
{#linkTo 'person.edit' person}}{{person.fullName}}{{/linkTo}}
<button {{action 'expand'}}>Show More...</button>
{{partial}}
takes the template to be rendered as an argument, and renders that template in place. It does not change context or scope. The partial's data-template-name must start with an underscore
<script type="text/x-handlebars" data-template-name='_author'>
Written by {{author.firstName}} {{author.lastName}}
</script>
<script type="text/x-handlebars" data-template-name='post'>
<h1>{{title}}</h1>
<div>{{content}}</div>
{{partial "author"}}
</script>
It is the simplest way to render a template. Turns template text into HTML using the current context (controller, data, router)
{{view}}
works like the partial helper, except instead of providing a template to be rendered within the current template, you provide a view class.App.AuthorView = Ember.View.extend({
templateName: "author",
fullName: (function() {
return this.get("author").get("firstName") + " " + this.get("author").get("lastName");
}).property("firstName","lastName")
})
<script type="text/x-handlebars" data-template-name='author'>
Written by {{view.fullName}}
</script>
<script type="text/x-handlebars" data-template-name='post'>
<h1>{{title}}</h1>
<div>{{content}}</div>
{{view App.AuthorView authorBinding=author}}
</script>
{{render}}
does not require the presence of a matching route. It takes two parameters: the context to be setup and optional second parameter is a model
App.AuthorController = Ember.ObjectController.extend({
postCount: function() {
return App.Post.countForAuthor(this.get("model"));
}.property("model","App.Post.@each.author")
})
<script type="text/x-handlebars" data-template-name='author'>
Written by {{firstName}} {{lastName}}.
Total Posts: {{postCount}}
</script>
<script type="text/x-handlebars" data-template-name='post'>
<h1>{{title}}</h1>
<div>{{content}}</div>
{{render "author" author}}
</script>
{{control}}
works like render, except it uses a new controller instance for every call, instead of reusing the singleton controller
{{render}}
cannot be called multiple times for the same route when not specifying a model. For that you'll need {{control}}
{{control}}
helper is currently disabled by default. To enable it set ENV.EXPERIMENTAL_CONTROL_HELPER = true
before requiring Ember.Ember.Enumerable
API that follows ECMAScript specifications as much as possible
enumerables.forEach callback, target # To iterate in each element of the Enumerable
enumerables.toArray() # Converts the enumerable into a genuine Array
enumerables.map callback, target # Maps all the items to another value, returning
# a new array
enumerables.filter callback, target # Returns an array with all of the items in the
# enumeration that the passed function returns true for
enumerables.every callback, target # Returns true if the passed function returns true
# for every item in the enumeration
enumerables.some callback, target # Returns true if the passed function returns true
# for any item in the enumeration
callback = (item, index, enumerable) -> # item is the current item in the iteration.
# index is the current index in the iteration.
# enumerable is the enumerable object itself.
Allows to begin developing Ember.js apps now, and switch to another adapter when the API is ready to be consumed without any changes to the application code
App = Ember.Application.create()
App.Store = DS.Store.extend(
revision: 13
adapter: DS.FixtureAdapter.create()
)
App.Person = DS.Model.extend(
firstName: DS.attr("string")
lastName: DS.attr("string")
)
App.Documenter.FIXTURES = [
{ id: 1, firstName: 'Yehuda', lastName: 'Katz' },
{ id: 2, firstName: 'Tom' , lastName: 'Dale' }
]
App.rootElement = "#arbitrary-element-to-contain-ember-application"
App.setupForTesting()
App.injectTestHelpers()
setup: ->
App.reset()
#HELPERS
visit url
find selector, context
click selector
keyDown selector, type, keyCode
wait()
#EXAMPLE
test "creating a post displays the new post", ->
visit("/posts/new")
.fillIn(".post-title", "A new post")
.fillIn(".post-author", "John Doe")
.click("button.create").then ->
ok find("h1:contains('A new post')").length, "The post's title should display"
ok find("a[rel=author]:contains('John Doe')").length, "A link to the author should display"
Use a spacebar or arrow keys to navigate