• Object Model

    • Use computed properties to build a new property by synthesizing other properties
    • Observers should contain behavior that reacts to changes in another property
    • Bindings are most often used to ensure objects in two different layers are always in sync
  • Application

    • define application namespace
    • adds document event listeners
    • render template and creates routers
    window.App = Ember.Application.create({
      LOG_TRANSITIONS: true // log routing
    });
  • Notes

    юрл - с него все начинается, route устанавливает модель, которая доступна в контроллере, контроллер определяет переменные в шаблоне, шаблон отображает информацию, outlet используется для показа. Также роут настраивает контроллер и то что будет отрендерено.
    контроллеры используются для декорирования моделей.

  • Classes & Instances

    • Base class: Ember.Object
    • Inheritance: .extend() and this._super()
    • Create new instance: .create(options={})
    • Constructor: init: function() {}
    • Access to the properties with .get() and .set()
    • Class.reopen to define additional methods
    • Class.reopenClass to define static methods
  • Computed Properties

    • let you declare functions as properties.
    • declare with property function, and the arguments tell Ember its dependencies.
    • computed properties can be depend from another properties.
    • support dynamic update of all chain
    • setup setter in property declaration
    • use @each for arrays, it updates deps on add, remove, property change or reassing new array.
    • @each only works one level deep
  • Observers

    • observes to setup method on a function. Use Ember.observer without prototype extension.
    • .addObserver to add outside of class definition
    • Ember.run.once to implement async behaviour in the end of event loop
    • use .on(‘init’) to add side-effect on initialization
    • lazy by default, if you never get it, observer never fire.
  • Bindings

    • A link between 2 properties.
    • name convention: myPropertyBinding
    • Bindings don’t update immediately. Ember waits until all of your application code has finished running before synchronizing changes, so you can change a bound property as many times as you’d like without worrying about the overhead of syncing bindings when values are transient.
    • Ember.Binding.oneWay for performance needs
  • Templates

    • define handlebars templates as <script type="text/x-handlebars" data-template-name="my-template"></script>
    • {{bind-attr}} to communicate with controller and setup dynamic classes
    • {{link-to}} to define dynamic routes
    • {{action}} to call specific action
    • render with custom helpers and {{partial}}, {{view}}, and {{render}}
  • Routing

    • each of the possible states in your application is represented by a URL
    • define root url: App.Router.reopen({ rootURL: ‘/blog/‘ })
  • Define Class

    Use Ember.Object as a base class, and this._super to call superclass methods.

    App.Person = Ember.Object.extend({
      say: function(thing) {
        var name = this.get('name');
    
        alert(name + " says: " + thing);
      }
    });
    
    App.Soldier = App.Person.extend({
      say: function(thing) {
        this._super(thing + ", sir!");
      }
    });
    
    var yehuda = App.Soldier.create({
      name: "Yehuda Katz"
    });
    
    yehuda.say("Yes");
    // alerts "Yehuda Katz says: Yes, sir!"
  • Create instances

    Use .create({options}) to create new instance. Also define init as a constructor.

    var Person = Ember.Object.extend({
      init: function() {
        var name = this.get('name');
        alert(name + ", reporting for duty!");
      }
    });
    
    Person.create({
      name: "Stefan Penner"
    });
    
    // alerts "Stefan Penner, reporting for duty!"
  • Access to the properties

    When accessing the properties of an object, use the get and set accessor methods

    var person = App.Person.create();
    
    var name = person.get('name');
    person.set('name', "Tobias Fünke");
  • Chaining computed properties

    App.Person = Ember.Object.extend({
      firstName: null,
      lastName: null,
      age: null,
      country: null,
    
      fullName: function() {
        return this.get('firstName') + ' ' + this.get('lastName');
      }.property('firstName', 'lastName'),
    
      description: function() {
        return this.get('fullName') + '; Age: ' + this.get('age') + '; Country: ' + this.get('country');
      }.property('fullName', 'age', 'country')
    });
    
    var captainAmerica = App.Person.create({
      firstName: 'Steve',
      lastName: 'Rogers',
      age: 80,
      country: 'USA'
    });
    
    captainAmerica.get('description'); // "Steve Rogers; Age: 80; Country: USA"
  • Dynamic Updating

    Computed properties, by default, observe any changes made to the properties they depend on and are dynamically updated when they’re called.

  • Set property

    With one function we can define getter and setter.

    App.Person = Ember.Object.extend({
      firstName: null,
      lastName: null,
    
      fullName: function(key, value) {
        // setter
        if (arguments.length > 1) {
          var nameParts = value.split(/\s+/);
          this.set('firstName', nameParts[0]);
          this.set('lastName',  nameParts[1]);
        }
    
        // getter
        return this.get('firstName') + ' ' + this.get('lastName');
      }.property('firstName', 'lastName')
    });
    
    var captainAmerica = App.Person.create();
    captainAmerica.set('fullName', "William Burnside");
    captainAmerica.get('firstName'); // William
    captainAmerica.get('lastName'); // Burnside
  • Aggregate data

    App.TodosController = Ember.Controller.extend({
      todos: [
        Ember.Object.create({ isDone: false })
      ],
    
      remaining: function() {
        var todos = this.get('todos');
        return todos.filterBy('isDone', false).get('length');
      }.property('todos.@each.isDone')
    });
    
    var todos = App.todosController.get('todos');
    App.todosController.get('remaining');
    // 1
    
    var todo = todos.objectAt(0);
    todo.set('isDone', true);
    
    App.todosController.get('remaining');
    // 0
    
    todo = Ember.Object.create({ isDone: false });
    todos.pushObject(todo);
    
    App.todosController.get('remaining');
    // 1
  • Without prototype extensions

    Person.reopen({
      fullNameChanged: Ember.observer('fullName', function() {
        // deal with the change
      })
    });
  • Async behaviour

    Person.reopen({
      partOfNameChanged: function() {
        Ember.run.once(this, 'processFullName');
      }.observes('firstName', 'lastName'),
    
      processFullName: function() {
        // This will only fire once if you set two properties at the same time, and
        // will also happen in the next run loop once all properties are synchronized
        console.log(this.get('fullName'));
      }
    });
    
    person.set('firstName', 'John');
    person.set('lastName', 'Smith');
  • App.wife = Ember.Object.create({
      householdIncome: 80000
    });
    
    App.husband = Ember.Object.create({
      householdIncomeBinding: 'App.wife.householdIncome'
    });
    
    App.husband.get('householdIncome'); // 80000
    
    // Someone gets raise.
    App.husband.set('householdIncome', 90000);
    App.wife.get('householdIncome'); // 90000
  • Application template

    • has to have name application
    • has one {{outlet}} as a placeholder for urls
    • contains default app layout
    <header>
      <h1>Igor's Blog</h1>
    </header>
    
    <div>
      {{outlet}}
    </div>
    
    <footer>
      &copy;2013 Igor's Publishing, Inc.
    </footer>
  • Define Handlebars template

    • Each template has an associated controller: this is where the template finds the properties that it displays.
    • use data-template-name attribute to setup name

    Example:

    Hello, <strong>{{firstName}} {{lastName}}</strong>!
    App.ApplicationController = Ember.Controller.extend({
      firstName: "Trek",
      lastName: "Glowacki"
    });

    result:

    Hello, <strong>Trek Glowacki</strong>!
  • Display list of items

    Use name, instead of changing context

    {{name}}'s Friends
    
    <ul>
      {{#each friend in friends}}
        <li>{{name}}'s friend {{friend.name}}</li>
      {{/each}}
    </ul>

    Use else for empty sets

    {{#each people}}
      Hello, {{name}}!
    {{else}}
      Sorry, nobody is here.
    {{/each}}
  • Binding element attributes

    To bind attribute to some of controller properties. For false values attr ignores.

    <input type="checkbox" {{bind-attr disabled=isAdministrator}}>
    <img {{bind-attr src=logoUrl}} alt="Logo">
  • Binding elements class names

    Regular values:

    <div {{bind-attr class="isUrgent priority"}}>
      Warning!
    </div>

    Bind boolean values:

    • {{bind-attr class=”isUrgent”}} -> class=”is-urgent”
    • {{bind-attr class=”isUrgent:urgent”}} -> class=”urgent”
    • {{bind-attr class=”isEnabled:enabled:disabled”}} -> class=”enabled”
    • {{bind-attr class=”isEnabled::disabled”}} -> for only false value

    Static (regular html) classes:

    <div {{bind-attr class=":high-priority isUrgent"}}>
      Warning!
    </div>
  • Generates dynamic links:

    App.Router.map(function() {
      this.resource("photos", function(){
        this.route("edit", { path: "/:photo_id" });
      });
    });
    <ul>
    {{#each photo in photos}}
      <li>{{#link-to 'photos.edit' photo}}{{photo.title}}{{/link-to}}</li>
    {{/each}}
    </ul>
    <ul>
      <li><a href="/photos/1">Happy Kittens</a></li>
      <li><a href="/photos/2">Puppy Running</a></li>
      <li><a href="/photos/3">Mountain Landscape</a></li>
    </ul>

    It also supports nested resources.

    {{#link-to 'photo.comment' nextPhoto primaryComment}}
  • {{action}}

    • delegates events to controller
    • pass object as a paramether {{action "select" post}}
    • specify events type on="mouseUp"
    • bubble by default, use bubbles=false to disable it
    • specify key modifier allowedKeys="alt"
    • send action to view instead of controller target="view"
  • Input helpers

    • {{input type=”text” value=firstName disabled=entryNotAllowed size=”50”}}
    • {{input type=”checkbox” name=”isAdmin” checked=isAdmin}}
    • {{textarea value=name cols=”80” rows=”6”}}
  • Rendering helpers

    • {{partial}} data-template-name=’_author’
    • {{view}} specify view class
    • {{render}} specify view, mode, controller
  • Custom helpers

    Reacts on updates from model.

    Ember.Handlebars.helper('fullName', function(person) {
      return person.get('firstName') + ' ' + person.get('lastName');
    }, 'firstName', 'lastName');

    Custom view helper:
    Ember.Handlebars.helper(‘calendar’, App.CalendarView) -> {{view App.CalendarView}}

{"cards":[{"_id":"3b166877c98409984a000225","treeId":"3b1646e6c98409984a00021e","seq":1,"position":0.5,"parentId":null,"content":"# [Ember.js Guides](http://emberjs.com/guides/)\n\n- ES6 modules http://thau.me/2013/12/es6-modules-and-emberjs-a-taste-of-the-future-part-2/\n- ember + rails https://github.com/tonycoco/ember_tester + http://www.youtube.com/watch?v=BpQj9_qEUAc and plugin https://github.com/emberjs/ember-rails"},{"_id":"3b30ffae4798590efd0000ac","treeId":"3b1646e6c98409984a00021e","seq":1,"position":2,"parentId":null,"content":"## Object Model\n\n- Use *computed properties* to build a new property by synthesizing other properties\n- *Observers* should contain behavior that reacts to changes in another property\n- *Bindings* are most often used to ensure objects in two different layers are always in sync"},{"_id":"3b1647e0c98409984a000221","treeId":"3b1646e6c98409984a00021e","seq":1,"position":2,"parentId":"3b30ffae4798590efd0000ac","content":"## [Classes & Instances](http://emberjs.com/guides/object-model/classes-and-instances/)\n\n- Base class: **Ember.Object**\n- Inheritance: **.extend()** and **this._super()**\n- Create new instance: **.create(options={})**\n- Constructor: **init: function() {}**\n- Access to the properties with **.get()** and **.set()**\n- **Class.reopen** to define additional methods\n- **Class.reopenClass** to define static methods"},{"_id":"3b1648adc98409984a000222","treeId":"3b1646e6c98409984a00021e","seq":1,"position":1,"parentId":"3b1647e0c98409984a000221","content":"### Define Class\n\nUse `Ember.Object` as a base class, and `this._super` to call superclass methods.\n\n```js\nApp.Person = Ember.Object.extend({\n say: function(thing) {\n var name = this.get('name');\n\n alert(name + \" says: \" + thing);\n }\n});\n\nApp.Soldier = App.Person.extend({\n say: function(thing) {\n this._super(thing + \", sir!\");\n }\n});\n\nvar yehuda = App.Soldier.create({\n name: \"Yehuda Katz\"\n});\n\nyehuda.say(\"Yes\");\n// alerts \"Yehuda Katz says: Yes, sir!\"\n```"},{"_id":"3b164df5c98409984a000223","treeId":"3b1646e6c98409984a00021e","seq":1,"position":2,"parentId":"3b1647e0c98409984a000221","content":"### Create instances\n\nUse `.create({options})` to create new instance. Also define `init` as a constructor.\n\n```js\nvar Person = Ember.Object.extend({\n init: function() {\n var name = this.get('name');\n alert(name + \", reporting for duty!\");\n }\n});\n\nPerson.create({\n name: \"Stefan Penner\"\n});\n\n// alerts \"Stefan Penner, reporting for duty!\"\n```"},{"_id":"3b165305c98409984a000224","treeId":"3b1646e6c98409984a00021e","seq":1,"position":3,"parentId":"3b1647e0c98409984a000221","content":"### Access to the properties\n\nWhen accessing the properties of an object, use the get and set accessor methods\n\n```js\nvar person = App.Person.create();\n\nvar name = person.get('name');\nperson.set('name', \"Tobias Fünke\");\n```"},{"_id":"3b2dc8cc1d26f7d9c10000a2","treeId":"3b1646e6c98409984a00021e","seq":1,"position":3,"parentId":"3b30ffae4798590efd0000ac","content":"## [Computed Properties](http://emberjs.com/guides/object-model/computed-properties)\n- let you declare functions as properties.\n- declare with **property** function, and the arguments tell Ember its dependencies.\n- computed properties can be depend from another properties.\n- support dynamic update of all chain\n- setup **setter** in property declaration\n- use **@each** for arrays, it updates deps on add, remove, property change or reassing new array.\n- **@each** only works one level deep"},{"_id":"3b2dc9151d26f7d9c10000a3","treeId":"3b1646e6c98409984a00021e","seq":1,"position":1,"parentId":"3b2dc8cc1d26f7d9c10000a2","content":"### Chaining computed properties\n\n```javascript\nApp.Person = Ember.Object.extend({\n firstName: null,\n lastName: null,\n age: null,\n country: null,\n\n fullName: function() {\n return this.get('firstName') + ' ' + this.get('lastName');\n }.property('firstName', 'lastName'),\n\n description: function() {\n return this.get('fullName') + '; Age: ' + this.get('age') + '; Country: ' + this.get('country');\n }.property('fullName', 'age', 'country')\n});\n\nvar captainAmerica = App.Person.create({\n firstName: 'Steve',\n lastName: 'Rogers',\n age: 80,\n country: 'USA'\n});\n\ncaptainAmerica.get('description'); // \"Steve Rogers; Age: 80; Country: USA\"\n```\n"},{"_id":"3b2dc92b1d26f7d9c10000a4","treeId":"3b1646e6c98409984a00021e","seq":1,"position":2,"parentId":"3b2dc8cc1d26f7d9c10000a2","content":"### Dynamic Updating\nComputed properties, by default, observe any changes made to the properties they depend on and are dynamically updated when they're called."},{"_id":"3b2dc9e01d26f7d9c10000a5","treeId":"3b1646e6c98409984a00021e","seq":1,"position":3,"parentId":"3b2dc8cc1d26f7d9c10000a2","content":"### Set property\n\nWith one function we can define getter and setter.\n\n```js\nApp.Person = Ember.Object.extend({\n firstName: null,\n lastName: null,\n\n fullName: function(key, value) {\n // setter\n if (arguments.length > 1) {\n var nameParts = value.split(/\\s+/);\n this.set('firstName', nameParts[0]);\n this.set('lastName', nameParts[1]);\n }\n\n // getter\n return this.get('firstName') + ' ' + this.get('lastName');\n }.property('firstName', 'lastName')\n});\n\nvar captainAmerica = App.Person.create();\ncaptainAmerica.set('fullName', \"William Burnside\");\ncaptainAmerica.get('firstName'); // William\ncaptainAmerica.get('lastName'); // Burnside\n```"},{"_id":"3b2dca241d26f7d9c10000a6","treeId":"3b1646e6c98409984a00021e","seq":1,"position":4,"parentId":"3b2dc8cc1d26f7d9c10000a2","content":"### Aggregate data\n\n```js\nApp.TodosController = Ember.Controller.extend({\n todos: [\n Ember.Object.create({ isDone: false })\n ],\n\n remaining: function() {\n var todos = this.get('todos');\n return todos.filterBy('isDone', false).get('length');\n }.property('todos.@each.isDone')\n});\n\nvar todos = App.todosController.get('todos');\nApp.todosController.get('remaining');\n// 1\n\nvar todo = todos.objectAt(0);\ntodo.set('isDone', true);\n\nApp.todosController.get('remaining');\n// 0\n\ntodo = Ember.Object.create({ isDone: false });\ntodos.pushObject(todo);\n\nApp.todosController.get('remaining');\n// 1\n```"},{"_id":"3b30da7c4798590efd0000a7","treeId":"3b1646e6c98409984a00021e","seq":1,"position":4,"parentId":"3b30ffae4798590efd0000ac","content":"## [Observers](http://emberjs.com/guides/object-model/observers/)\n\n- **observes** to setup method on a function. Use **Ember.observer** without prototype extension.\n- **.addObserver** to add outside of class definition\n- **Ember.run.once** to implement async behaviour in the end of event loop\n- use **.on('init')** to add side-effect on initialization\n- lazy by default, if you never get it, observer never fire."},{"_id":"3b30e74d4798590efd0000a9","treeId":"3b1646e6c98409984a00021e","seq":1,"position":0.5,"parentId":"3b30da7c4798590efd0000a7","content":"### Without prototype extensions\n```js\nPerson.reopen({\n fullNameChanged: Ember.observer('fullName', function() {\n // deal with the change\n })\n});\n```"},{"_id":"3b30dcff4798590efd0000a8","treeId":"3b1646e6c98409984a00021e","seq":1,"position":1,"parentId":"3b30da7c4798590efd0000a7","content":"### Async behaviour\n\n```js\nPerson.reopen({\n partOfNameChanged: function() {\n Ember.run.once(this, 'processFullName');\n }.observes('firstName', 'lastName'),\n\n processFullName: function() {\n // This will only fire once if you set two properties at the same time, and\n // will also happen in the next run loop once all properties are synchronized\n console.log(this.get('fullName'));\n }\n});\n\nperson.set('firstName', 'John');\nperson.set('lastName', 'Smith');\n```\n"},{"_id":"3b30f1ad4798590efd0000aa","treeId":"3b1646e6c98409984a00021e","seq":1,"position":5,"parentId":"3b30ffae4798590efd0000ac","content":"## [Bindings](http://emberjs.com/guides/object-model/bindings/)\n\n- A link between 2 properties. \n- name convention: myProperty**Binding**\n- Bindings don't update immediately. Ember waits until all of your application code has finished running before synchronizing changes, so you can change a bound property as many times as you'd like without worrying about the overhead of syncing bindings when values are transient. \n- **Ember.Binding.oneWay** for performance needs"},{"_id":"3b30f4644798590efd0000ab","treeId":"3b1646e6c98409984a00021e","seq":1,"position":1,"parentId":"3b30f1ad4798590efd0000aa","content":"```js\nApp.wife = Ember.Object.create({\n householdIncome: 80000\n});\n\nApp.husband = Ember.Object.create({\n householdIncomeBinding: 'App.wife.householdIncome'\n});\n\nApp.husband.get('householdIncome'); // 80000\n\n// Someone gets raise.\nApp.husband.set('householdIncome', 90000);\nApp.wife.get('householdIncome'); // 90000\n```"},{"_id":"3b3106354798590efd0000ae","treeId":"3b1646e6c98409984a00021e","seq":1,"position":3,"parentId":null,"content":"## Application\n\n- define application namespace\n- adds document event listeners\n- render template and creates routers\n\n```js\nwindow.App = Ember.Application.create({\n LOG_TRANSITIONS: true // log routing\n});\n```"},{"_id":"3b3154354798590efd0000b0","treeId":"3b1646e6c98409984a00021e","seq":1,"position":1,"parentId":"3b3106354798590efd0000ae","content":"## Templates\n\n- define handlebars templates as `<script type=\"text/x-handlebars\" data-template-name=\"my-template\"></script>`\n- {{bind-attr}} to communicate with controller and setup dynamic classes\n- {{link-to}} to define dynamic routes\n- {{action}} to call specific action\n- render with custom helpers and {{partial}}, {{view}}, and {{render}}\n"},{"_id":"3b3159484798590efd0000b1","treeId":"3b1646e6c98409984a00021e","seq":1,"position":1,"parentId":"3b3154354798590efd0000b0","content":"### Application template\n\n- has to have name `application` \n- has one {{outlet}} as a placeholder for urls\n- contains default app layout\n\n```html\n<header>\n <h1>Igor's Blog</h1>\n</header>\n\n<div>\n {{outlet}}\n</div>\n\n<footer>\n &copy;2013 Igor's Publishing, Inc.\n</footer>\n```"},{"_id":"3b31626c4798590efd0000b2","treeId":"3b1646e6c98409984a00021e","seq":1,"position":2,"parentId":"3b3154354798590efd0000b0","content":"### Define [Handlebars](http://www.handlebarsjs.com/) template\n\n- Each template has an associated controller: this is where the template finds the properties that it displays.\n- use `data-template-name` attribute to setup name\n\nExample:\n\n```hbs\nHello, <strong>{{firstName}} {{lastName}}</strong>!\n```\n\n```js\nApp.ApplicationController = Ember.Controller.extend({\n firstName: \"Trek\",\n lastName: \"Glowacki\"\n});\n```\n\nresult:\n\n```\nHello, <strong>Trek Glowacki</strong>!\n```"},{"_id":"3b31697b4798590efd0000b4","treeId":"3b1646e6c98409984a00021e","seq":1,"position":2.5,"parentId":"3b3154354798590efd0000b0","content":"### [Display list of items](http://emberjs.com/guides/templates/displaying-a-list-of-items)\n\nUse name, instead of changing context\n\n```hbs\n{{name}}'s Friends\n\n<ul>\n {{#each friend in friends}}\n <li>{{name}}'s friend {{friend.name}}</li>\n {{/each}}\n</ul>\n```\n\nUse `else` for empty sets\n\n```hbs\n{{#each people}}\n Hello, {{name}}!\n{{else}}\n Sorry, nobody is here.\n{{/each}}\n```"},{"_id":"3b316f964798590efd0000b5","treeId":"3b1646e6c98409984a00021e","seq":1,"position":3,"parentId":"3b3154354798590efd0000b0","content":"### [Binding element attributes](http://emberjs.com/guides/templates/binding-element-attributes)\n\nTo bind attribute to some of controller properties. For `false` values attr ignores.\n\n```hbs\n<input type=\"checkbox\" {{bind-attr disabled=isAdministrator}}>\n```\n\n```hbs\n<img {{bind-attr src=logoUrl}} alt=\"Logo\">\n```\n"},{"_id":"3b3198784798590efd0000b8","treeId":"3b1646e6c98409984a00021e","seq":1,"position":4,"parentId":"3b3154354798590efd0000b0","content":"### [Binding elements class names](http://emberjs.com/guides/templates/binding-element-class-names/)\n\nRegular values:\n\n```hbs\n<div {{bind-attr class=\"isUrgent priority\"}}>\n Warning!\n</div>\n```\n\nBind boolean values:\n\n- {{bind-attr class=\"isUrgent\"}} -> class=\"is-urgent\"\n- {{bind-attr class=\"isUrgent:urgent\"}} -> class=\"urgent\"\n- {{bind-attr class=\"isEnabled:enabled:disabled\"}} -> class=\"enabled\"\n- {{bind-attr class=\"isEnabled::disabled\"}} -> for only false value\n\nStatic (regular html) classes:\n\n```hbs\n<div {{bind-attr class=\":high-priority isUrgent\"}}>\n Warning!\n</div>\n```"},{"_id":"3b31a4444798590efd0000b9","treeId":"3b1646e6c98409984a00021e","seq":1,"position":5,"parentId":"3b3154354798590efd0000b0","content":"### [{{link-to}}](http://emberjs.com/guides/templates/links/)\n\nGenerates dynamic links:\n\n```js\nApp.Router.map(function() {\n this.resource(\"photos\", function(){\n this.route(\"edit\", { path: \"/:photo_id\" });\n });\n});\n```\n\n```hbs\n<ul>\n{{#each photo in photos}}\n <li>{{#link-to 'photos.edit' photo}}{{photo.title}}{{/link-to}}</li>\n{{/each}}\n</ul>\n```\n\n```html\n<ul>\n <li><a href=\"/photos/1\">Happy Kittens</a></li>\n <li><a href=\"/photos/2\">Puppy Running</a></li>\n <li><a href=\"/photos/3\">Mountain Landscape</a></li>\n</ul>\n```\n\nIt also supports nested resources.\n\n```hbs\n{{#link-to 'photo.comment' nextPhoto primaryComment}}\n```"},{"_id":"3b31d72e43db63e5f4000077","treeId":"3b1646e6c98409984a00021e","seq":1,"position":6,"parentId":"3b3154354798590efd0000b0","content":"### [{{action}}](http://emberjs.com/guides/templates/actions/)\n\n- delegates events to controller\n- pass object as a paramether `{{action \"select\" post}}`\n- specify events type `on=\"mouseUp\"`\n- bubble by default, use `bubbles=false` to disable it\n- specify key modifier `allowedKeys=\"alt\"`\n- send action to view instead of controller `target=\"view\"`"},{"_id":"3b31e57d43db63e5f4000078","treeId":"3b1646e6c98409984a00021e","seq":1,"position":7,"parentId":"3b3154354798590efd0000b0","content":"### [Input helpers](http://emberjs.com/guides/templates/input-helpers/)\n\n- {{input type=\"text\" value=firstName disabled=entryNotAllowed size=\"50\"}}\n- {{input type=\"checkbox\" name=\"isAdmin\" checked=isAdmin}}\n- {{textarea value=name cols=\"80\" rows=\"6\"}}"},{"_id":"3b31fd4c43db63e5f4000079","treeId":"3b1646e6c98409984a00021e","seq":1,"position":8,"parentId":"3b3154354798590efd0000b0","content":"### [Rendering helpers](http://emberjs.com/guides/templates/rendering-with-helpers/)\n\n- {{partial}} data-template-name='_author'\n- {{view}} specify view class\n- {{render}} specify view, mode, controller"},{"_id":"3b3203f243db63e5f400007a","treeId":"3b1646e6c98409984a00021e","seq":1,"position":9,"parentId":"3b3154354798590efd0000b0","content":"### [Custom helpers](http://emberjs.com/guides/templates/writing-helpers/)\n\nReacts on updates from model.\n\n```js\nEmber.Handlebars.helper('fullName', function(person) {\n return person.get('firstName') + ' ' + person.get('lastName');\n}, 'firstName', 'lastName');\n```\n\nCustom view helper:\nEmber.Handlebars.helper('calendar', App.CalendarView) -> {{view App.CalendarView}}"},{"_id":"3b3210d343db63e5f400007b","treeId":"3b1646e6c98409984a00021e","seq":1,"position":2,"parentId":"3b3106354798590efd0000ae","content":"## Routing\n- each of the possible states in your application is represented by a URL\n- define root url: App.Router.reopen({ rootURL: '/blog/' })"},{"_id":"3b318f3b4798590efd0000b7","treeId":"3b1646e6c98409984a00021e","seq":1,"position":4,"parentId":null,"content":"## Notes\n- [Enumerable](http://emberjs.com/guides/enumerables/) contains list of methods with a strange API, much better use native ES5 methods and `underscore/underscore.string/lodash`\n- Ember extends default prototypes, because of bad API. It has to be [disabled](http://emberjs.com/guides/configuring-ember/disabling-prototype-extensions/).\n- nice sync testing http://emberjs.com/guides/testing/integration/\n\nюрл - с него все начинается, route устанавливает модель, которая доступна в контроллере, контроллер определяет переменные в шаблоне, шаблон отображает информацию, outlet используется для показа. Также роут настраивает контроллер и то что будет отрендерено.\nконтроллеры используются для декорирования моделей."}],"tree":{"_id":"3b1646e6c98409984a00021e","name":"Ember.js","publicUrl":"ember"}}