Ember.js 技巧、诀窍和最佳实践

目录 [−]

  1. 通用
    1. 核心概念
    2. 命名约定
    3. 带Binding后缀的属性自动转为Ember.Binding对象
    4. View vs. Component
  • Controller
    1. 转到其它页面
    2. 访问其它Controller. needs属性
    3. 使用send调用定义的action
    4. 主动触发property变动事件
  • Route
    1. 一些钩子hook
    2. controllerFor
    3. modelFor得到父Route的model
    4. transitionTo, intermediateTransitionTo
    5. refresh 刷新本route及子route的model
    6. 渲染非默认的模版
  • Model
    1. 使用Ember.Object的extend定义新类
    2. 计算属性Computed Properties
    3. @each计算
    4. observers 是同步的
    5. Ember.computed('aaa', function(){}), Ember.observer('aaa', function () {})
    6. reopen覆盖
    7. 架构
    8. Store
    9. model 和 record
    10. adapter
    11. Serializer
    12. 属性定义时,attr()类型可以不设置
    13. One-to-One, One-to-Many和Many-to-Many. hasMany
    14. 增删改查
    15. model的一些方法
    16. Adapter的 URL Conventions
    17. namespace
    18. normalizeHash
    19. Adapter的其它属性
    20. 定制Transformations
  • View
    1. 定义view
    2. 定制view元素
    3. 内建的view
  • Template
  • 数据枚举 Ember.Enumerable
    1. forEach
    2. 可枚举的类型
    3. toArray()得到纯javascript数组
    4. firstObject, lastObject
    5. MAP
    6. filter
    7. Aggregate
  • Helper
    1. each helper
    2. if-else
    3. outlet
    4. partial 渲染另外一个页面而不改变上下文
    5. render 使用包含的上下文
    6. view
    7. view, partial和render的区别
    8. with changes scope, as
    9. link-to
    10. input
    11. 自定义helper
  • Component
    1. HTML代码代替hbs模版
    2. 定义组件
    3. 组件的钩子(Hook)函数
    4. 传给组件参数
    5. 定制组件的属性
    6. 从组件发送action给其它应用
  • Ember.js是一款用来创建炫酷的Web应用程序的JavaScript MV* 框架。 正像AngularJS,Backbone.js一样正在广泛的应用于现代的Web开发中, 知名用户包括 Yahoo!, Groupon, 和 ZenDesk.
    总的来说, Ember.js还在推广之中, 国内使用它做开发的还比较少, 官方的文档也不是很完备。 所以这篇文章记录了我使用Ember中收集的一些技巧,诀窍和最佳实践, 并且会不断的更新。

    想了解Ember.js和其它JavaScript框架的区别, 可以看这篇文章: AngularJS vs. Backbone.js vs. Ember.js

    通用

    核心概念

    Template: describes the user interface of your application by Handlebars。
    Router: The router translates a URL into a series of nested templates, each backed by a model.
    Route: A route is an object that tells the template which model it should display.
    Model: A model is an object that stores persistent state.
    Controller: A controller is an object that stores application state. A template can optionally have a controller in addition to a model, and can retrieve properties from both.
    Component: A component is a custom HTML tag whose behavior you implement using JavaScript and whose appearance you describe using Handlebars templates.

    命名约定

    Ember.js 由Yehuda Katz创建, 他还是jQuery, Ruby on Rails 和 SproutCore核心开发组的成员。 就像Katz其它项目一样, 命名约定(convention over configuration)被广泛使用。
    你可以查看这篇文章了解Ember.js的命名: Ember命名规则

    Binding后缀的属性自动转为Ember.Binding对象

    valueBinding: "MyApp.someController.title" will create a binding from MyApp.someController.title to the value property of your object instance automatically. Now the two values will be kept in sync.

    单向绑定: bigTitlesBinding: Ember.Binding.oneWay("MyApp.preferencesController.bigTitles")

    View vs. Component

    一句话, 尽量使用Component而不是View

    Contrarily to Ember views, an Ember component is not connected to a related Ember Controller, since it is self contained, in which regards both the data and events that it handles. In this sense, components are easier to reuse in different places of the application, as well as in different applications.
    http://raulbrito.github.io/articles/thoughts-on-ember-views-vs-components/

    Controller

    转到其它页面

    1
    2
    3
    4
    aController.transitionToRoute('blogPosts');
    aController.transitionToRoute('blogPosts.recentEntries');
    aController.transitionToRoute('blogPost', aPost);
    aController.transitionToRoute('blogPost', 1);

    转到指定的url在测试和调试的时候也可以使用,正式产品中还少用到。

    1
    2
    aController.transitionToRoute('/');
    aController.transitionToRoute('/blog/post/1/comment/13');

    访问其它Controller. needs属性

    1
    2
    3
    4
    5
    6
    7
    App.CommentsController = Ember.ArrayController.extend({
    needs: ['post'],
    postTitle: function(){
    var currentPost = this.get('controllers.post'); // instance of App.PostController
    return currentPost.get('title');
    }.property('controllers.post.title')
    });

    needs定义为此controller要访问的其它controllers的数组。
    嵌套的controller也可以访问:

    1
    2
    3
    4
    5
    6
    App.CommentsNewController = Ember.ObjectController.extend({
    });
    App.IndexController = Ember.ObjectController.extend({
    needs: ['commentsNew']
    });
    this.get('controllers.commentsNew'); // instance of App.CommentsNewController

    使用send调用定义的action

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    App.WelcomeRoute = Ember.Route.extend({
    actions: {
    playTheme: function() {
    this.send('playMusic', 'theme.mp3');
    },
    playMusic: function(track) {
    // ...
    }
    }
    });

    主动触发property变动事件

    propertyDidChange, propertyWillChange 即使你没有调用getset也能触发事件。

    Route

    一些钩子hook

    • activate Router进入此route时
    • beforeModel 在model之前调用
    • model 获取model数据
    • afterModel 当model获取到。 主要获取model时使用的是async/promise语法。
    • renderTemplate 渲染模版的钩子
    • setupController 为当前route设置钩子

    controllerFor

    在route中得到其它的controller对象。参数为the name of the route or controller。

    modelFor得到父Route的model

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    App.Router.map(function() {
    this.resource('post', { path: '/post/:post_id' }, function() {
    this.resource('comments');
    });
    });
    App.CommentsRoute = Ember.Route.extend({
    afterModel: function() {
    this.set('post', this.modelFor('post'));
    }
    });

    transitionTo, intermediateTransitionTo

    跳转到另外的route. The route may be either a single route or route path:

    1
    2
    3
    4
    this.transitionTo('blogPosts');
    this.transitionTo('blogPosts.recentEntries');
    this.transitionTo('blogPost', aPost);
    this.transitionTo('blogPost', 1);

    refresh 刷新本route及子route的model

    渲染非默认的模版

    1
    2
    3
    4
    5
    App.PostsRoute = Ember.Route.extend({
    renderTemplate: function() {
    this.render('favoritePost');
    }
    });

    Model

    使用Ember.Objectextend定义新类

    1
    2
    3
    4
    5
    App.Person = Ember.Object.extend({
    say: function(thing) {
    alert(thing);
    }
    });

    _super()父类的方法。

    创建实例: var person = App.Person.create();

    计算属性Computed Properties

    computed properties let you declare functions as properties.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    App.Person = Ember.Object.extend({
    // these will be supplied by `create`
    firstName: null,
    lastName: null,
    fullName: function() {
    return this.get('firstName') + ' ' + this.get('lastName');
    }.property('firstName', 'lastName')
    });
    var ironMan = App.Person.create({
    firstName: "Tony",
    lastName: "Stark"
    });
    ironMan.get('fullName'); // "Tony Stark"

    设置计算属性:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    App.Person = Ember.Object.extend({
    firstName: null,
    lastName: null,
    fullName: function(key, value, previousValue) {
    // 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')
    });

    @each计算

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    App.TodosController = Ember.Controller.extend({
    todos: [
    Ember.Object.create({ isDone: true }),
    Ember.Object.create({ isDone: false }),
    Ember.Object.create({ isDone: true })
    ],
    remaining: function() {
    var todos = this.get('todos');
    return todos.filterBy('isDone', false).get('length');
    }.property('todos.@each.isDone')
    });

    数组元素的每一个更新 (CUD或者数组重新被赋值)都会触发重新计算。

    observers 是同步的

    运行一次Ember.run.once

    Observers 在对象初始化之前不会被触发。如果想init时被触发,加上.on(\'init\')

    1
    2
    3
    4
    5
    6
    7
    8
    9
    App.Person = Ember.Object.extend({
    init: function() {
    this.set('salutation', "Mr/Ms");
    },
    salutationDidChange: function() {
    // some side effect of salutation changing
    }.observes('salutation').on('init')
    });

    增加observer:

    1
    2
    3
    person.addObserver('fullName', function() {
    // deal with the change
    });

    Ember.computed('aaa', function(){}), Ember.observer('aaa', function () {})

    可以写为 function(){}.property('aaaa'), function(){}.observers('aaa')

    reopen覆盖

    1
    2
    3
    4
    5
    6
    Person.reopen({
    isPerson: true,
    say: function(thing) {
    this._super(thing + "!");
    }
    });

    Person.create().get('isPerson') // true

    架构

    Store

    1
    2
    3
    App.Store = DS.Store.extend();
    App.register('store:main', App.Store);
    App.inject('view', 'store', 'store:main');

    model 和 record

    record是model的一个实例

    adapter

    负责将record和数据持久化后台结合起来。
    DS.RESTAdapter, FixtureAdapter

    Serializer

    A serializer is responsible for turning a raw JSON payload returned from your server into a record object.
    负责将一个纯的JSON转换成record对象。

    属性定义时,attr()类型可以不设置

    后台服务器返回啥就是啥。也可以指定类型。类型只能是string,number,booleandate
    Date遵循 ISO 8601. 例如: 2014-05-27T12:54:01。

    1
    2
    3
    firstName: attr(),
    lastName: attr(),
    birthday: DS.attr('date')

    One-to-One, One-to-Many和Many-to-Many. hasMany

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    App.User = DS.Model.extend({
    profile: DS.belongsTo('profile')
    });
    App.Profile = DS.Model.extend({
    user: DS.belongsTo('user')
    });
    App.Post = DS.Model.extend({
    comments: DS.hasMany('comment')
    });
    App.Comment = DS.Model.extend({
    post: DS.belongsTo('post')
    });
    App.Post = DS.Model.extend({
    tags: DS.hasMany('tag')
    });
    App.Tag = DS.Model.extend({
    posts: DS.hasMany('post')
    });
    var belongsTo = DS.belongsTo,
    hasMany = DS.hasMany;
    App.Comment = DS.Model.extend({
    onePost: belongsTo('post'),
    twoPost: belongsTo('post'),
    redPost: belongsTo('post'),
    bluePost: belongsTo('post')
    });
    App.Post = DS.Model.extend({
    comments: hasMany('comment', {
    inverse: 'redPost'
    })
    })

    增删改查

    1. createRecord
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    var store = this.store;
    var post = store.createRecord('post', {
    title: 'Rails is Omakase',
    body: 'Lorem ipsum'
    });
    store.find('user', 1).then(function(user) {
    post.set('author', user);
    });
    1. deleteRecord/save 或者 destroyRecord
      store.find('post', 1).then(function (post) {
      post.deleteRecord();
      post.get('isDeleted'); // => true
      post.save(); // => DELETE to /posts/1
      });

    // OR
    store.find('post', 2).then(function (post) {
    post.destroyRecord(); // => DELETE to /posts/2
    });

    1. push到store的缓存
    1
    2
    3
    4
    5
    6
    this.store.push('album', {
    id: 1,
    title: "Fewer Moving Parts",
    artist: "David Bazan",
    songCount: 10
    });
    1. save
    1
    2
    3
    4
    5
    6
    7
    8
    9
    var onSuccess = function(post) {
    self.transitionToRoute('posts.show', post);
    };
    var onFail = function(post) {
    // deal with the failure here
    };
    post.save().then(onSuccess, onFail);
    1. find
      根据参数内部用find,findAllfindQuery实现。
    1
    2
    3
    4
    var posts = this.store.find('post'); // => GET /posts
    var posts = this.store.all('post'); // => no network request
    var aSinglePost = this.store.find('post', 1); // => GET /posts/1
    var peters = this.store.find('person', { name: "Peter" }); // => GET to /persons?name='Peter'

    model的一些方法

    incrementProperty
    changedAttributes

    Adapter的 URL Conventions

    Action HTTP Verb URL
    Find GET /people/123
    Find All GET /people
    Update PUT /people/123
    Create POST /people
    Delete DELETE /people/123

    namespace

    1
    2
    3
    App.ApplicationAdapter = DS.RESTAdapter.extend({
    namespace: 'api/1'
    });

    Requests for App.Person would now target /api/1/people/1.

    normalizeHash

    如果想将JSON中的lastNameOfPerson关联model的lastName

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    App.Person = DS.Model.extend({
    lastName: DS.attr('string')
    });
    App.PersonSerializer = DS.RESTSerializer.extend({
    normalizeHash: {
    lastNameOfPerson: function(hash) {
    hash.lastName = hash.lastNameOfPerson;
    delete hash.lastNameOfPerson;
    return hash;
    }
    }
    });

    Adapter的其它属性

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    App.ApplicationAdapter = DS.RESTAdapter.extend({
    namespace: 'api/1'
    });
    App.ApplicationAdapter = DS.RESTAdapter.extend({
    host: 'https://api.example.com'
    });
    App.ApplicationAdapter = DS.RESTAdapter.extend({
    pathForType: function(type) {
    return Ember.String.underscore(type);
    }
    });
    MyCustomAdapterAdapter = DS.RESTAdapter.extend({
    defaultSerializer: '-default'
    });

    定制Transformations

    除了内建类型string, number, boolean, and date,你可以定义新类型。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    App.CoordinatePointTransform = DS.Transform.extend({
    serialize: function(value) {
    return [value.get('x'), value.get('y')];
    },
    deserialize: function(value) {
    return Ember.create({ x: value[0], y: value[1] });
    }
    });
    App.Cursor = DS.Model.extend({
    position: DS.attr('coordinatePoint')
    });

    数组类型

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    App.ArrayTransform = DS.Transform.extend({
    deserialize: function (serialized) {
    'use strict';
    return Ember.A(serialized);
    },
    serialize: function (deserialized) {
    'use strict';
    return deserialized ? deserialized.toArray() : [];
    }
    });

    View

    定义view

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    {{#view "clickable"}}
    This is a clickable area!
    {{/view}}
    App.ClickableView = Ember.View.extend({
    click: function(evt) {
    this.get('controller').send('turnItUp', 11);
    }
    });
    App.PlaybackController = Ember.ObjectController.extend({
    actions: {
    turnItUp: function(level){
    //Do your thing
    }
    }
    });

    定制view元素

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    App.MyView = Ember.View.extend({
    tagName: 'span'
    });
    App.MyView = Ember.View.extend({
    classNames: ['my-view']
    });
    App.MyView = Ember.View.extend({
    classNameBindings: ['isUrgent'],
    isUrgent: true
    });
    <div class="ember-view is-urgent">
    App.MyView = Ember.View.extend({
    classNameBindings: ['isUrgent:urgent'],
    isUrgent: true
    });
    App.MyView = Ember.View.extend({
    classNameBindings: ['isEnabled:enabled:disabled'],
    isEnabled: false
    });
    App.MyView = Ember.View.extend({
    tagName: 'a',
    attributeBindings: ['href'],
    href: "http://emberjs.com"
    });
    1
    2
    3
    {{view "info" tagName="span"}}
    {{view "info" id="info-view"}}
    {{view "alert" classBinding="isUrgent priority"}}

    内建的view

    • Ember.Checkbox
    • Ember.TextField
    • Ember.TextArea
    • Ember.Select

    Template

    数据枚举 Ember.Enumerable

    forEach

    1
    2
    3
    4
    [1,2,3].forEach(function(item) {
    console.log(item);
    console.log(item, this.indexOf(item));
    });

    可枚举的类型

    • Array: 实现纯javascript Array并且实现 Enumerable interface
    • Ember.ArrayController
    • Ember.Set

    toArray()得到纯javascript数组

    firstObject, lastObject

    MAP

    1
    2
    3
    4
    5
    6
    var words = ["goodbye", "cruel", "world"];
    var emphaticWords = words.map(function(item) {
    return item + "!";
    });
    // ["goodbye!", "cruel!", "world!"]
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    var hawaii = Ember.Object.create({
    capital: "Honolulu"
    });
    var california = Ember.Object.create({
    capital: "Sacramento"
    });
    var states = [hawaii, california];
    states.mapBy('capital');
    //=> ["Honolulu", "Sacramento"]

    filter

    1
    2
    3
    4
    5
    6
    7
    var arr = [1,2,3,4,5];
    arr.filter(function(item, index, self) {
    if (item < 4) { return true; }
    })
    // returns [1,2,3]
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    Todo = Ember.Object.extend({
    title: null,
    isDone: false
    });
    todos = [
    Todo.create({ title: 'Write code', isDone: true }),
    Todo.create({ title: 'Go to sleep' })
    ];
    todos.filterBy('isDone', true);
    // returns an Array containing only items with `isDone == true`

    Aggregate

    everysome判断集合所有元素或者部分元素是否满足条件。
    也可以

    1
    2
    people.isEvery('isHappy', true) // false
    people.isAny('isHappy', true) // true

    Helper

    bind class
    class可以是

    • a string return value of an object's property. <img \{\{bind-attr \class="view.someProperty}}>
    • a boolean return value of an object's property. true插入类名, false移除类名或替换类名 <img \{\{bind-attr \class="view.someBool:class-name-if-true"}}> <img \{\{bind-attr \class="view.someBool:class-name-if-true:class-name-if-false"}}>
    • a hard-coded value <img \{\{bind-attr \class=":class-name-to-always-apply"}}>

    复合型: <img \{\{bind-attr \class=":class-name-to-always-apply view.someBool:class-name-if-true view.someProperty"}}>

    each helper

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    Developers = [{name: 'Yehuda'},{name: 'Tom'}, {name: 'Paul'}];
    {{#each Developers}}
    {{name}}
    {{/each}}
    {{#each person in Developers}}
    {{person.name}}
    {{/each}}
    {{#each DeveloperNames}}
    {{this}}
    {{/each}}

    集合为空时使用\{\{#each}}

    1
    2
    3
    4
    5
    {{#each person in Developers}}
    {{person.name}}
    {{else}}
    <p>Sorry, nobody is available for this task.</p>
    {{/each}}

    指定渲染用的view

    1
    2
    3
    {{#view App.MyView }}
    {{each view.items itemView\class="App.AnItemView"}}
    {{/view}}

    默认遍历controller的属性,可以指定itemController:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    App.DeveloperController = Ember.ObjectController.extend({
    isAvailableForHire: function() {
    return !this.get('model.isEmployed') && this.get('model.isSeekingWork');
    }.property('isEmployed', 'isSeekingWork')
    })
    {{#each person in developers itemController="developer"}}
    {{person.name}} {{#if person.isAvailableForHire}}Hire me!{{/if}}
    {{/each}}

    if-else

    1
    2
    3
    4
    5
    {{#if message.isTypeSuccess}}
    ......
    {{else}}
    ......
    {{/if}}

    另外还提供了unless

    1
    2
    3
    {{#unless hasPaid}}
    You owe: ${{total}}
    {{/unless}}

    outlet

    一个模版占位符。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    {{outlet 'favoritePost'}}
    {{outlet 'posts'}}
    App.PostsRoute = Ember.Route.extend({
    renderTemplate: function() {
    this.render('favoritePost', { outlet: 'favoritePost' });
    this.render('posts', { outlet: 'posts' });
    }
    });
    1
    2
    3
    4
    5
    6
    {{outlet view='sectionContainer'}}
    App.SectionContainer = Ember.ContainerView.extend({
    tagName: 'section',
    classNames: ['special']
    });

    partial 渲染另外一个页面而不改变上下文

    1
    2
    {{foo}}
    {{partial "nav"}}

    render 使用包含的上下文

    {{render "navigation"}}

    Calling {{render}} from within a template will insert another template that matches the provided name. The inserted template will access its properties on its own controller (rather than the controller of the parent template).

    view

    {{view}} inserts a new instance of an Ember.View into a template passing its options to the Ember.View's create method and using the supplied block as the view's own template.

    view, partialrender的区别

    General

    Helper Template Model View Controller
    {{partial}} Specified Template Current Model Current View Current Controller
    {{view}} View's Template Current Model Specified View Current Controller
    {{render}} View's Template Specified Model Specified View Specified Controller

    Specific

    Helper Template Model View Controller
    {{partial "author"}} author.hbs Post App.PostView App.PostController
    {{view "author"}} author.hbs Post App.AuthorView App.PostController
    {{render "author" author}} author.hbs Author App.AuthorView App.AuthorController

    with changes scope, as

    1
    2
    3
    {{#with person}}
    Welcome back, <b>{{firstName}} {{lastName}}</b>!
    {{/with}}

    link-to (routeName, context, options) String
    提供tagName: \{\{#link-to 'photoGallery' tagName="li"}} Great Hamster Photos\{\{/link-to}}
    自动添加\class="active"
    指定model: \{\{#link-to 'photoGallery' aPhoto}} \{\{aPhoto.title}}\{\{/link-to}}, for dynamic segments或者多个model或者model id: \{\{#link-to 'photoGallery' aPhotoId}} \{\{aPhoto.title}}\{\{/link-to}}

    input

    \{\{input type="text" value=firstName disabled=entryNotAllowed size="50"}}
    支持action: \{\{input action="submit"}}
    支持的actions: enter insert-newline escape-press focus-\in focus-out key-press\{\{input focus-\in="alertMessage"}}

    checkbox: \{\{input type="checkbox" name="isAdmin"}}

    自定义helper

    简单语法

    1
    2
    3
    4
    Ember.Handlebars.helper('highlight', function(value, options) {
    var escaped = Handlebars.Utils.escapeExpression(value);
    return new Ember.Handlebars.SafeString('<span \class="highlight">' + escaped + '</span>');
    });

    使用\{\{highlight name}}

    依赖参数

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

    使用{{fullName person}}

    定制view helper

    1
    Ember.Handlebars.helper('calendar', App.CalendarView);

    使用\{\{calendar}}或者\{\{view "calendar"}}

    Component

    HTML代码代替hbs模版

    在Component和View的实现时,可以直接使用Ember.Handlebars.compile直接写HTML代码,不用定义一个hbs模版文件。

    1
    template: Ember.Handlebars.compile("Greetings {{name}}")

    定义组件

    1. template必须以components/开头。比如组件0的模版为components/blog-post
    2. 组件名必须包含破折号dash 。blog-post合法但是post不合法。
    3. 继承Ember.Component或子类。 blog-post的类为App.BlogPostComponent

    组件的钩子(Hook)函数

    传给组件参数

    \{\{blog-post title=name}}, name是外部对象的属性
    一句话componentProperty=outerProperty

    定制组件的属性

    类似view。

    从组件发送action给其它应用

    When a component is used inside a template, it has the ability to send actions to that template's controller and routes. These allow the component to inform the application when important events, such as the user clicking a particular element in a component, occur.
    组件\{\{my-button action="showUser"}}

    1
    2
    3
    4
    5
    App.MyButtonComponent = Ember.Component.extend({
    click: function() {
    this.sendAction();
    }
    });

    `this.sendAction('action', param1, param2);

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    App.ConfirmButtonComponent = Ember.Component.extend({
    actions: {
    showConfirmation: function() {
    this.toggleProperty('isShowingConfirmation');
    },
    confirm: function() {
    this.toggleProperty('isShowingConfirmation');
    this.sendAction('deleteAction', this.get('param'));
    }
    }
    });
    1
    2
    3
    4
    5
    6
    7
    {{! templates/components/confirm-button.handlebars }}
    {{#if isShowingConfirmation}}
    <button {{action "confirm"}}>Click again to confirm</button>
    {{else}}
    <button {{action "showConfirmation"}}>{{title}}</button>
    {{/if}}
    1
    2
    3
    4
    5
    {{! index.handlebars }}
    {{#each todo in todos}}
    <p>{{todo.title}} {{confirm-button title="Delete" deleteAction="deleteTodo" param=todo}}</p>
    {{/each}}

    多action: \{\{user-form submit="createUser" cancel="cancelUserCreation"}}