diff --git a/Rakefile b/Rakefile index e15761d..6386a4c 100644 --- a/Rakefile +++ b/Rakefile @@ -31,6 +31,10 @@ def ember_path File.expand_path(ENV['EMBER_PATH'] || File.expand_path("../../ember.js", __FILE__)) end +def ember_data_path + File.expand_path(ENV['EMBER_DATA_PATH'] || File.expand_path("../../ember-data", __FILE__)) +end + def generate_docs print "Generating docs data from #{ember_path}... " diff --git a/data/guides.yml b/data/guides.yml index d21e316..130b99e 100644 --- a/data/guides.yml +++ b/data/guides.yml @@ -152,34 +152,26 @@ 模型: - title: "介绍" url: "models" - warning: "ember-data" - - title: "创建一个存储器" - url: "models/defining-a-store" - warning: "ember-data" + - title: "Using the Store" + url: "models/using-the-store" - title: "定义模型" url: "models/defining-models" - warning: "ember-data" - - title: "查找模型" - url: "models/finding-models" - warning: "ember-data" - - title: "修改属性" - url: "models/modifying-attributes" - warning: "ember-data" - #- title: "Modifying Relationships" - #url: "models/modifying-relationships" - #warning: "ember-data" - - title: "模型生命周期" - url: "models/model-lifecycle" - warning: "ember-data" - - title: "The Fixture Adapter" - url: "models/the-fixture-adapter" - warning: "ember-data" - - title: "REST适配器" - url: "models/the-rest-adapter" - warning: "ember-data" - #- title: "The Basic Adapter" - #url: "modells/the-basic-adapter" - #warning: "ember-data" + - title: "Finding a Record" + url: "models/finding-a-record" + - title: "Finding All Records of a Type" + url: "models/finding-all-records-of-a-type" + - title: "Pushing Records into the Store" + url: "models/pushing-records-into-the-store" + - title: "Querying the Server for Records" + url: "models/querying-the-server-for-records" + # - title: "Filtering Records" + # url: "models/filtering-records" + - title: "Working with Records" + url: "models/working-with-records" + - title: "Connecting to an HTTP Server" + url: "models/connecting-to-an-http-server" + - title: "Frequently Asked Questions" + url: "models/frequently-asked-questions" 视图: - title: "介绍" @@ -228,7 +220,6 @@ Testing: url: "configuring-ember/disabling-prototype-extensions" - title: "嵌入式应用" url: "configuring-ember/embedding-applications" - Cookbook: - title: "Introduction" url: "cookbook" @@ -347,7 +338,9 @@ Cookbook: url: "understanding-ember/keeping-templates-up-to-date" - title: "调试" url: "understanding-ember/debugging" - +Contributing to Ember.js: + - title: "Adding New Features" + url: "contributing/adding-new-features" #Understanding Ember Data: #- title: "Record Lifecycle" #url: "understanding-ember-data/record-lifecycle" diff --git a/lib/toc.rb b/lib/toc.rb index 188947e..354fff0 100644 --- a/lib/toc.rb +++ b/lib/toc.rb @@ -246,13 +246,13 @@ def warning WARNINGS = { "ember-data"=> %Q{
-

+

警告: ember-data处在快速成长的开发阶段。
使用的时候请自己关注ember-data的最新动态!!!
-

+
} } diff --git a/source/bilingual_guides/components/defining-a-component.md b/source/bilingual_guides/components/defining-a-component.md index d07e8e3..1f32e9b 100644 --- a/source/bilingual_guides/components/defining-a-component.md +++ b/source/bilingual_guides/components/defining-a-component.md @@ -3,11 +3,19 @@ 中英对照:[http://emberjs.cn/bilingual_guides/components/defining-a-component/](http://emberjs.cn/bilingual_guides/components/defining-a-component/) To define a component, create a template whose name starts with -`components/`. To define a new component `{{blog-post}}`, for example, +`components/`. To define a new component, `{{blog-post}}` for example, create a `components/blog-post` template. 为了定义一个组件,需要先创建一个名字以`components/`开始的模板。例如:如果需要定义一个新组建`{{blog-post}}`,需要创建`components/blog-post`模板。 + + If you are including your Handlebars templates inside an HTML file via ` -Each component, under the hood, is backed by an element. By default, +Each component, under the hood, is backed by an element. By default Ember will use a `
` element to contain your component's template. To learn how to change the element Ember uses for your component, see [Customizing a Component's Element](/guides/components/customizing-a-components-element). @@ -59,7 +67,7 @@ created. 通常情况下,组件只封装一些会被不停的重复使用的Handlebars模板片段。在这些情况下,不需要编写任何Javascript代码,只需要像之前所述,定义好Handlebars模板,就能使用组件了。 -If you need to customize the behavior of the component, however, you'll +If you need to customize the behavior of the component you'll need to define a subclass of `Ember.Component`. For example, you would need a custom subclass if you wanted to change a component's element, respond to actions from the component's template, or manually make diff --git a/source/bilingual_guides/components/passing-properties-to-a-component.md b/source/bilingual_guides/components/passing-properties-to-a-component.md index baac804..70a452b 100644 --- a/source/bilingual_guides/components/passing-properties-to-a-component.md +++ b/source/bilingual_guides/components/passing-properties-to-a-component.md @@ -2,7 +2,7 @@ 中英对照:[http://emberjs.cn/bilingual_guides/components/passing-properties-to-a-component/](http://emberjs.cn/bilingual_guides/components/passing-properties-to-a-component/) -By default, a component does not have access to properties in the +By default a component does not have access to properties in the template scope in which it is used. 默认情况下,组件不能访问模板作用域下的属性。 diff --git a/source/bilingual_guides/concepts/naming-conventions.md b/source/bilingual_guides/concepts/naming-conventions.md index 1b6fb9b..ec09a9b 100644 --- a/source/bilingual_guides/concepts/naming-conventions.md +++ b/source/bilingual_guides/concepts/naming-conventions.md @@ -133,7 +133,7 @@ Here's an example: App.FavoritesRoute = Ember.Route.extend({ model: function() { // the model is an Array of all of the posts - return App.Post.find(); + return this.get('store').find('post'); } }); ``` @@ -211,7 +211,7 @@ generating a link for a model object). ```javascript App.PostRoute = Ember.Route.extend({ model: function(params) { - return App.Post.find(params.post_id); + return this.get('store').find('post', params.post_id); }, serialize: function(post) { diff --git a/source/bilingual_guides/getting-started/accepting-edits.md b/source/bilingual_guides/getting-started/accepting-edits.md index f527c85..ccff257 100644 --- a/source/bilingual_guides/getting-started/accepting-edits.md +++ b/source/bilingual_guides/getting-started/accepting-edits.md @@ -8,7 +8,7 @@ In the previous step we updated TodoMVC to allow a user to toggle the display of 在上一步中我们修改了TodoMVC使其可以支持用户能够切换到一个文本输入框``来编辑一个待办事项的标题。接下来,我们将实现在``显示时立即当前焦点移至其上,开始接收用户的输入,并在用户按下``键时或把焦点从编辑的``元素中移出时,将用户的修改持久化,并显示待办事项修改后的标题。 -To accomplish this, we'll create a new custom component and register it with Handelbars to make it available to our templates. +To accomplish this, we'll create a new custom component and register it with Handlebars to make it available to our templates. 为了实现这一步,可以创建一个自定义的组件并且通过Handlebars来注册它,使得其在模板中可用。 @@ -59,7 +59,7 @@ In `index.html` replace the static `` element with our custom `{{edit-tod ``` -Pressing the `` key will trigger the `acceptChanges` event on the instance of `TodoController`. Moving focus away from from the `` will trigger the `focus-out` event, calling a method `acceptChanges` on this view's instance of `TodoController`. +Pressing the `` key will trigger the `acceptChanges` event on the instance of `TodoController`. Moving focus away from the `` will trigger the `focus-out` event, calling a method `acceptChanges` on this view's instance of `TodoController`. 点击``键会触发`TodoController`实例的`acceptChanges`事件。焦点离开``时会出发`focus-out`事件,并调用视图的`TodoController`实例的`acceptChanges`方法。 diff --git a/source/bilingual_guides/getting-started/creating-a-new-model.md b/source/bilingual_guides/getting-started/creating-a-new-model.md index fccde52..ebb8530 100644 --- a/source/bilingual_guides/getting-started/creating-a-new-model.md +++ b/source/bilingual_guides/getting-started/creating-a-new-model.md @@ -73,7 +73,6 @@ In `index.html` include `js/controllers/todos_controller.js` as a dependency: ```html - diff --git a/source/bilingual_guides/getting-started/displaying-the-number-of-incomplete-todos.md b/source/bilingual_guides/getting-started/displaying-the-number-of-incomplete-todos.md index 9f8e529..4f903bf 100644 --- a/source/bilingual_guides/getting-started/displaying-the-number-of-incomplete-todos.md +++ b/source/bilingual_guides/getting-started/displaying-the-number-of-incomplete-todos.md @@ -23,6 +23,8 @@ Implement these properties as part of this template's controller, the `Todos.Tod 在模板的控制器`Todos.TodosController`中实现以上的属性: ```javascript +// Hint: these lines MUST NOT go into the 'actions' object. +// 提示:下面的代码不能放入'actions' // ... additional lines truncated for brevity ... // ... 为保持代码简洁,在此省略了其他代码 ... remaining: function () { diff --git a/source/bilingual_guides/getting-started/index.md b/source/bilingual_guides/getting-started/index.md index 8aa5301..c0d4036 100644 --- a/source/bilingual_guides/getting-started/index.md +++ b/source/bilingual_guides/getting-started/index.md @@ -4,4 +4,4 @@ Welcome to Ember.js! This guide will take you through creating a simple applicat In this guide we will walk through the steps of building the popular [TodoMVC demo application](http://addyosmani.github.com/todomvc/). -本入门指南采用非常流行的[TodoMVC示例](http://addyosmani.github.com/todomvc/) 应用作为例子,介绍如何使用Ember.js来开发TodoMVC。 +本入门指南采用非常流行的[TodoMVC示例](http://todomvc.com) 应用作为例子,介绍如何使用Ember.js来开发TodoMVC。 diff --git a/source/bilingual_guides/getting-started/marking-a-model-as-complete-incomplete.md b/source/bilingual_guides/getting-started/marking-a-model-as-complete-incomplete.md index 0208e0d..94bb8ff 100644 --- a/source/bilingual_guides/getting-started/marking-a-model-as-complete-incomplete.md +++ b/source/bilingual_guides/getting-started/marking-a-model-as-complete-incomplete.md @@ -43,10 +43,10 @@ Todos.TodoController = Ember.ObjectController.extend({ var model = this.get('model'); if (value === undefined) { - // property being used as getter + // property being used as a getter return model.get('isCompleted'); } else { - // property being used as setter + // property being used as a setter model.set('isCompleted', value); model.save(); return value; @@ -55,10 +55,14 @@ Todos.TodoController = Ember.ObjectController.extend({ }); ``` -When called from the template to display the current `isCompleted` state of the todo, this property will proxy that question to its underlying `model`. When called with a value because a user has toggled the checkbox in the template, this property will set the `isCompleted` property of its `model` to the passed value (`true` or `false`), persist the model update, and return the passed value so the checkbox will display correctly. +When called from the template to display the current `isCompleted` state of the todo, this property will proxy that question to its underlying `model`. When called with a value because a user has toggled the checkbox in the template, this property will set the `isCompleted` property of its `model` to the passed value (`true` or `false`), persist the model update, and return the passed value so the checkbox will display correctly. + +The `isCompleted` function is marked a [computed property](/guides/object-model/computed-properties/) whose value is dependent on the value of `model.isCompleted`. 当模板中需要显示待办事项的当前`isCompleted`状态,这个属性将这个问题委派给其底层的`model`。当被调用时因为用户触发了模板中的复选框而带有一个参数,那么这个属性将设置`model`的`isCompleted`属性为传入的参数值(`true`或者`false`),并将模型的变更持久化,返回传入的值以便复选框显示正确。 +`isCompleted`函数被声明为一个[计算属性](/guides/object-model/computed-properties/),其值依赖于`model.isCompleted`。 + In `index.html` include `js/controllers/todo_controller.js` as a dependency: 在`index.html`中包含`js/controllers/todo_controller.js`依赖: diff --git a/source/bilingual_guides/getting-started/modeling-data.md b/source/bilingual_guides/getting-started/modeling-data.md index a354931..8c71a70 100644 --- a/source/bilingual_guides/getting-started/modeling-data.md +++ b/source/bilingual_guides/getting-started/modeling-data.md @@ -51,11 +51,11 @@ Reload your web browser to ensure that all files have been referenced correctly ### Additional Resources * [Changes in this step in `diff` format](https://github.com/emberjs/quickstart-code-sample/commit/a1ccdb43df29d316a7729321764c00b8d850fcd1) - * [Defining A Store Guide](/guides/models/defining-a-store) + * [Using the Store Guide](/guides/models/using-the-store) * [Defining Models Guide](/guides/models/defining-models) ### 附加资源 * [采用`diff`格式显示这步骤所作的修改](https://github.com/emberjs/quickstart-code-sample/commit/a1ccdb43df29d316a7729321764c00b8d850fcd1) - * [创建一个存储器指南](/guides/models/defining-a-store) + * [Store使用指南](/guides/models/using-the-store) * [定义模型指南](/guides/models/defining-models) diff --git a/source/bilingual_guides/getting-started/obtaining-emberjs-and-dependencies.md b/source/bilingual_guides/getting-started/obtaining-emberjs-and-dependencies.md index 68805db..2fd5ae1 100644 --- a/source/bilingual_guides/getting-started/obtaining-emberjs-and-dependencies.md +++ b/source/bilingual_guides/getting-started/obtaining-emberjs-and-dependencies.md @@ -4,7 +4,7 @@ ## 获取EMBER.JS和相应依赖 -TodoMVC has a few dependecnies: +TodoMVC has a few dependencies: TodoMVC的依赖: @@ -19,9 +19,9 @@ For this example, all of these resources should be stored in the folder `js/libs ```html - - - + + + diff --git a/source/bilingual_guides/getting-started/toggle-all-todos.md b/source/bilingual_guides/getting-started/toggle-all-todos.md index 5813b83..fbfe03a 100644 --- a/source/bilingual_guides/getting-started/toggle-all-todos.md +++ b/source/bilingual_guides/getting-started/toggle-all-todos.md @@ -1,6 +1,6 @@ ## Toggling All Todos Between Complete and Incomplete -TodoMVC allows user to toggle all existing todos into either a complete or incomplete state. It uses the same checkbox that becomes checked when all todos are completed and unchecked when one or more todos remain incomplete. +TodoMVC allows users to toggle all existing todos into either a complete or incomplete state. It uses the same checkbox that becomes checked when all todos are completed and unchecked when one or more todos remain incomplete. To implement this behavior update the `allAreDone` property in `js/controllers/todos_controller.js` to handle both getting and setting behavior: diff --git a/source/bilingual_guides/getting-started/using-other-adapters.md b/source/bilingual_guides/getting-started/using-other-adapters.md index 81167cb..f75ba7a 100644 --- a/source/bilingual_guides/getting-started/using-other-adapters.md +++ b/source/bilingual_guides/getting-started/using-other-adapters.md @@ -19,7 +19,7 @@ In `index.html` include `js/libs/local_storage_adapter.js` as a dependency: ```html - + ``` diff --git a/source/bilingual_guides/models/connecting-to-a-streaming-api.md b/source/bilingual_guides/models/connecting-to-a-streaming-api.md new file mode 100644 index 0000000..e69de29 diff --git a/source/bilingual_guides/models/defining-a-store.md b/source/bilingual_guides/models/defining-a-store.md deleted file mode 100644 index 780d39a..0000000 --- a/source/bilingual_guides/models/defining-a-store.md +++ /dev/null @@ -1,46 +0,0 @@ -英文原文:[http://emberjs.com/guides/models/defining-a-store/](http://emberjs.com/guides/models/defining-a-store/) - -## 创建一个存储器(Defining a Store) - -Every application using Ember Data has a store. The store is the -repository that holds loaded models, and is responsible for retrieving -models that have not yet been loaded. - -每一个使用 Ember Data 的应用都会有一个存储器。这个存储器会成为已加载模型的贮存器,并且检索还未被加载的模型。 - -Typically, you will interact with models directly, not the store. -However, you do need to tell Ember.js that you want Ember Data to manage -your models. To do so, simply define a subclass of `DS.Store` on your -`Ember.Application`: - -通常,你可以直接跟模型交互,而不使用存储器。但是你需要让 Ember.js 知道你现在在使用 Ember Data 来管理你的模型。 -要达到这样的目的,我们可以简单地在Ember.Application中定义一个DS.Store的子类,如下所示: - -```js -App.Store = DS.Store.extend({ - revision: 13 -}); -``` - -Note the `revision` property defined here. This is the API revision -number, and is used by Ember Data to notify you of breaking changes to -the public API. This will be removed once Ember Data reaches 1.0. See -the [Breaking Changes document][1] for more information. - -注意这里的`revision`属性,它是 API 的修订版本号,在Ember Data 1.0 版本之前用来提醒你公共 API 的重大更改。 -查看[Breaking Changes 文档][1]以获取更多信息。 - -[1]: https://github.com/emberjs/data/blob/master/BREAKING_CHANGES.md - -If you want to customize the store, you can do so when creating the -subclass. For example, if you wanted to use an adapter other than the -default `DS.RESTAdapter`, you would do so like this: - -如果你想要定制存储器,你可以在创建子类的时候去定制。例如,如果你想要用另外一个适配器去替换默认的`DS.RESTAdapter`,你可以像下面这样做: - -```js -App.Store = DS.Store.extend({ - revision: 13, - adapter: 'App.MyCustomAdapter' -}); -``` diff --git a/source/bilingual_guides/models/defining-models.md b/source/bilingual_guides/models/defining-models.md index 40b64bf..17fd1f1 100644 --- a/source/bilingual_guides/models/defining-models.md +++ b/source/bilingual_guides/models/defining-models.md @@ -1,29 +1,73 @@ 英文原文:[http://emberjs.com/guides/models/defining-models/](http://emberjs.com/guides/models/defining-models/) -## 定义模型(Defining Models) +A model is a class that defines the properties and behavior of the +data that you present to the user. Anything that the user expects to see +if they leave your app and come back later (or if they refresh the page) +should be represented by a model. -For every type of model you'd like to represent, create a new subclass of -`DS.Model`: - -你可以通过创建`DS.Model`的子类来表示每一种类型的模型: +For every model in your application, create a subclass of `DS.Model`: ```javascript App.Person = DS.Model.extend(); ``` -### 属性标志(Attributes) +After you have defined a model class, you can start finding and creating +records of that type. When interacting with the store, you will need to +specify a record's type using the model name. For example, the store's +`find()` method expects a string as the first argument to tell it what +type of record to find: -You can specify which attributes a model has by using `DS.attr`. You can -use attributes just like any other property, including as part of a -computed property. +```js +store.find('person', 1); +``` -你可以通过`DS.attr`指定模型具有哪些属性。你可以像其他属性一样使用属性标志,包括作为计算属性的一部分。 +The table below shows how model names map to model classes. + + + + + + + + + + + + + + + + +
Model NameModel Class
photoApp.Photo
admin-user-profileApp.AdminUserProfile
+ +### Defining Attributes + +You can specify which attributes a model has by using `DS.attr`. ```javascript +var attr = DS.attr; + App.Person = DS.Model.extend({ - firstName: DS.attr('string'), - lastName: DS.attr('string'), - birthday: DS.attr('date'), + firstName: attr(), + lastName: attr(), + birthday: attr() +}); +``` + +Attributes are used when turning the JSON payload returned from your +server into a record, and when serializing a record to save back to the +server after it has been modified. + +You can use attributes just like any other property, including as part of a +computed property. Frequently, you will want to define computed +properties that combine or transform primitive attributes. + +```javascript +var attr = DS.attr; + +App.Person = DS.Model.extend({ + firstName: attr(), + lastName: attr(), fullName: function() { return this.get('firstName') + ' ' + this.get('lastName'); @@ -31,7 +75,19 @@ App.Person = DS.Model.extend({ }); ``` -By default, the REST adapter supports attribute types of `string`, +For more about adding computed properties to your classes, see [Computed Properties](/guides/object-model/computed-properties). + +If you don't specify the type of the attribute, it will be whatever was +provided by the server. You can make sure that an attribute is always +coerced into a particular type by passing a `type` option to `attr`: + +```js +App.Person = DS.Model.extend({ + birthday: DS.attr('date') +}); +``` + +The default adapter supports attribute types of `string`, `number`, `boolean`, and `date`. Custom adapters may offer additional attribute types, and new types can be registered as transforms. See the [documentation section on the REST Adapter](/guides/models/the-rest-adapter). @@ -40,7 +96,7 @@ attribute types, and new types can be registered as transforms. See the 传统的适配器会提供额外的属性类型,并支持你注册自定义的属性类型。 详情请查看[documentation section on the REST Adapter](/guides/models/the-rest-adapter)。 -### 关联模型(Relationships) +### 定义关联模型(Defining Relationships) Ember Data includes several built-in relationship types to help you define how your models relate to each other. @@ -56,11 +112,11 @@ To declare a one-to-one relationship between two models, use ```js App.User = DS.Model.extend({ - profile: DS.belongsTo('App.Profile') + profile: DS.belongsTo('profile') }); App.Profile = DS.Model.extend({ - user: DS.belongsTo('App.User') + user: DS.belongsTo('user') }); ``` @@ -73,11 +129,11 @@ To declare a one-to-many relationship between two models, use ```js App.Post = DS.Model.extend({ - comments: DS.hasMany('App.Comment') + comments: DS.hasMany('comment') }); App.Comment = DS.Model.extend({ - post: DS.belongsTo('App.Post') + post: DS.belongsTo('post') }); ``` @@ -90,51 +146,41 @@ To declare a many-to-many relationship between two models, use ```js App.Post = DS.Model.extend({ - tags: DS.hasMany('App.Tag') + tags: DS.hasMany('tag') }); App.Tag = DS.Model.extend({ - posts: DS.hasMany('App.Post') + posts: DS.hasMany('post') }); ``` #### Explicit Inverses #### 显式反转 -From [This Week in Ember.JS, posted November 2, 2012](http://emberjs.com/blog/2012/11/02/this-week-in-ember-js.html) - -自[本周Ember.js,2012-11-2](http://emberjs.com/blog/2012/11/02/this-week-in-ember-js.html)起, +Ember Data will do its best to discover which relationships map to one +another. In the one-to-many code above, for example, Ember Data can figure out that +changing the `comments` relationship should update the `post` +relationship on the inverse because `post` is the only relationship to +that model. -Ember Data has always been smart enough to know that when you set a -`belongsTo` relationship, the child record should be added to the -parent's corresponding `hasMany` relationship. - -Ember Data知道当设定一个`belongsTo`的关联关系时,子记录应该要被添加到对应的父的`hasMany`关联关系中去。 - -Unfortunately, it was pretty braindead about *which* `hasMany` -relationship it would update. Before, it would just pick the first -relationship it found with the same type as the child. - -但是不幸的是,它并不知道哪一个`hasMany`关联关系应该得到更新。因此,Ember -Data选择其找到的第一个拥有相同类型的子关联来进行更新。 - -Because it's reasonable for people to have multiple -`belongsTo`/`hasMany`s for the same type, we added support for -specifying an inverse: - -因为可能存在许多具有相同类型的`belongsTo`/`hasMany`,这是可以显式的指定对应的反转对象: +However, sometimes you may have multiple `belongsTo`/`hasMany`s for the +same type. You can specify which property on the related model is the +inverse using `DS.attr`'s `inverse` option: ```javascript +var belongsTo = DS.belongsTo, + hasMany = DS.hasMany; + App.Comment = DS.Model.extend({ - onePost: DS.belongsTo("App.Post"), - twoPost: DS.belongsTo("App.Post"), - redPost: DS.belongsTo("App.Post"), - bluePost: DS.belongsTo("App.Post") + onePost: belongsTo("post"), + twoPost: belongsTo("post"), + redPost: belongsTo("post"), + bluePost: belongsTo("post") }); App.Post = DS.Model.extend({ - comments: DS.hasMany('App.Comment', { + comments: hasMany('comment', { inverse: 'redPost' }) }); @@ -164,7 +210,7 @@ load the data with the embedded structure. App.Comment = DS.Model.extend({}); App.Post = DS.Model.extend({ - comments: DS.hasMany('App.Comment') + comments: DS.hasMany('comment') }); App.Adapter.map('App.Post', { diff --git a/source/bilingual_guides/models/filtering-records.md b/source/bilingual_guides/models/filtering-records.md new file mode 100644 index 0000000..e69de29 diff --git a/source/bilingual_guides/models/finding-a-record.md b/source/bilingual_guides/models/finding-a-record.md new file mode 100644 index 0000000..4f22242 --- /dev/null +++ b/source/bilingual_guides/models/finding-a-record.md @@ -0,0 +1,45 @@ +## Finding a Record + +## 查找一个对象 + +You can retrieve a record by passing its model and unique ID to the `find()` method. This will return a promise that resolves to the requested record: + +通过将记录对应的模型和唯一ID传递给`find()`方法,可以获取记录。这将返回一个承诺,并解析为请求的记录: + +```js +store.find('post', 1).then(function(post) { + post.set('title', "My Dark Twisted Fantasy"); +}); +``` + +### Integrating with the Route's Model Hook + +As discussed in [Specifying a Route's +Model](/guides/routing/specifying-a-routes-model), routes are +responsible for telling their template which model to render. + +`Ember.Route`'s `model` hook supports asynchronous values +out-of-the-box. If you return a promise from the `model` hook, the +router will wait until the promise has resolved to render the +template. + +This makes it easy to write apps with asynchronous data using Ember +Data. Just return the requested record from the `model` hook, and let +Ember deal with figuring out whether a network request is needed or not: + +```js +App.Router.map(function() { + this.resource('post', { path: ':post_id' }); +}); + +App.PostRoute = Ember.Route.extend({ + model: function(params) { + return this.get('store').find('post', params.post_id); + } +}); +``` + +In fact, this pattern is so common, the above `model` hook is the +default implementation for routes with a dynamic segment. If you're +using Ember Data, you only need to override the `model` hook if you need +to return a model different from the record with the provided ID. diff --git a/source/bilingual_guides/models/finding-models.md b/source/bilingual_guides/models/finding-models.md deleted file mode 100644 index e9774a0..0000000 --- a/source/bilingual_guides/models/finding-models.md +++ /dev/null @@ -1,72 +0,0 @@ -## Finding Models - -## 查找模型 - -You can retrieve a record by passing its unique ID to the `find()` method: - -通过将记录的唯一ID传递给`find()`方法,可以获取记录: - -```js -var post = App.Post.find(1); -``` - -If a record with that ID has already been created, it will be returned -immediately. This feature is sometimes called an _identity map_. - -如果该ID的记录已经被创建,那么它将被立即返回。这个特性被称为 _标示符映射_ 。 - -Otherwise, a new record will be created and put into the loading -state, then returned. Feel free to use this record in templates; because -everything in Ember.js is bindings-aware, templates will automatically -update as soon as the record finishes loading. - -否则,一个新的记录将被创建,其状态将被设置为加载中,并且返回。返回的记录可以在模板文件中使用;因为Ember.js中一切都是支持绑定的,因此模板在记录完成加载后会自动得到更新。 - -### Finding All Records - -### 查找所有记录 - -You can find all records for a given model by calling `find()` without -arguments: - -如果调用`find()`方法时不带任何参数,将返回该模型的所有记录: - -```js -var posts = App.Post.find(); -``` - -This will return an instance of `DS.RecordArray`. Like with records, the -record array will start in a loading state with a `length` of `0`, but -you can immediately use it in your templates. When the server responds -with results, the templates will watch for changes in the length of the -array and update themselves automatically. - -这将返回一个`DS.RecordArray`的实例。跟记录一样,记录数组开始的时候状态也是加载中,并且`length`为0,但是已经可以在模板文件中使用了。当服务端返回结果时,模板会监听数组长度的变化,从而自动的更新自己的内容。 - -Note: `DS.RecordArray` is not a JavaScript array, it is an object that -implements `Ember.Enumerable`. If you want to, for example, retrieve -records by index, you must use the `objectAt(index)` method. Since the -object is not a JavaScript array, using the `[]` notation will not work. -For more information, see [Ember.Enumerable][1] and [Ember.Array][2]. - -注意:`DS.RecordArray`不是一个Javascript数组,它是一个实现了`Ember.Enumerable`接口的对象。如果需要通过索引来获取对象,可以使用`objectAt(index)`方法。由于该对象不是Javascript数组,因此不能使用`[]`来取其中的元素。更多详细信息请查看[Ember.Enumerable][1]和[Ember.Array][2]。 - -[1]: http://emberjs.com/api/classes/Ember.Enumerable.html -[2]: http://emberjs.com/api/classes/Ember.Array.html - -### Queries - -### 查询 - -You can query the server by passing a hash to `find()`. - -通过将一个哈希传递给`find()`方法,可以实现查询: - -```js -var people = App.Person.find({ name: "Peter" }); -``` - -The contents of the hash is opaque to Ember Data; it is up to your -server to interpret it and return a list of records. - -哈希的内容对于Ember Data来说是透明的,它将被服务器端解析并返回对应的记录。 diff --git a/source/bilingual_guides/models/frequently-asked-questions.md b/source/bilingual_guides/models/frequently-asked-questions.md new file mode 100644 index 0000000..9db8387 --- /dev/null +++ b/source/bilingual_guides/models/frequently-asked-questions.md @@ -0,0 +1,87 @@ +#### Should I use a query or a filter to search records? + +It depends on how many records you want to search and whether they have +been loaded into the store. + +_Queries_ are useful for doing searches of hundreds, thousands, or even +millions of records. You just hand the search options to your server, +and it is responsible for handing you back the list of records that +match. Because the response from the server includes the ID of all of +the records that matched, it doesn't matter if the store hadn't loaded +them previously; it sees that they are not in the cache and can request +the records by ID if necessary. + +The downside of queries is that they do not live update, they are +slower, and they require that your server support the kind of queries +that you wish to perform. + +Because the server decides which records match the query, not the store, +queries do not live update. If you want to update them, you must +manually call `reload()` and wait for the server to respond. If you +create a new record on the client, it will not show up in the results +until you both save the new record to the server and reload the query +results. + +Because the store must confer with your server to determine the results +of a query, it necessitates a network request. This can feel slow to +users, especially if they are on a slow connection or your server is +slow to respond. The typical speed of JavaScript web applications can +heighten the perceived slowness when the server must be consulted. + +Lastly, performing queries requires collaboration between the store and +your server. By default, Ember Data will send the search options that +you pass as the body of an HTTP request to your server. If your server +does not support requests in this format, you will need to either change +your server to do so, or customize how queries are sent by creating a +custom adapter. + +_Filters_, on the other hand, perform a live search of all of the records +in the store's cache. As soon as a new record is loaded into the store, +the filter will check to see if the record matches, and if so, add it to +the array of search results. If that array is displayed in a template, +it will update automatically. + +Filters also take into account newly created records that have not been +saved, and records that have been modified but not yet saved. If you +want records to show up in search results as soon as they are created or +modified on the client, you should use a filter. + +Keep in mind that records will not show up in a filter if the store +doesn't know about them. You can ensure that a record is in the store by +using the store's `push()` method. + +There is also a limit to how many records you can reasonably keep in +memory and search before you start hitting performance issues. + +Finally, keep in mind that you can combine queries and filters to take +advantage of their respective strengths and weaknesses. Remember that +records returned by a query to the server are cached in the store. You +can use this fact to perform a query that starts matching records into +the store, then create a filter that matches the same records. + +This will offload searching all of the possible records to the server, +while still creating a live updating list that includes records created +and modified on the client. + +```js +App.PostsFavoritedRoute = Ember.Route.extend({ + model: function() { + var store = this.get('store'); + + // Kick off a query to the server for all posts that + // the user has favorited. Note that we're ignoring the results + // of the query; we're just relying on the side-effect that they + // will be loaded into the store so we can filter them. + store.findQuery('posts', { favorited: true }); + + // Create a filter for all favorited posts that will be displayed in + // the template. Any favorited posts that are already in the store + // will be displayed immediately; as // results from the above query are + // returned from the server, they will also begin to appear. + return store.filter('posts', function(post) { + return post.get('isFavorited'); + }); + } +}); +``` + diff --git a/source/bilingual_guides/models/index.md b/source/bilingual_guides/models/index.md index 7e902fa..e824e34 100644 --- a/source/bilingual_guides/models/index.md +++ b/source/bilingual_guides/models/index.md @@ -2,47 +2,210 @@ ## 模型(Models) -In most Ember.js apps, models are handled by [Ember Data][emberdata]. Ember Data -is a library, built with and for Ember.js, designed to make it easy to -retrieve records from a server, make changes in the browser, then save -those changes back to the server. +In Ember, every route has an associated model. This model is set by +implementing a route's `model` hook, by passing the model as an argument +to `{{link-to}}`, or by calling a route's `transitionTo()` method. -在大部分的 Ember.js 应用里,模型是通过 [Ember Data](https://github.com/emberjs/data) 来处理的。Ember -Data 是一个由 Ember.js 写的库,它使我们可以很方便从服务器端取回记录并动态改变浏览器中的内容,然后保存这些更改回服务器。 +See [Specifying a Route's Model](/guides/routing/specifying-a-routes-model) for more information +on setting a route's model. -It provides many of the facilities you'd find in server-side ORMs like -ActiveRecord, but is designed specifically for the unique environment of -JavaScript in the browser. +For simple applications, you can get by using jQuery to load JSON data +from a server, then use those JSON objects as models. -你可以在服务器端找到像 ActiveRecord 这样的 ORM,不过它提供了专门为浏览器端的 Javascript 环境设计的工具方法。 +However, using a model library that manages finding models, making +changes, and saving them back to the server can dramatically simplify +your code while improving the robustness and performance of your +application. + +Many Ember apps use [Ember Data][emberdata] to handle this. +Ember Data is a library that integrates tightly with Ember.js to make it +easy to retrieve records from a server, cache them for performance, +save updates back to the server, and create new records on the client. Without any configuration, Ember Data can load and save records and -relationships served via a RESTful JSON API, provided it follows certain +their relationships served via a RESTful JSON API, provided it follows certain conventions. Ember Data 并不需要任务配置就可以通过 RESTful JSON API 的惯例来加载和保存记录和关系。 -We also understand that there exist many web service APIs in the world, -many of them crazy, inconsistent, and out of your control. Ember Data is -designed to be configurable to work with whatever persistence layer you -want, from the ordinary to the exotic. +If you need to integrate your Ember.js app with existing JSON APIs that +do not follow strong conventions, Ember Data is designed to be easily +configurable to work with whatever data your server returns. -我们也知道世界上还有很多Web服务的API,其中许多会是荒唐的,矛盾的和容易失去控制。 -Ember Data 被设计成可配置的,无论你想要怎么样的持久层它都可以满足。 +Ember Data is also designed to work with streaming APIs like +socket.io, Firebase, or WebSockets. You can open a socket to your server +and push changes to records into the store whenever they occur. -Currently, Ember Data ships as a separate library from Ember.js, while -we expand the adapter API to support more features. The API described in -this section tends to be stable, however. Until Ember Data is included +Currently, Ember Data ships as a separate library from Ember.js. Until Ember Data is included as part of the standard distribution, you can get your copy of the latest -passing build from the "master" branch from [builds.emberjs.com][builds]: +passing build from [builds.emberjs.com][builds]: -目前, Ember Data还是作为Ember.js的一个独立的库,与此同时,我们仍然在扩展适配器的API以便支持更多功能。 -在Ember Data被作为标准配置的一部分之前,你可以从其[builds.emberjs.com][builds]下载到从"master"分支编译得到的最新的拷贝。 +目前,Ember Data还是作为Ember.js的一个独立的库。在Ember Data被作为标准配置的一部分之前,你可以从其[builds.emberjs.com][builds]下载最新的版本。 * [Development][development-build] * [Minified][minified-build] [emberdata]: https://github.com/emberjs/data -[builds]: http://builds.emberjs.com -[development-build]: http://builds.emberjs.com/latest/ember-data.js -[minified-build]: http://builds.emberjs.com/latest/ember-data.min.js +[builds]: http://emberjs.com/builds +[development-build]: http://builds.emberjs.com/canary/ember-data.js +[minified-build]: http://builds.emberjs.com/canary/ember-data.min.js + +### Core Concepts + +Learning to use Ember Data is easiest once you understand some of the +concepts that underpin its design. + +#### Store + +The **store** is the central repository of records in your application. +Both your application's controllers and routes have access to this +shared store; when they need to display or modify a record, they will +first ask the store for it. + +#### Models + +A **model** is a class that defines the properties and behavior of the +data that you present to the user. Anything that the user expects to see +if they leave your app and come back later (or if they refresh the page) +should be represented by a model. + +For example, if you were writing a web application for placing orders at +a restaurant, you might have models like `Order`, `LineItem`, and +`MenuItem`. + +Models define the type of data that will be provided by your server. For +example, a `Person` model might have a `firstName` attribute that is a +string, and a `birthday` attribute that is a date. + +A model also describes its relationships with other objects. For +example, an `Order` may have many `LineItems`, and a `LineItem` may +belong to a particular `Order`. + +Models don't have any data themselves; they just define the properties and +behavior of specific instances, which are called _records_. + +#### Records + +A **record** is an instance of a model that contains data loaded from a +server. Your application can also create new records and save them back +to the server. + +Records are uniquely identified by two things: + +1. A model type. +2. A globally unique ID. + +For example, if you were writing a contact management app, you might +have a model called `Person`. An individual record in your app might +have a type of `Person` and an ID of `1` or `steve-buscemi`. + +IDs are usually assigned by the server when you save them for the first +time, but you can also generate IDs client-side. + +#### Adapter + +An **adapter** is an object that knows about your particular server +backend and is responsible for translating requests for and changes to +records into the appropriate calls to your server. + +For example, if your application asks for a `person` record with an ID +of `1`, how should Ember Data load it? Is it over HTTP or a WebSocket? +If it's HTTP, is the URL `/person/1` or `/resources/people/1`? + +The adapter is responsible for answering all of these questions. +Whenever your app asks the store for a record that it doesn't have +cached, it will ask the adapter for it. If you change a record and save +it, the store will hand the record to the adapter to send the +appropriate data to your server and confirm that the save was +successful. + +#### Serializer + +A **serializer** is responsible for turning a raw JSON payload returned +from your server into a record object. + +JSON APIs may represent attributes and relationships in many different +ways. For example, some attribute names may be `camelCased` and others +may be `under_scored`. Representing relationships is even more diverse: +they may be encoded as an array of IDs, an array of embedded objects, or +as foreign keys. + +When the adapter gets a payload back for a particular record, it will +give that payload to the serializer to normalize into the form that +Ember Data is expecting. + +While most people will use a serializer for normalizing JSON, because +Ember Data treats these payloads as opaque objects, there's no reason +they couldn't be binary data stored in a `Blob` or +[ArrayBuffer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays/ArrayBuffer). + +#### Automatic Caching + +The store will automatically cache records for you. If a record had already +been loaded, asking for it a second time will always return the same +object instance. This minimizes the number of round-trips to the +server, and allows your application to render UI to the user as fast as +possible. + +For example, the first time your application asks the store for a +`person` record with an ID of `1`, it will fetch that information from +your server. + +However, the next time your app asks for a `person` with ID `1`, the +store will notice that it had already retrieved and cached that +information from the server. Instead of sending another request for the +same information, it will give your application the same record it had +provided it the first time. This feature—always returning the same +record object, no matter how many times you look it up—is sometimes +called an _identity map_. + +Using an identity map is important because it ensures that changes you +make in one part of your UI are propagated to other parts of the UI. It +also means that you don't have to manually keep records in sync—you can +ask for a record by ID and not have to worry about whether other parts +of your application have already asked for and loaded it. + +### Architecture Overview + +The first time your application asks the store for a record, the store +sees that it doesn't have a local copy and requests it from your +adapter. Your adapter will go and retrieve the record from your +persistence layer; typically, this will be a JSON representation of the +record served from an HTTP server. + +![Diagram showing process for finding an unloaded record](/images/guides/models/finding-unloaded-record-step1-diagram.png) + +As illustrated in the diagram above, the adapter cannot always return the +requested record immediately. In this case, the adapter must make an +_asynchronous_ request to the server, and only when that request finishes +loading can the record be created with its backing data. + +Because of this asynchronicity, the store immediately returns a +_promise_ from the `find()` method. Similarly, any requests that the +store makes to the adapter also return promises. + +Once the request to the server returns with a JSON payload for the +requested record, the adapter resolves the promise it returned to the +store with the JSON. + +The store then takes that JSON, initializes the record with the +JSON data, and resolves the promise returned to your application +with the newly-loaded record. + +![Diagram showing process for finding an unloaded record after the payload has returned from the server](/images/guides/models/finding-unloaded-record-step2-diagram.png) + +Let's look at what happens if you request a record that the store +already has in its cache. + +![Diagram showing process for finding an unloaded record after the payload has returned from the server](/images/guides/models/finding-loaded-record-diagram.png) + +In this case, because the store already knew about the record, it +returns a promise that it resolves with the record immediately. It does +not need to ask the adapter (and, therefore, the server) for a copy +since it already has it saved locally. + +--- + +These are the core concepts you should understand to get the most out of +Ember Data. The following sections go into more depth about each of +these concepts, and how to use them together. \ No newline at end of file diff --git a/source/bilingual_guides/models/model-lifecycle.md b/source/bilingual_guides/models/model-lifecycle.md deleted file mode 100644 index 118a2c3..0000000 --- a/source/bilingual_guides/models/model-lifecycle.md +++ /dev/null @@ -1,131 +0,0 @@ -## Model Lifecycle - -## 模型生命周期 - -Since models must be loaded and saved asynchronously, there are several -possible states that a record may be in at a given time. Each instance -of `DS.Model` has a set of Boolean properties that you can use to -introspect the current state of the record. - -因为模型必须是异步加载和保存的,所以在给定的时间内一条记录就会存在几个不同的可能状态。每一个`DS.Model`的实例都有一系列布尔类型的属性,用来检查记录的当前状态。 - -* **isLoaded** The adapter has finished retrieving the current state of - the record from its backend. -* **isDirty** The record has local changes that have not yet been saved - by the adapter. This includes records that have been created (but not - yet saved) or deleted. -* **isSaving** The record has been sent to the adapter to have its - changes saved to the backend, but the adapter has not yet confirmed - that the changes were successful. -* **isDeleted** The record was marked for deletion. When `isDeleted` - is true and `isDirty` is true, the record is deleted locally - but the deletion was not yet persisted. When `isSaving` is - true, the change is in-flight. When both `isDirty` and - `isSaving` are false, the change has been saved. -* **isError** The adapter reported that it was unable to save - local changes to the backend. This may also result in the - record having its `isValid` property become false if the - adapter reported that server-side validations failed. -* **isNew** The record was created locally and the adapter - did not yet report that it was successfully saved. -* **isValid** No client-side validations have failed and the - adapter did not report any server-side validation failures. - -* **isLoaded** 适配器已完成从后端读取记录的当前状态 -* **isDirty** 记录有本地修改且尚未被适配器保存到后端。包括记录被创建或删除(但尚未保存) -* **isSaving** 记录已被发给适配器,但适配器尚未确认修改已成功保存与否 -* **isDeleted** 记录被标记将被删除。当`isDeleted`和`isDirty`同时为真时,记录只是在本地被删除,并没有持久化。`isSaving`为真时,修改正在保存当中。`isDirty`和`isSaving`同时为假时,修改已经保存成功。 -* **isError** 适配器报告说无法保存修改到后端。如果服务器端的验证失败时,也会导致记录的`isValid`属性为假。 -* **isNew** 记录只是在本地被创建,适配器尚未报告记录是否被成功保存。 -* **isValid** 没有客户端验证失败,适配器也没有报告任何服务器端验证失败 - -Additionally, you can subscribe to events that are emitted when a record -changes state. For example, you can run some code when a record becomes -loaded: - -另外,你可以定义记录状态发生改变时的事件。比如,你可以在记录加载完之后运行一些代码: - -```js -record.on('didLoad', function() { - console.log("Loaded!"); -}); -``` - -Valid events are: - -有效的事件包括: - -* `didLoad` -* `didCreate` -* `didUpdate` -* `didDelete` -* `becameError` -* `becameInvalid` - - -### Record States - -### 记录的状态 - -#### Loading - -#### 加载中(loading) - -A record typically starts off in the loading state. This means Ember -Data has not yet received information from the adapter about what values -its attributes and relationships should have. You can put the record -into templates and they will automatically update once the record -becomes loaded. - -Attempting to modify a record in the loading state will raise an -exception. - -记录一般都起始于加载中(loading)这个状态。这意味着 Ember Data尚未从适配器收到任何有关记录的属性值为多少,处于什么样的关系之类的信息。你可以将记录放进模板中,一旦记录加载完成(loaded),模板就会自动更新。 - -#### Loaded/Clean - -#### 加载完成(loaded)/干净 - -A record that is both loaded and clean means that is has received -information about its attributes and relationships from the server, and -no changes have been made locally on the client. - -一条记录加载完成(loaded)且干净意味着已经从服务器获得了它的属性以及关系的信息。而且本地对记录尚无修改。 - -#### Dirty - -#### 脏 - -A dirty record has outstanding changes to either its attributes or -relationships that have not yet been synchronized with the adapter. - -脏的状态意味着记录的属性或关系已经被更改,但尚未通过适配器同步到服务器端。 - -#### In-Flight - -#### 处理中 - -A record that is in-flight is a dirty record that has been given to the -adapter to save the changes made locally. Once the server has -acknowledged that the changes have been saved successfully, the record -will become clean. - -一条在进行中的记录是指记录被交给适配器,以保存在本地所做的修改。一旦服务器确认修改的记录被成功保存,记录的状态就变成干净(clean)了 - -#### Invalid - -A record becomes invalid if the adapter is notified by the server that -it was not able to be saved because the backend deemed it invalid. - -#### 无效 - -如果服务器认为记录的修改无效以至于无法保存,那么这条记录的状态就变成无效(invalid) - -#### Error - -#### 错误 - -A record enters the error state when the adapter is unable to save it -for some reason other than invalidity. - -如果一条记录因为除无效之外的其他原因无法保存时,它就进入了错误状态。 diff --git a/source/bilingual_guides/models/pushing-records-into-the-store.md b/source/bilingual_guides/models/pushing-records-into-the-store.md new file mode 100644 index 0000000..2199ddf --- /dev/null +++ b/source/bilingual_guides/models/pushing-records-into-the-store.md @@ -0,0 +1,57 @@ +One way to think about the store is as a cache of all of the records +that have been loaded by your application. If a route or a controller in +your app asks for a record, the store can return it immediately if it is +in the cache. Otherwise, the store must ask the adapter to load it, +which usually means a trip over the network to retrieve it from the +server. + +Instead of waiting for the app to request a record, however, you can +push records into the store's cache ahead of time. + +This is useful if you have a good sense of what records the user +will need next. When they click on a link, instead of waiting for a +network request to finish, Ember.js can render the new template +immediately. It feels instantaneous. + +Another use case for pushing in records is if your application has a +streaming connection to a backend. If a record is created or modified, +you want to update the UI immediately. + +### Pushing Records + +To push a record into the store, call the store's `push()` method. + +For example, imagine we want to preload some data into the store when +the application boots for the first time. + +We can use the `ApplicationRoute` to do so. The `ApplicationRoute` is +the top-most route in the route hierarchy, and its `model` hook gets +called once when the app starts up. + +```js +var attr = DS.attr; + +App.Album = DS.Model.extend({ + title: attr(), + artist: attr(), + songCount: attr() +}); + +App.ApplicationRoute = Ember.Route.extend({ + model: function() { + var store = this.get('store'); + + store.push('album', { + title: "Fewer Moving Parts", + artist: "David Bazan", + songCount: 10 + }); + + store.push('album', { + title: "Calgary b/w I Can't Make You Love Me/Nick Of Time", + artist: "Bon Iver", + songCount: 2 + }); + } +}); +``` diff --git a/source/bilingual_guides/models/querying-the-server-for-records.md b/source/bilingual_guides/models/querying-the-server-for-records.md new file mode 100644 index 0000000..32e229a --- /dev/null +++ b/source/bilingual_guides/models/querying-the-server-for-records.md @@ -0,0 +1,19 @@ +You can query the server by calling the store's `findQuery()` method and +passing a hash of search options. This method returns a promise that +resolves to an array of the search results. + +For example, we could search for all `person` models who had the name of +`Peter`: + +```js +store.findQuery('person', { name: "Peter" }).then(function(people) { + console.log("Found " + people.get('length') + " people named Peter."); +}); +``` + +The hash of search options that you pass to `findQuery()` is opaque to Ember +Data. By default, these options will be sent to your server as the body +of an HTTP `GET` request. + +**Using this feature requires that your server knows how to interpret +query responses.** diff --git a/source/bilingual_guides/models/using-the-store.md b/source/bilingual_guides/models/using-the-store.md new file mode 100644 index 0000000..ac3df1c --- /dev/null +++ b/source/bilingual_guides/models/using-the-store.md @@ -0,0 +1,53 @@ +Before your application can display a model, it must first load it from +the server. Of course, since network requests can be expensive, it is +important to only request a given record once. + +To keep track of which records are loaded and which still need to be +fetched, every controller and route in your application has access to a +**store**. You can think of the store as a cache of all of the records +available in your app. + +### Getting Records into the Store + +There are two ways of getting records into the store. + +First, you can manually push records into the store. This is useful if, +for example, you have a streaming API that notifies your application of +new records as soon as they are created. + +For more about pushing records into the store, see: + +* [Pushing Records into the + Store](/guides/models/pushing-records-into-the-store) + +Second, you can ask the store for a record. If the store doesn't already +have the record loaded, it will ask its _adapter_ to load it from the +server. The record that your server returns will be pushed into the +store automatically. + +Note that if the record _has_ been loaded, the store returns it +immediately without making an unnecessary network request. + +### Accessing the Store + +Once you include the Ember Data library in your application, all of your +controllers and routes will automatically gain access to a new property +called `store`. This instance of `DS.Store` is created for you +automatically and is shared among all of the objects in your +application. + +You will use the store to retrieve records, as well to create new ones. +For example, we might want to find an `App.Person` model with the ID of +`1` from our route's `model` hook: + +```js +App.IndexRoute = Ember.Route.extend({ + model: function() { + var store = this.get('store'); + return store.find('person', 1) + } +}); +``` + +For more about finding records, see [Finding a +Record](/guides/models/finding-a-record). diff --git a/source/bilingual_guides/models/modifying-attributes.md b/source/bilingual_guides/models/working-with-records.md similarity index 72% rename from source/bilingual_guides/models/modifying-attributes.md rename to source/bilingual_guides/models/working-with-records.md index 887e8a3..0b57168 100644 --- a/source/bilingual_guides/models/modifying-attributes.md +++ b/source/bilingual_guides/models/working-with-records.md @@ -10,7 +10,7 @@ want to change: 一旦一条记录已经加载进来,你就可以开始修改它的属性(attributes)了。属性(attributes)和Ember.js中对象的普通属性(properties)差不多。(译注:这两个词我都译成了属性,读者自行判定其中潜在的区别)。修改记录就是修改记录的属性。 ```js -var tyrion = App.Person.find(1); +var tyrion = this.store.find('person', 1); // ...after the record has loaded tyrion.set('firstName', "Yollo"); ``` @@ -41,12 +41,3 @@ person.set('isAdmin', true); person.get('isDirty'); //=> true ``` - -Make sure that a record has finished loading before you try to modify. -If you attempt to modify a record before it has finished loading, Ember -Data will raise an exception. For more information, see [Model -Lifecycle][1]. - -在修改一条记录前,得保证记录已经被完整地加载进来。如果记录尚未完成加载就修改,Ember Data会抛出一个异常。想了解更多信息,见[模型生命周期][1]. - -[1]: /guides/models/model-lifecycle diff --git a/source/bilingual_guides/object-model/computed-properties-and-aggregate-data.md b/source/bilingual_guides/object-model/computed-properties-and-aggregate-data.md index 265f65e..c98c741 100644 --- a/source/bilingual_guides/object-model/computed-properties-and-aggregate-data.md +++ b/source/bilingual_guides/object-model/computed-properties-and-aggregate-data.md @@ -22,7 +22,7 @@ App.TodosController = Ember.Controller.extend({ remaining: function() { var todos = this.get('todos'); - return todos.filterProperty('isDone', false).get('length'); + return todos.filterBy('isDone', false).get('length'); }.property('todos.@each.isDone') }); ``` diff --git a/source/bilingual_guides/object-model/computed-properties.md b/source/bilingual_guides/object-model/computed-properties.md index 807c464..34ca358 100644 --- a/source/bilingual_guides/object-model/computed-properties.md +++ b/source/bilingual_guides/object-model/computed-properties.md @@ -10,6 +10,7 @@ We'll start with a simple example: ```javascript App.Person = Ember.Object.extend({ + // these will be supplied by `create` firstName: null, lastName: null, @@ -18,7 +19,7 @@ App.Person = Ember.Object.extend({ }.property('firstName', 'lastName') }); -var ironMan = Person.create({ +var ironMan = App.Person.create({ firstName: "Tony", lastName: "Stark" }); @@ -34,7 +35,7 @@ Whenever you access the `fullName` property, this function gets called, and it r You can use computed properties as values to create new computed properties. Let's add a `description` computed property to the previous example, and use the existing `fullName` property and add in some other properties: ```javascript -Person = Ember.Object.extend({ +App.Person = Ember.Object.extend({ firstName: null, lastName: null, age: null, @@ -49,7 +50,7 @@ Person = Ember.Object.extend({ }.property('fullName', 'age', 'country') }); -var captainAmerica = Person.create({ +var captainAmerica = App.Person.create({ firstName: 'Steve', lastName: 'Rogers', age: 80, @@ -64,7 +65,7 @@ captainAmerica.get('description'); // "Steve Rogers; Age: 80; Country: USA" Computed properties, by default, observe any changes made to the properties they depend on and are dynamically updated when they're called. Let's use computed properties to dynamically update . ```javascript -captainAmerica.set('firstName', 'William') +captainAmerica.set('firstName', 'William'); captainAmerica.get('description'); // "William Rogers; Age: 80; Country: USA" ``` @@ -79,14 +80,13 @@ You can also define what Ember should do when setting a computed property. If yo ```javascript App.Person = Ember.Object.extend({ - // these will be supplied by `create` firstName: null, lastName: null, - fullName: function(key, value, oldValue) { + fullName: function(key, value) { // setter if (arguments.length > 1) { - var nameParts = fullNameString.split(/\s+/); + var nameParts = value.split(/\s+/); this.set('firstName', nameParts[0]); this.set('lastName', nameParts[1]); } @@ -97,10 +97,8 @@ App.Person = Ember.Object.extend({ }); -var captainAmerica = Person.create(); +var captainAmerica = App.Person.create(); captainAmerica.set('fullName', "William Burnside"); -captainAmerica.get('firstName') // William -captainAmerica.get('lastName') // Burnside -``` - -Ember will call the computed property for both setters and getters, so if you want to call use a computed property as a setter, you'll need to check the number of arguments to determine whether it is being called as a getter or a setter. \ No newline at end of file +captainAmerica.get('firstName'); // William +captainAmerica.get('lastName'); // Burnside +``` \ No newline at end of file diff --git a/source/bilingual_guides/object-model/reopening-classes-and-instances.md b/source/bilingual_guides/object-model/reopening-classes-and-instances.md index f41db18..79ff624 100644 --- a/source/bilingual_guides/object-model/reopening-classes-and-instances.md +++ b/source/bilingual_guides/object-model/reopening-classes-and-instances.md @@ -36,7 +36,7 @@ methods and properties to a particular instance of a class as in vanilla JavaScr `reopen`用来添加被所有类的实例共享的实例方法和属性。它不能像Vanilla Javascript那样,用来为某一特定的实例来添加方法和属性(不使用prototype)。 -But when you need to create class method or add the properties to the class itself you can use `reopenClass`. +But when you need to create class method or add properties to the class itself you can use `reopenClass`. 但是如果需要为类添加方法和属性,则可以使用`reopenClass`方法。 diff --git a/source/bilingual_guides/routing/defining-your-routes.md b/source/bilingual_guides/routing/defining-your-routes.md index 6432dda..b7affce 100644 --- a/source/bilingual_guides/routing/defining-your-routes.md +++ b/source/bilingual_guides/routing/defining-your-routes.md @@ -267,7 +267,7 @@ route handler might look like this: ```js App.BlogPostsRoute = Ember.Route.extend({ model: function() { - return App.BlogPost.find(); + return this.get('store').find('blogPost'); } }); ``` @@ -303,7 +303,7 @@ App.Router.map(function() { App.PostRoute = Ember.Route.extend({ model: function(params) { - return App.Post.find(params.post_id); + return this.get('store').find('post', params.post_id); } }); ``` @@ -467,7 +467,7 @@ App.Router.map(function() { }); ``` -This router creates following routes: +This router creates the following routes: 上面定义的路由器会创建如下路由: diff --git a/source/bilingual_guides/routing/index.md b/source/bilingual_guides/routing/index.md index 157fc6d..ed4a121 100644 --- a/source/bilingual_guides/routing/index.md +++ b/source/bilingual_guides/routing/index.md @@ -71,3 +71,17 @@ App = Ember.Application.create({ LOG_TRANSITIONS: true }); ``` + +###Specifying a Root URL + +If your Ember application is one of multiple web applications served from the same domain, it may be necessary to indicate to the router what the root URL for your Ember application is. By default, Ember will assume it is served from the root of your domain. + +If for example, you wanted to serve your blogging application from www.emberjs.com/blog/, it would be necessary to specify a root URL of `/blog/`. + +This can be achieved by setting the rootURL on the router: + +```js +App.Router.reopen({ + rootURL: '/blog/' +}); +``` diff --git a/source/bilingual_guides/routing/rendering-a-template.md b/source/bilingual_guides/routing/rendering-a-template.md index 3a7a372..368dc10 100644 --- a/source/bilingual_guides/routing/rendering-a-template.md +++ b/source/bilingual_guides/routing/rendering-a-template.md @@ -110,27 +110,3 @@ App.PostRoute = App.Route.extend({ } }); ``` - -## Rendering Warning - -## 渲染警告 - -When a template tries to render, and the parent route did not render a -template, then you will see this warning: - -"The immediate parent route did not render into the main outlet ..." - -当渲染一个模板时,如果父级路由并没有渲染模版,那么你会看到一条警告: -“直接父级路由没有渲染进主插座……” - -This means that the current route tried to render into the parent -route's template, but the parent route didn't render a template, or, if -it did, that the template which the parent route provided did not render -into the main template (i.e., a default `{{outlet}}`). - -Ember provides this warning because it expects that you will want to -render into the main template. - -什么意思呢?当前路由试图渲染进父级路由的模板,但父级路由没有渲染模板,或者假使父级路由渲染了模板,但是父级路由提供的模板却没有渲染进主插座(也就是说,一个默认的`{{outlet}}`插座) - -Ember提供这条警告是希望你将模板渲染到主模板中去。 diff --git a/source/bilingual_guides/routing/specifying-a-routes-model.md b/source/bilingual_guides/routing/specifying-a-routes-model.md index 68120c8..775edae 100644 --- a/source/bilingual_guides/routing/specifying-a-routes-model.md +++ b/source/bilingual_guides/routing/specifying-a-routes-model.md @@ -1,83 +1,199 @@ 英文原文: [http://emberjs.com/guides/routing/specifying-a-routes-model/](http://emberjs.com/guides/routing/specifying-a-routes-model/) + ## 指定路由的模型 (Specifying a Route's Model) -In the router, each URL is associated with one or more _route handlers_. -The route handler is responsible for converting the URL into a model -object, telling a controller to represent that model, then rendering a -template bound to that controller. +Templates in your application are backed by models. But how do templates +know which model they should display? + +For example, if you have a `photos` template, how does it know which +model to render? + +This is one of the jobs of an `Ember.Route`. You can tell a template +which model it should render by defining a route with the same name as +the template, and implementing its `model` hook. + +For example, to provide some model data to the `photos` template, we +would define an `App.PhotosRoute` object: + +```js +App.PhotosRoute = Ember.Route.extend({ + model: function() { + return [{ + title: "Tomster", + url: "http://emberjs.com/images/about/ember-productivity-sm.png" + }, { + title: "Eiffel Tower", + url: "http://emberjs.com/images/about/ember-structure-sm.png" + }]; + } +}); +``` + +JS Bin + +### Asynchronously Loading Models + +In the above example, the model data was returned synchronously from the +`model` hook. This means that the data was available immediately and +your application did not need to wait for it to load, in this case +because we immediately returned an array of hardcoded data. + +Of course, this is not always realistic. Usually, the data will not be +available synchronously, but instead must be loaded asynchronously over +the network. For example, we may want to retrieve the list of photos +from a JSON API available on our server. -在路由器中, 每一个URL都会有一个或多个路由处理器(`route handlers`)与之相关联。路由处理器会负责将URL转换成一个模型对象, -并利用一个控制器来表示这个模型,然后渲染绑定于那个控制器的模板。 +In cases where data is available asynchronously, you can just return a +promise from the `model` hook, and Ember will wait until that promise is +resolved before rendering the template. -### 单一模型 (Singleton Models) +If you're unfamiliar with promises, the basic idea is that they are +objects that represent eventual values. For example, if you use jQuery's +`getJSON()` method, it will return a promise for the JSON that is +eventually returned over the network. Ember uses this promise object to +know when it has enough data to continue rendering. -If a route does not have a dynamic segment, you can hardcode which model -should be associated with that URL by implementing the route handler's -`model` hook: +For more about promises, see [A Word on +Promises](/guides/routing/asynchronous-routing/#toc_a-word-on-promises) +in the Asynchronous Routing guide. -如果一个路由不包含动态段,你就可以通过执行路由处理器的模型(`model`)钩子函数来写死与这个URL关联的模型(`model`): +Let's look at an example in action. Here's a route that loads the most +recent pull requests sent to Ember.js on GitHub: ```js -App.Router.map(function() { - this.resource('posts'); +App.PullRequestsRoute = Ember.Route.extend({ + model: function() { + return Ember.$.getJSON('https://api.github.com/repos/emberjs/ember.js/pulls'); + } }); +``` + +While this example looks like it's synchronous, making it easy to read +and reason about, it's actually completely asynchronous. That's because +jQuery's `getJSON()` method returns a promise. Ember will detect the +fact that you've returned a promise from the `model` hook, and wait +until that promise resolves to render the `pullRequest` template. + +(For more information on jQuery's XHR functionality, see +[jQuery.ajax](http://api.jquery.com/jQuery.ajax/) in the jQuery +documentation.) + +Because Ember supports promises, it can work with any persistence +library that uses them as part of its public API. You can also use many +of the conveniences built in to promises to make your code even nicer. + +For example, imagine if we wanted to modify the above example so that +the template only displayed the three most recent pull requests. We can +rely on promise chaining to modify the data returned from the JSON +request before it gets passed to the template: -App.PostsRoute = Ember.Route.extend({ +```js +App.PullRequestsRoute = Ember.Route.extend({ model: function() { - return App.Post.find(); + var url = 'https://api.github.com/repos/emberjs/ember.js/pulls'; + return Ember.$.getJSON(url).then(function(data) { + return data.splice(0, 3); + }); } }); ``` +### Setting Up Controllers with the Model + +So what actually happens with the value you return from the `model` +hook? + By default, the value returned from your `model` hook will be assigned -to the `model` property of the `posts` controller. You can change this -behavior by implementing the [setupControllers hook][1]. The `posts` -controller is the context for the `posts` template. +to the `model` property of the associated controller. For example, if your +`App.PostsRoute` returns an object from its `model` hook, that object +will be set as the `model` property of the `App.PostsController`. -默认情况下,从模型(`model`)钩子函数返回的值将会赋值给`posts`控制器的`model`属性。 -你可以通过执行[setupControllers hook][1]钩子函数来改变这种默认的方式。`posts`控制器是`posts`模板的上下文环境。 +(This, under the hood, is how templates know which model to render: they +look at their associated controller's `model` property. For example, the +`photos` template will render whatever the `App.PhotosController`'s +`model` property is set to.) + +See the [Setting Up a Controller guide][1] to learn how to change this +default behavior. Note that if you override the default behavior and do +not set the `model` property on a controller, your template will not +have any data to render! [1]: /guides/routing/setting-up-a-controller ### 动态模型(Dynamic Models) +Some routes always display the same model. For example, the `/photos` +route will always display the same list of photos available in the +application. If your user leaves this route and comes back later, the +model does not change. + +However, you will often have a route whose model will change depending +on user interaction. For example, imagine a photo viewer app. The +`/photos` route will render the `photos` template with the list of +photos as the model, which never changes. But when the user clicks on a +particular photo, we want to display that model with the `photo` +template. If the user goes back and clicks on a different photo, we want +to display the `photo` template again, this time with a different model. + +In cases like this, it's important that we include some information in +the URL about not only which template to display, but also which model. -If a route has a dynamic segment, you will want to use the parameters to -decide which model to use: +In Ember, this is accomplished by defining routes with _dynamic segments_. -如果一个路由包含动态段,你会想用参数来决定用哪个模型(`model`): +A dynamic segment is a part of the URL that is filled in by the current +model's ID. Dynamic segments always start with a colon (`:`). Our photo +example might have its `photo` route defined like this: ```js App.Router.map(function() { - this.resource('post', { path: '/posts/:post_id' }); -}); - -App.PostRoute = Ember.Route.extend({ - model: function(params) { - return App.Post.find(params.post_id); - } + this.resource('photo', { path: '/photos/:photo_id' }); }); ``` -Because this pattern is so common, the above `model` hook is the -default behavior. +In this example, the `photo` route has a dynamic segment `:photo_id`. +When the user goes to the `photo` route to display a particular photo +model (usually via the `{{link-to}}` helper), that model's ID will be +placed into the URL automatically. -由于这种模式很常用,所以上面的模型(`model`)钩子函数就是默认的行为。 +See [Links](/guides/templates/links) for more information about linking +to a route with a model using the `{{link-to}}` helper. -For example, if the dynamic segment is `:post_id`, Ember.js is smart -enough to know that it should use the model `App.Post` (with the ID -provided in the URL). Specifically, unless you override `model`, the route will -return `App.Post.find(params.post_id)` automatically. +For example, if you transitioned to the `photo` route with a model whose +`id` property was `47`, the URL in the user's browser would be updated +to: -例如,如果动态段是`:post_id`,`ember.js`会智能地使用`App.post`(加上`URL`提供的`ID`)。 -特别地,如果你没有重写了模型(`model`),路由将会自动地返回`App.Post.find(params.post_id)`。 +``` +/photos/47 +``` -Not coincidentally, this is exactly what Ember Data expects. So if you -use the Ember router with Ember Data, your dynamic segments will work -as expected out of the box. +What happens if the user visits your application directly with a URL +that contains a dynamic segment? For example, they might reload the +page, or send the link to a friend, who clicks on it. At that point, +because we are starting the application up from scratch, the actual +JavaScript model object to display has been lost; all we have is the ID +from the URL. -这不是巧合,而是`Ember Data`所想要的。所以如果你使用`Ember`路由和`Ember Data`, -你的动态段将会以默认的方式工作。 +Luckily, Ember will extract any dynamic segments from the URL for +you and pass them as a hash to the `model` hook as the first argument: + +```js +App.Router.map(function() { + this.resource('photo', { path: '/photos/:photo_id' }); +}); + +App.PhotoRoute = Ember.Route.extend({ + model: function(params) { + return Ember.$.getJSON('/photos/'+params.photo_id); + } +}); +``` + +In the `model` hook for routes with dynamic segments, it's your job to +turn the ID (something like `47` or `post-slug`) into a model that can +be rendered by the route's template. In the above example, we use the +photo's ID (`params.photo_id`) to construct a URL for the JSON +representation of that photo. Once we have the URL, we use jQuery to +return a promise for the JSON model data. Note: A route with a dynamic segment will only have its `model` hook called when it is entered via the URL. If the route is entered through a transition @@ -88,3 +204,14 @@ will always execute the model hook. 注意:一个具有动态段的路由只有在通过URL访问的时候,`model`钩子才会被调用。如果路由是从一个跳转进入的(例如:使用Handlebars的[link-to][2]助手时),模型上下文已经准备好了,因此`model`钩子这时不会被执行。没有动态段的路由其`model`钩子每次都会被执行。 [2]: /guides/templates/links + +### Ember Data + +Many Ember developers use a model library to make finding and saving +records easier than manually managing Ajax calls. In particular, using a +model library allows you to cache records that have been loaded, +significantly improving the performance of your application. + +One popular model library built for Ember is Ember Data. To learn more +about using Ember Data to manage your models, see the +[Models](/guides/models) guide. \ No newline at end of file diff --git a/source/bilingual_guides/routing/specifying-the-location-api.md b/source/bilingual_guides/routing/specifying-the-location-api.md index d6991e1..e7bff20 100644 --- a/source/bilingual_guides/routing/specifying-the-location-api.md +++ b/source/bilingual_guides/routing/specifying-the-location-api.md @@ -22,7 +22,7 @@ App.Router.map(function() { }); ``` -If you want `/posts/new` to work instead, you can tell the Router use the browser's +If you want `/posts/new` to work instead, you can tell the Router to use the browser's [history](http://caniuse.com/history) API. Keep in mind that your server must serve the Ember app at all the routes diff --git a/source/bilingual_guides/templates/actions.md b/source/bilingual_guides/templates/actions.md index 8d9d279..4a74184 100644 --- a/source/bilingual_guides/templates/actions.md +++ b/source/bilingual_guides/templates/actions.md @@ -109,6 +109,14 @@ current route's ancestors, an error will be thrown. ![操作冒泡(Action Bubbling)](/images/template-guide/action-bubbling.png) +This allows you to create a button that has different behavior based on +where you are in the application. For example, you might want to have a +button in a sidebar that does one thing if you are somewhere inside of +the `/posts` route, and another thing if you are inside of the `/about` +route. + +这样可以创建一个按钮,且该按钮根据当前应用所在位置有不同的行为。例如,有一个在侧栏中的按钮,当在`/posts`路由和`/about`路由时,分别有不同的行为。 + ### Action Parameters ### 操作参数 @@ -227,38 +235,6 @@ the event. 指定`bubbles=false`时,Ember.js 就会阻止浏览器将此点击事件传递到父级元素。 -### Target Bubbling - -### 向目标冒泡 - -If the action is not found on the current controller, it will bubble up -to the current route handler. From there, it will bubble up to parent -route handlers until it reaches the application route. - -如果在当前控制器没有找到指定的操作,当前路由就会接管来处理。经由路由,再冒泡到父级路由处理,最终到达应用程序路由。 - -Define actions on the route's `actions` property. - -在路由的`actions`属性里定义操作。 - -```javascript -App.PostsIndexRoute = Ember.Route.extend({ - actions: { - myCoolAction: function() { - // do your business. - } - } -}); -``` - -This allows you to create a button that has different behavior based on -where you are in the application. For example, you might want to have a -button in a sidebar that does one thing if you are somewhere inside of -the `/posts` route, and another thing if you are inside of the `/about` -route. - -上面的代码允许你根据你目前在应用程序中的位置来创建具有不同行为的按钮。比如,如果你在 `/posts`路由中,你想在侧边栏创建一个按钮来完成某种操作,而在`/about`路由中时,此按钮却是做另外一件不同的事。 - ### Specifying a Target ### 指定目标 @@ -282,7 +258,7 @@ controller.

``` -You would handle it this in an `actions` hash on your view. +You would handle this in an `actions` hash on your view. 这样应该在视图的`actions`哈希中处理。 diff --git a/source/bilingual_guides/templates/binding-element-attributes.md b/source/bilingual_guides/templates/binding-element-attributes.md index 23c7ff0..945d296 100644 --- a/source/bilingual_guides/templates/binding-element-attributes.md +++ b/source/bilingual_guides/templates/binding-element-attributes.md @@ -39,18 +39,18 @@ the specified attribute. For example, given this template: ``` -If `isAdministrator` is `false`, Handlebars will produce the following +If `isAdministrator` is `true`, Handlebars will produce the following HTML element: -如果`isAdministrator`的值是`false`,`Handlebars`将生成如下所示的HTML元素: +如果`isAdministrator`的值是`true`,`Handlebars`将生成如下所示的HTML元素: ```html ``` -If `isAdministrator` is `true`, Handlebars will produce the following: +If `isAdministrator` is `false`, Handlebars will produce the following: -否则,如果`isAdministrator`是`true`,生成的HTML元素如下: +否则,如果`isAdministrator`是`false`,生成的HTML元素如下: ```html diff --git a/source/bilingual_guides/templates/handlebars-basics.md b/source/bilingual_guides/templates/handlebars-basics.md index 48200aa..09699e4 100644 --- a/source/bilingual_guides/templates/handlebars-basics.md +++ b/source/bilingual_guides/templates/handlebars-basics.md @@ -44,9 +44,9 @@ template inside your HTML by putting it inside a ` ``` -```handlebars -Posts: {{view.posts}} -
-Hobbies: {{view.hobbies}} +```html + ``` If we were to create an instance of `App.UserView` and render it, we would get diff --git a/source/blog/2013-04-23-ember-js-1-0-0-rc3.markdown b/source/blog/2013-04-23-ember-js-1-0-0-rc3.markdown index eaf8cd5..4fbde0a 100644 --- a/source/blog/2013-04-23-ember-js-1-0-0-rc3.markdown +++ b/source/blog/2013-04-23-ember-js-1-0-0-rc3.markdown @@ -31,7 +31,7 @@ App.reset(); ### `Ember`构建 -每一个成功的[CI](https://travis-ci.org/emberjs/ember.js)都会把结果发布到[http://builds.emberjs.com/](http://builds.emberjs.com/)。这会使引用及使用[最新的`Ember`构建](http://builds.emberjs.com/latest.js)更简单。 +每一个成功的[CI](https://travis-ci.org/emberjs/ember.js)都会把结果发布到[http://emberjs.com/builds](http://emberjs.com/builds)。这会使引用及使用[最新的`Ember`构建](http://builds.emberjs.com/canary/ember.js)更简单。 ### 新的`{{input}}`和`{{textarea}}`助手 diff --git a/source/blog/2013-05-28-using-ember-dev-to-develop-ember-js-packages.markdown b/source/blog/2013-05-28-using-ember-dev-to-develop-ember-js-packages.markdown new file mode 100644 index 0000000..b949792 --- /dev/null +++ b/source/blog/2013-05-28-using-ember-dev-to-develop-ember-js-packages.markdown @@ -0,0 +1,9 @@ +--- +title: 使用ember-dev开发Ember.js库 +tags: Draft +author: Tower He +--- + +在第三期周报中,我们曾经推荐了[ember-dev](https://github.com/emberjs/ember-dev)。目前作者还不建议我们在生产环境中使用ember-dev来开发Ember.js包,主要原因是ember-dev目前还不够成熟,接口不稳定,另外也缺失了一些重要特性,比如项目目录结构生成器。尽管如此,也无法遮住ember-dev的光芒,通过其目前提供的功能,已经可以很好的帮助我们进行Ember.js扩展功能的开发。下面将一步步介绍如何使用ember-dev来开发一个扩展包。 + +_注意:_ ember-dev是用来开发Ember.js扩展包的,而不是用来开发Ember.js应用的。请注意区分! diff --git a/source/blog/2013-08-31-ember-1-0-released.md b/source/blog/2013-08-31-ember-1-0-released.md new file mode 100644 index 0000000..0e80eff --- /dev/null +++ b/source/blog/2013-08-31-ember-1-0-released.md @@ -0,0 +1,331 @@ +--- +title: Ember 1.0 Released +author: The Core Team +tags: Releases, Recent Posts +--- + +Today, we're excited to announce the final release of Ember.js 1.0. + +The first commit to the repository that would become Ember.js happened on April +30th, 2011, almost two and a half years ago. + +At the time, Backbone.js was rocketing to popularity. In response to large +JavaScript frameworks like SproutCore, Cappuccino, and Dojo, which tried to +abstract away HTML, most web developers began rejecting any solution whose +source code they couldn't read over in an afternoon. The "microlibrary" frenzy +had hit full tilt. + +However, we knew that as web browsers became more and more powerful, these +simplistic abstractions wouldn't scale up to the kind of apps that users would +begin to demand. + +We realized that helping developers grapple with the complexity of building +100% JavaScript web applications could only happen if we embraced the tools +that they were most comfortable with: HTML and CSS. + +Based on the current popularity of frameworks like Ember, Angular and Knockout, +it's clear that this strategy turned out to be the right one. + +As we began work on Ember.js, however, we soon realized that there was a +fundamental problem. Just having templates that were bound to models was +not enough. We also needed to help developers decide _which_ templates and +models to display at any given time. + +While struggling to figure out the best solution, we couldn't help but notice +that many JavaScript applications on the web felt broken. Basic things that +we had taken for granted for two decades all of a sudden stopped working. +Just clicking the browser's back button was enough to break many of these apps. + +We realized that the solution to our problem had been sitting under our noses +all along: the URL is what web applications use to decide what to display! + +We knew that we had to go back to the drawing board. We rebooted the entire +project mid-course to refocus on how to build JavaScript apps that not only +helped you architect large, multi-page applications, but helped you to do so +without breaking the basic building blocks of the web. + +Over time, we've added even more features, like components, that help bring +solid UI architecture to the web. We are incredibly proud of the job that +the community has done to lay a solid foundation that we can build upon for the +years to come. + +This 1.0 release is a promise from us: the pain that many experienced while we +were figuring out how to build a JavaScript framework for the future of the web +is now over. In keeping with the Semantic Versioning spec, there will be no +more intentional breaking changes until we release Ember 2.0, which we don't +anticipate happening for some time. + +## Recent Developments + +### Router Facelift + +Over the past few months, Alex Matchneer has taken the Ember router to the next +level. Alex's changes focus on making the router an excellent tool for managing +complex asynchronous flows (like authentication), and you can learn all about it +in his recently completed guides: + +* [Asynchronous Routing][1] +* [Preventing and Retrying Transitions][2] + +[1]: http://emberjs.com/guides/routing/asynchronous-routing/ +[2]: http://emberjs.com/guides/routing/preventing-and-retrying-transitions/ + +### Preparation for Modules + +In the years since we started Ember, the JavaScript module ecosystem has become +increasingly mature. + +Today, tools like [require.js][3] and module systems like [AMD][4], [Node][5], +and [ES6 Modules][6] continue to gain traction. Increasingly, people are using +named modules and module loaders rather than storing their code in globals. + +To prepare for this future, all of the code lookup and naming conventions in +Ember.js now go through a single `Resolver`. The default `Resolver` still looks +for code under global namespaces, but [Ember App Kit][7] already provides an +alternative resolver that looks for code in AMD modules. + +In the near future, we plan to roll in first-class support for modules into the +framework, based on the experiences of users of the increasingly popular Ember +App Kit. + +[3]: http://requirejs.org/ +[4]: https://github.com/amdjs/amdjs-api/wiki/AMD +[5]: http://nodejs.org/api/modules.html +[6]: https://github.com/square/es6-module-transpiler +[7]: https://github.com/stefanpenner/ember-app-kit/blob/master/vendor/loader.js#L41-L136 + +### Ember Testing + +The Ember community has always been passionate about testing. Even at the earliest +meetups, testing was one of the most frequently asked-about topics, and testing +featured prominently in our thinking as we built out the new router. + +As we got closer to Ember 1.0, we realized that we needed to provide an official +set of testing-framework agnostic testing helpers. The Ember Testing +package is the start of a longer-term focus on testing facilities that we plan +to improve even more in the 1.x timeframe. + +You can see some of our thoughts for future improvements on the [Ember +Discussion Forum][8]. + +[8]: http://discuss.emberjs.com/t/ember-testing-improvements/1652 + +### Ember Inspector for Chrome + +Teddy Zeenny's relentless work on the Ember Inspector has been some of the most +awe-inspiring work we've seen in open source. + +The [Ember Inspector](https://chrome.google.com/webstore/detail/ember-inspector/bmdblncegkenkacieihfhpjfppoconhi) that ships with Ember 1.0 makes it easy to see how an +Ember application is laid out, and presents all of the naming conventions in an easy-to-read table. +If you're using Ember Data, it also lets you see all of the records that your application has loaded at +a glance. + +Coming up next is asynchronous debugging to help make sense of some of the more +quirky behavior of Promises. + + + +### Performance, Performance, Performance + +A number of community members, especially Kris Selden, Erik Bryn and Robin Ward +have done heroic work lately on performance. + +Over the years, Ember's internals have been significantly sped up time and +time again, and we will continue to hone the performance of Ember going forward. + +In the 1.x timeframe, we have a number of ideas that should significantly +improve rendering performance and decrease the amount of GC during rendering, +so keep an eye out! + +### Docs, Docs, Docs + +The early lack of good documentation for Ember seriously pained us, as we're all +big believers in the idea that user confusion should be considered a bug in the +framework. + +Over the past year, we've significantly improved both the [API documentation][9] +and the [Guides][10]. Trek Glowacki has led up the effort, which has resulted in +comprehensive coverage of how to use Ember, an excellent, up-to-date Getting +Started Guide, and most recently, a Cookbook section for common scenarios. + +For Ember 1.0, Trek led a documentation audit of all of the API documentation in +the entire codebase, which led to 1,700 new lines of documentation, and an +across-the-board freshening for new idioms and best practices. + +[9]: http://emberjs.com/api +[10]: http://emberjs.com/guides + +### Ember Data 1.0 Beta 1 + +With the release of Ember 1.0, we're glad to also release the first beta of +Ember Data 1.0. + +Ember Data 1.0 is a reboot of our data layer. The focus of the effort (codenamed +`jj-abrams`, famous for franchise reboots) was: + +* A more flexible codebase, able to handle streaming, custom JSON, and edits + while saving with ease. If you've found Ember Data too inflexible for your + backend in the past, try it again! +* Asynchronous operations are now all backed by promises. This will integrate + better with Ember's own asynchronous handling, and make it easier to combine + and pipeline asynchronous operations like `find` and `save`. +* Better support for modules. Ember.js itself now has good support for modules, + through Ember App Kit's drop-in resolver, but Ember Data's reliance on global + lookups (through `App.Post.find`, etc.) made Ember Data hard to use with + modules. The Ember Data 1.0 API is much more friendly to modules, and + therefore the future of Ember.js and the web platform. +* Much better documentation of Ember Data's APIs, including the adapter and + serializer APIs. Flexible APIs are no good if it's impossible to learn about + them. + +If you're a current user of Ember Data, you may want to check out the +[Transition Guide][11]. If you have issues upgrading that aren't covered in the +guide, please let us know right away so we can improve it. + +Note: If you aren't ready to upgrade just yet, we've released Ember Data 0.14, +which includes a number of useful performance optimization for Ember Data 0.13 +but no breaking changes. + +[11]: https://github.com/emberjs/data/blob/master/TRANSITION.md + +## Community + +The Ember community is amazing. + + + + +In addition to the insane amount of work that members of the Ember community +have been doing to prepare the Ember 1.0 release, the community has also been +churning out a number of awesome Ember-related projects. + +* [Ember App Kit][12]: An effort by a number of members of the Ember community + to work on tooling for Ember that will eventually become the core of official + Ember tools. +* [Ember Tools][13]: Similar work by Ryan Florence, which will be merged into Ember + App Kit as we begin to bring tooling into core. +* [Bootstrap for Ember][14]: Twitter Bootstrap wrapped in Ember components! +* [Ember Animated Outlet][15]: Support for animated `{{outlet}}`s and + `{{link-to}}` on top of the current Ember by Sebastian Seilund of Billy's + Billings. +* [Admin.js][16]: An awesome, flexible and configurable admin for your site + written in Ember by Gordon Hempton. You can use it with an Ember app or just + to provide an admin interface for your Rails, Django, PHP, or whatever app! +* [The Ember Hot Seat][17]: A regular podcast brought to you by DeVaris Brown. + It regularly features members of the Ember Core Team and prominent members of + the Ember community. +* [EmberWatch][23]: Philip Poot's EmberWatch Twitter account and website will + keep you up-to-date on the latest projects and news. +* [Ember Weekly][24]: Ember Weekly, curated by the inimitable Owain + Williams, packs all the Ember news that's fit to print into your inbox + every week. +* And way more projects. Keep an eye out on this blog, or follow us on the + official @emberjs Twitter account. We plan to feature more projects like these + in the future. + +[12]: https://github.com/stefanpenner/ember-app-kit +[13]: https://github.com/rpflorence/ember-tools +[14]: http://ember-addons.github.io/bootstrap-for-ember/dist/ +[15]: https://github.com/billysbilling/ember-animated-outlet +[16]: http://adminjs.com/ +[17]: http://emberhotseat.com/ +[23]: https://twitter.com/EmberWatch +[24]: http://emberweekly.com/ + +We've also been grateful to be the beneficiary of large amounts of support from +a number of companies over the years. + +* LivingSocial, which funded much of the original work on Ember Data. +* Yapp, whose employees have been working on Ember (and SproutCore before + Ember) for years, and which has dedicated countless man-hours to making Ember + better. +* Zendesk, an early user and contributor to Ember. Thank you for betting on + Ember as early as you did. +* McGraw-Hill Education Labs, which has been funding Ember work for over a year, + with great patience, resolve and vision. +* Tilde, which employs Tom, Yehuda, Peter and Leah, and which handles much of + the (unseen) administrative work of the project. +* Billy's Billings, which has given Sebastian Selund time to work on Ember, and + hosted the work on `ember-animated-outlet`, which will make its way into a + future version of Ember. + +Finally, a number of large open source projects have bet on Ember. These +projects contribute significantly to Ember's development, and also give Ember +users a place to look at large, real-world projects. + +* [Travis CI][18]: A very early Ember adopter. The Ember project makes heavy use of + Travis, so thank you! +* [Discourse][19]: The increasingly popular forum engine that now powers + TalkingPointsMemo and BoingBoing. These guys have contributed heavily to Ember + and its community. +* Balanced: Balanced is an [open-source][20], **open company**. They use Ember + for their [dashboard][21]. + +[18]: https://travis-ci.org/ +[19]: http://www.discourse.org/ +[20]: https://github.com/balanced +[21]: https://github.com/balanced/balanced-dashboard + +## Undefined Semantics + +There are two areas of Ember.js that have semantics that may accidentally work +in some cases today, but are the source of a number of bugs, and which we don't +plan to support in the future. + +### Observer Timing + +At present, Ember observers sometimes fire synchronously, but sometimes fire +asynchronously. The only thing your code should rely on is that the observer +will fire **after** the property it observes has changed. + +We plan to bring all observers into alignment with [Object.observe][25], a +future JavaScript feature. In the future, observers will **never** fire +synchronously. If you rely on specific timing, your code may break. + +[25]: http://wiki.ecmascript.org/doku.php?id=harmony:observe + +### Observing Properties of Framework Objects + +In general, you should not observe properties of framework objects defined by +the framework that are not explicitly documented as observable. Some of these +observations may happen to work today, but may not work in the future. + +For example, you should not observe the `element` property on an Ember view or +component. Instead, you should use the `didInsertElement` hook. + +If you find yourself observing a framework-defined property that is not +documented as observable to work around an issue, **please** file an issue with +us so we can give you a publicly defined API. + +## The Future + +Despite our commitment to stability, we are not resting on our laurels. We have +an aggressive pipeline of new features planned, which we'll be announcing soon. + +We're also switching our releases to follow a more Chrome-like model. This means +that you can expect a new release every six weeks. We'll have more details about +this soon. + +## Thanks + +Special thanks to a number of community members who have done heroic work +leading up to Ember 1.0: + +* Eric Berry, for the new Cookbook section in the guides and examples +* Paul Chavard, for help reviewing Ember Data 1.0 Beta 1 +* Domenic Denicola, for putting us on the right path with promises +* Dan Gebhardt, for website infrastructure +* David Hamilton, for the Array Computed feature +* Robert Jackson, for the new emberjs.com/builds +* Julien Knebel, for design work +* Alex Matchneer, for the async router guide +* Luke Melia, for `actions` namespacing, last minute bugfix work, and the Ember NYC community +* Alex Navasardyan, for inline examples on the homepage and design work +* Stanley Stuart, for testing infrastructure +* Igor Terzic, for help reviewing Ember Data 1.0 Beta 1 +* Teddy Zeenny, for the Ember Inspector +* The 300 people who submitted code and documentation to Ember 1.0 +* The 131 people who submitted code and documentation to Ember Data 1.0 Beta 1 +* The 269 people who helped with [emberjs.com](http://emberjs.com) + +Go forth and build great things! diff --git a/source/blog/feed.xml.builder b/source/blog/feed.xml.builder index d68a4d2..c2498ba 100644 --- a/source/blog/feed.xml.builder +++ b/source/blog/feed.xml.builder @@ -11,9 +11,11 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom" do blog.articles[0..5].each do |article| xml.entry do + entry_url = "http://emberjs.cn#{article.url}" + xml.title article.title - xml.link "rel" => "alternate", "href" => article.url - xml.id article.url + xml.link "rel" => "alternate", "href" => entry_url + xml.id entry_url xml.published article.date.to_time.iso8601 xml.updated article.date.to_time.iso8601 xml.author { xml.name "EmberJS.CN" } diff --git a/source/guides/components/defining-a-component.md b/source/guides/components/defining-a-component.md index bf518a7..246de8d 100644 --- a/source/guides/components/defining-a-component.md +++ b/source/guides/components/defining-a-component.md @@ -4,6 +4,10 @@ 为了定义一个组件,需要先创建一个名字以`components/`开始的模板。例如:如果需要定义一个新组建`{{blog-post}}`,需要创建`components/blog-post`模板。 + + 如果在HTML文件中使用` diff --git a/source/guides/getting-started/displaying-the-number-of-incomplete-todos.md b/source/guides/getting-started/displaying-the-number-of-incomplete-todos.md index 8936ffb..2cf7f73 100644 --- a/source/guides/getting-started/displaying-the-number-of-incomplete-todos.md +++ b/source/guides/getting-started/displaying-the-number-of-incomplete-todos.md @@ -15,6 +15,8 @@ 在模板的控制器`Todos.TodosController`中实现以上的属性: ```javascript +// 提示:下面的代码不能放入'actions' + // ... 为保持代码简洁,在此省略了其他代码 ... remaining: function () { return this.filterProperty('isCompleted', false).get('length'); diff --git a/source/guides/getting-started/index.md b/source/guides/getting-started/index.md index 6a82cba..4da7ca5 100644 --- a/source/guides/getting-started/index.md +++ b/source/guides/getting-started/index.md @@ -1,3 +1,3 @@ 欢迎来到Ember.js世界!本入门指南将使用Ember.js来创建一个简单的应用,并简单介绍整个框架背后的一些核心概念。阅读本指南需要熟悉一些基本的Web技术,比如Javascript、HTML、CSS和一些诸如浏览器[Web审查器](https://developers.google.com/chrome-developer-tools/)。 -本入门指南采用非常流行的[TodoMVC示例](http://addyosmani.github.com/todomvc/) 应用作为例子,介绍如何使用Ember.js来开发TodoMVC。 +本入门指南采用非常流行的[TodoMVC示例](http://todomvc.com) 应用作为例子,介绍如何使用Ember.js来开发TodoMVC。 diff --git a/source/guides/getting-started/marking-a-model-as-complete-incomplete.md b/source/guides/getting-started/marking-a-model-as-complete-incomplete.md index 34ca9e9..ca087ad 100644 --- a/source/guides/getting-started/marking-a-model-as-complete-incomplete.md +++ b/source/guides/getting-started/marking-a-model-as-complete-incomplete.md @@ -29,10 +29,10 @@ Todos.TodoController = Ember.ObjectController.extend({ var model = this.get('model'); if (value === undefined) { - // property being used as getter + // property being used as a getter return model.get('isCompleted'); } else { - // property being used as setter + // property being used as a setter model.set('isCompleted', value); model.save(); return value; @@ -43,6 +43,8 @@ Todos.TodoController = Ember.ObjectController.extend({ 当模板中需要显示待办事项的当前`isCompleted`状态,这个属性将这个问题委派给其底层的`model`。当被调用时因为用户触发了模板中的复选框而带有一个参数,那么这个属性将设置`model`的`isCompleted`属性为传入的参数值(`true`或者`false`),并将模型的变更持久化,返回传入的值以便复选框显示正确。 +`isCompleted`函数被声明为一个[计算属性](/guides/object-model/computed-properties/),其值依赖于`model.isCompleted`。 + 在`index.html`中包含`js/controllers/todo_controller.js`依赖: ```html diff --git a/source/guides/getting-started/modeling-data.md b/source/guides/getting-started/modeling-data.md index fc4408a..6d5b791 100644 --- a/source/guides/getting-started/modeling-data.md +++ b/source/guides/getting-started/modeling-data.md @@ -34,5 +34,5 @@ Todos.Todo = DS.Model.extend({ ### 附加资源 * [采用`diff`格式显示这步骤所作的修改](https://github.com/emberjs/quickstart-code-sample/commit/a1ccdb43df29d316a7729321764c00b8d850fcd1) - * [创建一个存储器指南](/guides/models/defining-a-store) + * [Store使用指南](/guides/models/using-the-store) * [定义模型指南](/guides/models/defining-models) diff --git a/source/guides/getting-started/obtaining-emberjs-and-dependencies.md b/source/guides/getting-started/obtaining-emberjs-and-dependencies.md index 1deac43..63c484a 100644 --- a/source/guides/getting-started/obtaining-emberjs-and-dependencies.md +++ b/source/guides/getting-started/obtaining-emberjs-and-dependencies.md @@ -13,8 +13,8 @@ TodoMVC的依赖: ```html - - + + diff --git a/source/guides/getting-started/using-other-adapters.md b/source/guides/getting-started/using-other-adapters.md index 81167cb..f75ba7a 100644 --- a/source/guides/getting-started/using-other-adapters.md +++ b/source/guides/getting-started/using-other-adapters.md @@ -19,7 +19,7 @@ In `index.html` include `js/libs/local_storage_adapter.js` as a dependency: ```html - + ``` diff --git a/source/guides/models/connecting-to-a-streaming-api.md b/source/guides/models/connecting-to-a-streaming-api.md new file mode 100644 index 0000000..e69de29 diff --git a/source/guides/models/defining-a-store.md b/source/guides/models/defining-a-store.md deleted file mode 100644 index adb0925..0000000 --- a/source/guides/models/defining-a-store.md +++ /dev/null @@ -1,28 +0,0 @@ -英文原文:[http://emberjs.com/guides/models/defining-a-store/](http://emberjs.com/guides/models/defining-a-store/) - -## 创建一个存储器 - -每一个使用 Ember Data 的应用都会有一个存储器。这个存储器会成为已加载模型的贮存器,并且检索还未被加载的模型。 - -通常,你可以直接跟模型交互,而不使用存储器。但是你需要让 Ember.js 知道你现在在使用 Ember Data 来管理你的模型。 -要达到这样的目的,我们可以简单地在Ember.Application中定义一个DS.Store的子类,如下所示: - -```js -App.Store = DS.Store.extend({ - revision: 13 -}); -``` - -注意这里的`revision`属性,它是 API 的修订版本号,在Ember Data 1.0 版本之前用来提醒你公共 API 的重大更改。 -查看[Breaking Changes 文档][1]以获取更多信息。 - -[1]: https://github.com/emberjs/data/blob/master/BREAKING_CHANGES.md - -如果你想要定制存储器,你可以在创建子类的时候去定制。例如,如果你想要用另外一个适配器去替换默认的`DS.RESTAdapter`,你可以像下面这样做: - -```js -App.Store = DS.Store.extend({ - revision: 13, - adapter: 'App.MyCustomAdapter' -}); -``` diff --git a/source/guides/models/defining-models.md b/source/guides/models/defining-models.md index 8d873b7..d51fce3 100644 --- a/source/guides/models/defining-models.md +++ b/source/guides/models/defining-models.md @@ -1,22 +1,73 @@ 英文原文:[http://emberjs.com/guides/models/defining-models/](http://emberjs.com/guides/models/defining-models/) -## 定义模型 +A model is a class that defines the properties and behavior of the +data that you present to the user. Anything that the user expects to see +if they leave your app and come back later (or if they refresh the page) +should be represented by a model. -你可以通过创建`DS.Model`的子类来表示每一种类型的模型: +For every model in your application, create a subclass of `DS.Model`: ```javascript App.Person = DS.Model.extend(); ``` -### 属性标志 +After you have defined a model class, you can start finding and creating +records of that type. When interacting with the store, you will need to +specify a record's type using the model name. For example, the store's +`find()` method expects a string as the first argument to tell it what +type of record to find: -你可以通过`DS.attr`指定模型具有哪些属性。你可以像其他属性一样使用属性标志,包括作为计算属性的一部分。 +```js +store.find('person', 1); +``` + +The table below shows how model names map to model classes. + + + + + + + + + + + + + + + + +
Model NameModel Class
photoApp.Photo
admin-user-profileApp.AdminUserProfile
+ +### Defining Attributes + +You can specify which attributes a model has by using `DS.attr`. + +```javascript +var attr = DS.attr; + +App.Person = DS.Model.extend({ + firstName: attr(), + lastName: attr(), + birthday: attr() +}); +``` + +Attributes are used when turning the JSON payload returned from your +server into a record, and when serializing a record to save back to the +server after it has been modified. + +You can use attributes just like any other property, including as part of a +computed property. Frequently, you will want to define computed +properties that combine or transform primitive attributes. ```javascript +var attr = DS.attr; + App.Person = DS.Model.extend({ - firstName: DS.attr('string'), - lastName: DS.attr('string'), - birthday: DS.attr('date'), + firstName: attr(), + lastName: attr(), fullName: function() { return this.get('firstName') + ' ' + this.get('lastName'); @@ -24,11 +75,23 @@ App.Person = DS.Model.extend({ }); ``` +For more about adding computed properties to your classes, see [Computed Properties](/guides/object-model/computed-properties). + +If you don't specify the type of the attribute, it will be whatever was +provided by the server. You can make sure that an attribute is always +coerced into a particular type by passing a `type` option to `attr`: + +```js +App.Person = DS.Model.extend({ + birthday: DS.attr('date') +}); +``` + 默认情况下,REST 适配器支持的属性类型有`string`, `number`, `boolean`和`date`。 传统的适配器会提供额外的属性类型,并支持你注册自定义的属性类型。 详情请查看[documentation section on the REST Adapter](/guides/models/the-rest-adapter)。 -### 关联模型 +### 定义关联模型 Ember Data 包括了几个内置的关联类型,以帮助你确定你的模型如何相互关联的。 @@ -38,11 +101,11 @@ Ember Data 包括了几个内置的关联类型,以帮助你确定你的模型 ```js App.User = DS.Model.extend({ - profile: DS.belongsTo('App.Profile') + profile: DS.belongsTo('profile') }); App.Profile = DS.Model.extend({ - user: DS.belongsTo('App.User') + user: DS.belongsTo('user') }); ``` @@ -52,11 +115,11 @@ App.Profile = DS.Model.extend({ ```js App.Post = DS.Model.extend({ - comments: DS.hasMany('App.Comment') + comments: DS.hasMany('comment') }); App.Comment = DS.Model.extend({ - post: DS.belongsTo('App.Post') + post: DS.belongsTo('post') }); ``` @@ -66,36 +129,40 @@ App.Comment = DS.Model.extend({ ```js App.Post = DS.Model.extend({ - tags: DS.hasMany('App.Tag') + tags: DS.hasMany('tag') }); App.Tag = DS.Model.extend({ - posts: DS.hasMany('App.Post') + posts: DS.hasMany('post') }); ``` #### 显式反转 -自[本周Ember.js,2012-11-2](http://emberjs.com/blog/2012/11/02/this-week-in-ember-js.html)起, - -Ember Data知道当设定一个`belongsTo`的关联关系时,子应该要被添加到对应的父的`hasMany`关联关系中去。 - -但是不幸的是,它并不知道哪一个`hasMany`关联关系应该得到更新。因此,Ember -Data选择其找到的第一个拥有相同类型的子关联来进行更新。 +Ember Data will do its best to discover which relationships map to one +another. In the one-to-many code above, for example, Ember Data can figure out that +changing the `comments` relationship should update the `post` +relationship on the inverse because `post` is the only relationship to +that model. -因为可能存在许多具有相同类型的`belongsTo`/`hasMany`,这是可以显式的指定对应的反转对象: +However, sometimes you may have multiple `belongsTo`/`hasMany`s for the +same type. You can specify which property on the related model is the +inverse using `DS.attr`'s `inverse` option: ```javascript +var belongsTo = DS.belongsTo, + hasMany = DS.hasMany; + App.Comment = DS.Model.extend({ - onePost: DS.belongsTo("App.Post"), - twoPost: DS.belongsTo("App.Post"), - redPost: DS.belongsTo("App.Post"), - bluePost: DS.belongsTo("App.Post") + onePost: belongsTo("post"), + twoPost: belongsTo("post"), + redPost: belongsTo("post"), + bluePost: belongsTo("post") }); App.Post = DS.Model.extend({ - comments: DS.hasMany('App.Comment', { + comments: hasMany('comment', { inverse: 'redPost' }) }); @@ -103,7 +170,6 @@ App.Post = DS.Model.extend({ 当然也可以在`belongsTo`一侧指定,它将按照预期那样工作。 - #### 嵌套对象 当有数据结构的嵌套数据不使用或者需要IDS,必须指定`hasMany`包含的`belongsTo`。 @@ -114,7 +180,7 @@ App.Post = DS.Model.extend({ App.Comment = DS.Model.extend({}); App.Post = DS.Model.extend({ - comments: DS.hasMany('App.Comment') + comments: DS.hasMany('comment') }); App.Adapter.map('App.Post', { diff --git a/source/guides/models/filtering-records.md b/source/guides/models/filtering-records.md new file mode 100644 index 0000000..e69de29 diff --git a/source/guides/models/finding-a-record.md b/source/guides/models/finding-a-record.md new file mode 100644 index 0000000..f8bd123 --- /dev/null +++ b/source/guides/models/finding-a-record.md @@ -0,0 +1,43 @@ +英文原文:[http://emberjs.com/guides/models/finding-models/](http://emberjs.com/guides/models/finding-models/) + +## 查找一个对象 + +通过将记录对应的模型和唯一ID传递给`find()`方法,可以获取记录。这将返回一个承诺,并解析为请求的记录: + +```js +store.find('post', 1).then(function(post) { + post.set('title', "My Dark Twisted Fantasy"); +}); +``` + +### Integrating with the Route's Model Hook + +As discussed in [Specifying a Route's +Model](/guides/routing/specifying-a-routes-model), routes are +responsible for telling their template which model to render. + +`Ember.Route`'s `model` hook supports asynchronous values +out-of-the-box. If you return a promise from the `model` hook, the +router will wait until the promise has resolved to render the +template. + +This makes it easy to write apps with asynchronous data using Ember +Data. Just return the requested record from the `model` hook, and let +Ember deal with figuring out whether a network request is needed or not: + +```js +App.Router.map(function() { + this.resource('post', { path: ':post_id' }); +}); + +App.PostRoute = Ember.Route.extend({ + model: function(params) { + return this.get('store').find('post', params.post_id); + } +}); +``` + +In fact, this pattern is so common, the above `model` hook is the +default implementation for routes with a dynamic segment. If you're +using Ember Data, you only need to override the `model` hook if you need +to return a model different from the record with the provided ID. diff --git a/source/guides/models/finding-all-records-of-a-type.md b/source/guides/models/finding-all-records-of-a-type.md new file mode 100644 index 0000000..8c67205 --- /dev/null +++ b/source/guides/models/finding-all-records-of-a-type.md @@ -0,0 +1,21 @@ +You can find all records for a given model by calling the store's +`findAll()` method: + +```js +var posts = store.findAll('post'); +``` + +This will return an instance of `DS.RecordArray`. Like with records, the +record array will start in a loading state with a `length` of `0`, but +you can immediately use it in your templates. When the server responds +with results, the templates will watch for changes in the length of the +array and update themselves automatically. + +**Note** `DS.RecordArray` is not a JavaScript array, it is an object that +implements `Ember.Enumerable`. If you want to, for example, retrieve +records by index, you must use the `objectAt(index)` method. Since the +object is not a JavaScript array, using the `[]` notation will not work. +For more information, see [Ember.Enumerable][1] and [Ember.Array][2]. + +[1]: http://emberjs.com/api/classes/Ember.Enumerable.html +[2]: http://emberjs.com/api/classes/Ember.Array.html diff --git a/source/guides/models/finding-models.md b/source/guides/models/finding-models.md deleted file mode 100644 index b271936..0000000 --- a/source/guides/models/finding-models.md +++ /dev/null @@ -1,38 +0,0 @@ -英文原文:[http://emberjs.com/guides/models/finding-models/](http://emberjs.com/guides/models/finding-models/) - -## 查找模型 - -通过将记录的唯一ID传递给`find()`方法,可以获取记录: - -```js -var post = App.Post.find(1); -``` - -如果该ID的记录已经被创建,那么它将被立即返回。这个特性被称为 _标示符映射_ 。 - -否则,一个新的记录将被创建,其状态将被设置为加载中,并且返回。返回的记录可以在模板文件中使用;因为Ember.js中一切都是支持绑定的,因此模板在记录完成加载后会自动得到更新。 - -### 查找所有记录 - -如果调用`find()`方法时不带任何参数,将返回该模型的所有记录: - -```js -var posts = App.Post.find(); -``` - -这将返回一个`DS.RecordArray`的实例。跟记录一样,记录数组开始的时候状态也是加载中,并且`length`为0,但是已经可以在模板文件中使用了。当服务端返回结果时,模板会监听数组长度的变化,从而自动的更新自己的内容。 - -注意:`DS.RecordArray`不是一个Javascript数组,它是一个实现了`Ember.Enumerable`接口的对象。如果需要通过索引来获取对象,可以使用`objectAt(index)`方法。由于该对象不是Javascript数组,因此不能使用`[]`来取其中的元素。更多详细信息请查看[Ember.Enumerable][1]和[Ember.Array][2]。 - -[1]: http://emberjs.com/api/classes/Ember.Enumerable.html -[2]: http://emberjs.com/api/classes/Ember.Array.html - -### 查询 - -通过将一个哈希传递给`find()`方法,可以实现查询: - -```js -var people = App.Person.find({ name: "Peter" }); -``` - -哈希的内容对于Ember Data来说是透明的,它将被服务器端解析并返回对应的记录。 diff --git a/source/guides/models/frequently-asked-questions.md b/source/guides/models/frequently-asked-questions.md new file mode 100644 index 0000000..9db8387 --- /dev/null +++ b/source/guides/models/frequently-asked-questions.md @@ -0,0 +1,87 @@ +#### Should I use a query or a filter to search records? + +It depends on how many records you want to search and whether they have +been loaded into the store. + +_Queries_ are useful for doing searches of hundreds, thousands, or even +millions of records. You just hand the search options to your server, +and it is responsible for handing you back the list of records that +match. Because the response from the server includes the ID of all of +the records that matched, it doesn't matter if the store hadn't loaded +them previously; it sees that they are not in the cache and can request +the records by ID if necessary. + +The downside of queries is that they do not live update, they are +slower, and they require that your server support the kind of queries +that you wish to perform. + +Because the server decides which records match the query, not the store, +queries do not live update. If you want to update them, you must +manually call `reload()` and wait for the server to respond. If you +create a new record on the client, it will not show up in the results +until you both save the new record to the server and reload the query +results. + +Because the store must confer with your server to determine the results +of a query, it necessitates a network request. This can feel slow to +users, especially if they are on a slow connection or your server is +slow to respond. The typical speed of JavaScript web applications can +heighten the perceived slowness when the server must be consulted. + +Lastly, performing queries requires collaboration between the store and +your server. By default, Ember Data will send the search options that +you pass as the body of an HTTP request to your server. If your server +does not support requests in this format, you will need to either change +your server to do so, or customize how queries are sent by creating a +custom adapter. + +_Filters_, on the other hand, perform a live search of all of the records +in the store's cache. As soon as a new record is loaded into the store, +the filter will check to see if the record matches, and if so, add it to +the array of search results. If that array is displayed in a template, +it will update automatically. + +Filters also take into account newly created records that have not been +saved, and records that have been modified but not yet saved. If you +want records to show up in search results as soon as they are created or +modified on the client, you should use a filter. + +Keep in mind that records will not show up in a filter if the store +doesn't know about them. You can ensure that a record is in the store by +using the store's `push()` method. + +There is also a limit to how many records you can reasonably keep in +memory and search before you start hitting performance issues. + +Finally, keep in mind that you can combine queries and filters to take +advantage of their respective strengths and weaknesses. Remember that +records returned by a query to the server are cached in the store. You +can use this fact to perform a query that starts matching records into +the store, then create a filter that matches the same records. + +This will offload searching all of the possible records to the server, +while still creating a live updating list that includes records created +and modified on the client. + +```js +App.PostsFavoritedRoute = Ember.Route.extend({ + model: function() { + var store = this.get('store'); + + // Kick off a query to the server for all posts that + // the user has favorited. Note that we're ignoring the results + // of the query; we're just relying on the side-effect that they + // will be loaded into the store so we can filter them. + store.findQuery('posts', { favorited: true }); + + // Create a filter for all favorited posts that will be displayed in + // the template. Any favorited posts that are already in the store + // will be displayed immediately; as // results from the above query are + // returned from the server, they will also begin to appear. + return store.filter('posts', function(post) { + return post.get('isFavorited'); + }); + } +}); +``` + diff --git a/source/guides/models/index.md b/source/guides/models/index.md index 60b99ae..49df64d 100644 --- a/source/guides/models/index.md +++ b/source/guides/models/index.md @@ -2,23 +2,202 @@ ## 模型 -在大部分的 Ember.js 应用里,模型是通过 [Ember Data](https://github.com/emberjs/data) 来处理的。Ember -Data 是一个由 Ember.js 写的库,它使我们可以很方便从服务器端取回记录并动态改变浏览器中的内容,然后保存这些更改回服务器。 +In Ember, every route has an associated model. This model is set by +implementing a route's `model` hook, by passing the model as an argument +to `{{link-to}}`, or by calling a route's `transitionTo()` method. -你可以在服务器端找到像 ActiveRecord 这样的 ORM,不过它提供了专门为浏览器端的 Javascript 环境设计的工具方法。 +See [Specifying a Route's Model](/guides/routing/specifying-a-routes-model) for more information +on setting a route's model. + +For simple applications, you can get by using jQuery to load JSON data +from a server, then use those JSON objects as models. + +However, using a model library that manages finding models, making +changes, and saving them back to the server can dramatically simplify +your code while improving the robustness and performance of your +application. + +Many Ember apps use [Ember Data][emberdata] to handle this. +Ember Data is a library that integrates tightly with Ember.js to make it +easy to retrieve records from a server, cache them for performance, +save updates back to the server, and create new records on the client. Ember Data 并不需要任务配置就可以通过 RESTful JSON API 的惯例来加载和保存记录和关系。 -我们也知道世界上还有很多Web服务的API,其中许多会是荒唐的,矛盾的和容易失去控制。 -Ember Data 被设计成可配置的,无论你想要怎么样的持久层它都可以满足。 +If you need to integrate your Ember.js app with existing JSON APIs that +do not follow strong conventions, Ember Data is designed to be easily +configurable to work with whatever data your server returns. -目前, Ember Data还是作为Ember.js的一个独立的库,与此同时,我们仍然在扩展适配器的API以便支持更多功能。 -在Ember Data被作为标准配置的一部分之前,你可以从其[builds.emberjs.com][builds]下载到从"master"分支编译得到的最新的拷贝。 +Ember Data is also designed to work with streaming APIs like +socket.io, Firebase, or WebSockets. You can open a socket to your server +and push changes to records into the store whenever they occur. + +目前,Ember Data还是作为Ember.js的一个独立的库。在Ember Data被作为标准配置的一部分之前,你可以从其[builds.emberjs.com][builds]下载最新的版本。 * [Development][development-build] * [Minified][minified-build] [emberdata]: https://github.com/emberjs/data -[builds]: http://builds.emberjs.com -[development-build]: http://builds.emberjs.com/latest/ember-data.js -[minified-build]: http://builds.emberjs.com/latest/ember-data.min.js +[builds]: http://emberjs.com/builds +[development-build]: http://builds.emberjs.com/canary/ember-data.js +[minified-build]: http://builds.emberjs.com/canary/ember-data.min.js + +### Core Concepts + +Learning to use Ember Data is easiest once you understand some of the +concepts that underpin its design. + +#### Store + +The **store** is the central repository of records in your application. +Both your application's controllers and routes have access to this +shared store; when they need to display or modify a record, they will +first ask the store for it. + +#### Models + +A **model** is a class that defines the properties and behavior of the +data that you present to the user. Anything that the user expects to see +if they leave your app and come back later (or if they refresh the page) +should be represented by a model. + +For example, if you were writing a web application for placing orders at +a restaurant, you might have models like `Order`, `LineItem`, and +`MenuItem`. + +Models define the type of data that will be provided by your server. For +example, a `Person` model might have a `firstName` attribute that is a +string, and a `birthday` attribute that is a date. + +A model also describes its relationships with other objects. For +example, an `Order` may have many `LineItems`, and a `LineItem` may +belong to a particular `Order`. + +Models don't have any data themselves; they just define the properties and +behavior of specific instances, which are called _records_. + +#### Records + +A **record** is an instance of a model that contains data loaded from a +server. Your application can also create new records and save them back +to the server. + +Records are uniquely identified by two things: + +1. A model type. +2. A globally unique ID. + +For example, if you were writing a contact management app, you might +have a model called `Person`. An individual record in your app might +have a type of `Person` and an ID of `1` or `steve-buscemi`. + +IDs are usually assigned by the server when you save them for the first +time, but you can also generate IDs client-side. + +#### Adapter + +An **adapter** is an object that knows about your particular server +backend and is responsible for translating requests for and changes to +records into the appropriate calls to your server. + +For example, if your application asks for a `person` record with an ID +of `1`, how should Ember Data load it? Is it over HTTP or a WebSocket? +If it's HTTP, is the URL `/person/1` or `/resources/people/1`? + +The adapter is responsible for answering all of these questions. +Whenever your app asks the store for a record that it doesn't have +cached, it will ask the adapter for it. If you change a record and save +it, the store will hand the record to the adapter to send the +appropriate data to your server and confirm that the save was +successful. + +#### Serializer + +A **serializer** is responsible for turning a raw JSON payload returned +from your server into a record object. + +JSON APIs may represent attributes and relationships in many different +ways. For example, some attribute names may be `camelCased` and others +may be `under_scored`. Representing relationships is even more diverse: +they may be encoded as an array of IDs, an array of embedded objects, or +as foreign keys. + +When the adapter gets a payload back for a particular record, it will +give that payload to the serializer to normalize into the form that +Ember Data is expecting. + +While most people will use a serializer for normalizing JSON, because +Ember Data treats these payloads as opaque objects, there's no reason +they couldn't be binary data stored in a `Blob` or +[ArrayBuffer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays/ArrayBuffer). + +#### Automatic Caching + +The store will automatically cache records for you. If a record had already +been loaded, asking for it a second time will always return the same +object instance. This minimizes the number of round-trips to the +server, and allows your application to render UI to the user as fast as +possible. + +For example, the first time your application asks the store for a +`person` record with an ID of `1`, it will fetch that information from +your server. + +However, the next time your app asks for a `person` with ID `1`, the +store will notice that it had already retrieved and cached that +information from the server. Instead of sending another request for the +same information, it will give your application the same record it had +provided it the first time. This feature—always returning the same +record object, no matter how many times you look it up—is sometimes +called an _identity map_. + +Using an identity map is important because it ensures that changes you +make in one part of your UI are propagated to other parts of the UI. It +also means that you don't have to manually keep records in sync—you can +ask for a record by ID and not have to worry about whether other parts +of your application have already asked for and loaded it. + +### Architecture Overview + +The first time your application asks the store for a record, the store +sees that it doesn't have a local copy and requests it from your +adapter. Your adapter will go and retrieve the record from your +persistence layer; typically, this will be a JSON representation of the +record served from an HTTP server. + +![Diagram showing process for finding an unloaded record](/images/guides/models/finding-unloaded-record-step1-diagram.png) + +As illustrated in the diagram above, the adapter cannot always return the +requested record immediately. In this case, the adapter must make an +_asynchronous_ request to the server, and only when that request finishes +loading can the record be created with its backing data. + +Because of this asynchronicity, the store immediately returns a +_promise_ from the `find()` method. Similarly, any requests that the +store makes to the adapter also return promises. + +Once the request to the server returns with a JSON payload for the +requested record, the adapter resolves the promise it returned to the +store with the JSON. + +The store then takes that JSON, initializes the record with the +JSON data, and resolves the promise returned to your application +with the newly-loaded record. + +![Diagram showing process for finding an unloaded record after the payload has returned from the server](/images/guides/models/finding-unloaded-record-step2-diagram.png) + +Let's look at what happens if you request a record that the store +already has in its cache. + +![Diagram showing process for finding an unloaded record after the payload has returned from the server](/images/guides/models/finding-loaded-record-diagram.png) + +In this case, because the store already knew about the record, it +returns a promise that it resolves with the record immediately. It does +not need to ask the adapter (and, therefore, the server) for a copy +since it already has it saved locally. + +--- + +These are the core concepts you should understand to get the most out of +Ember Data. The following sections go into more depth about each of +these concepts, and how to use them together. \ No newline at end of file diff --git a/source/guides/models/model-lifecycle.md b/source/guides/models/model-lifecycle.md deleted file mode 100644 index 6de7185..0000000 --- a/source/guides/models/model-lifecycle.md +++ /dev/null @@ -1,69 +0,0 @@ -英文原文:[http://emberjs.com/guides/models/model-lifecycle/](http://emberjs.com/guides/models/model-lifecycle/) - -## 模型生命周期 - - -因为模型必须是异步加载和保存的,所以在给定的时间内一条记录就会存在几个不同的可能状态。每一个`DS.Model`的实例都有一系列布尔类型的属性,用来检查记录的当前状态。 - - -* **isLoaded** 适配器已完成从后端读取记录的当前状态 -* **isDirty** 记录有本地修改且尚未被适配器保存到后端。包括记录被创建或删除(但尚未保存) -* **isSaving** 记录已被发给适配器,但适配器尚未确认修改已成功保存与否 -* **isDeleted** 记录被标记将被删除。当`isDeleted`和`isDirty`同时为真时,记录只是在本地被删除,并没有持久化。`isSaving`为真时,修改正在保存当中。`isDirty`和`isSaving`同时为假时,修改已经保存成功。 -* **isError** 适配器报告说无法保存修改到后端。如果服务器端的验证失败时,也会导致记录的`isValid`属性为假。 -* **isNew** 记录只是在本地被创建,适配器尚未报告记录是否被成功保存。 -* **isValid** 没有客户端验证失败,适配器也没有报告任何服务器端验证失败 - - -另外,你可以定义记录状态发生改变时的事件。比如,你可以在记录加载完之后运行一些代码: - -```js -record.on('didLoad', function() { - console.log("Loaded!"); -}); -``` - -有效的事件包括: - -* `didLoad` -* `didCreate` -* `didUpdate` -* `didDelete` -* `becameError` -* `becameInvalid` - - -### 记录的状态 - -#### 加载中(loading) - - -记录一般都起始于加载中(loading)这个状态。这意味着 Ember Data尚未从适配器收到任何有关记录的属性值为多少,处于什么样的关系之类的信息。你可以将记录放进模板中,一旦记录加载完成(loaded),模板就会自动更新。 - - -#### 加载完成(loaded)/干净 - - -一条记录加载完成(loaded)且干净意味着已经从服务器获得了它的属性以及关系的信息。而且本地对记录尚无修改。 - - -#### 脏 - - -脏的状态意味着记录的属性或关系已经被更改,但尚未通过适配器同步到服务器端。 - - -#### 处理中 - - -一条在进行中的记录是指记录被交给适配器,以保存在本地所做的修改。一旦服务器确认修改的记录被成功保存,记录的状态就变成干净(clean)了 - - -#### 无效 - -如果服务器认为记录的修改无效以至于无法保存,那么这条记录的状态就变成无效(invalid) - - -#### 错误 - -如果一条记录因为除无效之外的其他原因无法保存时,它就进入了错误状态。 diff --git a/source/guides/models/pushing-records-into-the-store.md b/source/guides/models/pushing-records-into-the-store.md new file mode 100644 index 0000000..2199ddf --- /dev/null +++ b/source/guides/models/pushing-records-into-the-store.md @@ -0,0 +1,57 @@ +One way to think about the store is as a cache of all of the records +that have been loaded by your application. If a route or a controller in +your app asks for a record, the store can return it immediately if it is +in the cache. Otherwise, the store must ask the adapter to load it, +which usually means a trip over the network to retrieve it from the +server. + +Instead of waiting for the app to request a record, however, you can +push records into the store's cache ahead of time. + +This is useful if you have a good sense of what records the user +will need next. When they click on a link, instead of waiting for a +network request to finish, Ember.js can render the new template +immediately. It feels instantaneous. + +Another use case for pushing in records is if your application has a +streaming connection to a backend. If a record is created or modified, +you want to update the UI immediately. + +### Pushing Records + +To push a record into the store, call the store's `push()` method. + +For example, imagine we want to preload some data into the store when +the application boots for the first time. + +We can use the `ApplicationRoute` to do so. The `ApplicationRoute` is +the top-most route in the route hierarchy, and its `model` hook gets +called once when the app starts up. + +```js +var attr = DS.attr; + +App.Album = DS.Model.extend({ + title: attr(), + artist: attr(), + songCount: attr() +}); + +App.ApplicationRoute = Ember.Route.extend({ + model: function() { + var store = this.get('store'); + + store.push('album', { + title: "Fewer Moving Parts", + artist: "David Bazan", + songCount: 10 + }); + + store.push('album', { + title: "Calgary b/w I Can't Make You Love Me/Nick Of Time", + artist: "Bon Iver", + songCount: 2 + }); + } +}); +``` diff --git a/source/guides/models/querying-the-server-for-records.md b/source/guides/models/querying-the-server-for-records.md new file mode 100644 index 0000000..32e229a --- /dev/null +++ b/source/guides/models/querying-the-server-for-records.md @@ -0,0 +1,19 @@ +You can query the server by calling the store's `findQuery()` method and +passing a hash of search options. This method returns a promise that +resolves to an array of the search results. + +For example, we could search for all `person` models who had the name of +`Peter`: + +```js +store.findQuery('person', { name: "Peter" }).then(function(people) { + console.log("Found " + people.get('length') + " people named Peter."); +}); +``` + +The hash of search options that you pass to `findQuery()` is opaque to Ember +Data. By default, these options will be sent to your server as the body +of an HTTP `GET` request. + +**Using this feature requires that your server knows how to interpret +query responses.** diff --git a/source/guides/models/using-the-store.md b/source/guides/models/using-the-store.md new file mode 100644 index 0000000..e9acca4 --- /dev/null +++ b/source/guides/models/using-the-store.md @@ -0,0 +1,52 @@ +Before your application can display a model, it must first load it from +the server. Of course, since network requests can be expensive, it is +important to only request a given record once. + +To keep track of which records are loaded and which still need to be +fetched, every controller and route in your application has access to a +**store**. You can think of the store as a cache of all of the records +available in your app. + +### Getting Records into the Store + +There are two ways of getting records into the store. + +First, you can manually push records into the store. This is useful if, +for example, you have a streaming API that notifies your application of +new records as soon as they are created. + +For more about pushing records into the store, see: + +* [Pushing Records into the + Store](/guides/models/pushing-records-into-the-store) + +Second, you can ask the store for a record. If the store doesn't already +have the record loaded, it will ask its _adapter_ to load it from the +server. The record that your server returns will be pushed into the +store automatically. + +Note that if the record _has_ been loaded, the store returns it +immediately without making an unnecessary network request. + +### Accessing the Store + +Once you include the Ember Data library in your application, all of your +controllers and routes will automatically gain access to a new property +called `store`. This instance of `DS.Store` is created for you +automatically and is shared among all of the objects in your +application. + +You will use the store to retrieve records, as well to create new ones. +For example, we might want to find an `App.Person` model with the ID of +`1` from our route's `model` hook: + +```js +App.IndexRoute = Ember.Route.extend({ + model: function() { + var store = this.get('store'); + return store.find('person', 1) + } +}); +``` + +For more about finding records, see [Finding a Record](/guides/models/finding-a-record). diff --git a/source/guides/models/modifying-attributes.md b/source/guides/models/working-with-records.md similarity index 77% rename from source/guides/models/modifying-attributes.md rename to source/guides/models/working-with-records.md index 084933d..1adfe4b 100644 --- a/source/guides/models/modifying-attributes.md +++ b/source/guides/models/working-with-records.md @@ -5,7 +5,7 @@ 一旦一条记录已经加载进来,你就可以开始修改它的属性(attributes)了。属性(attributes)和Ember.js中对象的普通属性(properties)差不多。(译注:这两个词我都译成了属性,读者自行判定其中潜在的区别)。修改记录就是修改记录的属性。 ```js -var tyrion = App.Person.find(1); +var tyrion = this.store.find('person', 1); // ...after the record has loaded tyrion.set('firstName', "Yollo"); ``` @@ -28,7 +28,3 @@ person.set('isAdmin', true); person.get('isDirty'); //=> true ``` - -在修改一条记录前,得保证记录已经被完整地加载进来。如果记录尚未完成加载就修改,Ember Data会抛出一个异常。想了解更多信息,见[模型生命周期][1]. - -[1]: /guides/models/model-lifecycle diff --git a/source/guides/object-model/computed-properties-and-aggregate-data.md b/source/guides/object-model/computed-properties-and-aggregate-data.md index 27917fc..002a23d 100644 --- a/source/guides/object-model/computed-properties-and-aggregate-data.md +++ b/source/guides/object-model/computed-properties-and-aggregate-data.md @@ -14,7 +14,7 @@ App.TodosController = Ember.Controller.extend({ remaining: function() { var todos = this.get('todos'); - return todos.filterProperty('isDone', false).get('length'); + return todos.filterBy('isDone', false).get('length'); }.property('todos.@each.isDone') }); ``` diff --git a/source/guides/object-model/computed-properties.md b/source/guides/object-model/computed-properties.md index 807c464..94a3a35 100644 --- a/source/guides/object-model/computed-properties.md +++ b/source/guides/object-model/computed-properties.md @@ -18,7 +18,7 @@ App.Person = Ember.Object.extend({ }.property('firstName', 'lastName') }); -var ironMan = Person.create({ +var ironMan = App.Person.create({ firstName: "Tony", lastName: "Stark" }); @@ -34,7 +34,8 @@ Whenever you access the `fullName` property, this function gets called, and it r You can use computed properties as values to create new computed properties. Let's add a `description` computed property to the previous example, and use the existing `fullName` property and add in some other properties: ```javascript -Person = Ember.Object.extend({ +App.Person = Ember.Object.extend({ + // these will be supplied by `create` firstName: null, lastName: null, age: null, @@ -49,7 +50,7 @@ Person = Ember.Object.extend({ }.property('fullName', 'age', 'country') }); -var captainAmerica = Person.create({ +var captainAmerica = App.Person.create({ firstName: 'Steve', lastName: 'Rogers', age: 80, @@ -64,7 +65,7 @@ captainAmerica.get('description'); // "Steve Rogers; Age: 80; Country: USA" Computed properties, by default, observe any changes made to the properties they depend on and are dynamically updated when they're called. Let's use computed properties to dynamically update . ```javascript -captainAmerica.set('firstName', 'William') +captainAmerica.set('firstName', 'William'); captainAmerica.get('description'); // "William Rogers; Age: 80; Country: USA" ``` @@ -79,14 +80,13 @@ You can also define what Ember should do when setting a computed property. If yo ```javascript App.Person = Ember.Object.extend({ - // these will be supplied by `create` firstName: null, lastName: null, - fullName: function(key, value, oldValue) { + fullName: function(key, value) { // setter if (arguments.length > 1) { - var nameParts = fullNameString.split(/\s+/); + var nameParts = value.split(/\s+/); this.set('firstName', nameParts[0]); this.set('lastName', nameParts[1]); } @@ -97,10 +97,8 @@ App.Person = Ember.Object.extend({ }); -var captainAmerica = Person.create(); +var captainAmerica = App.Person.create(); captainAmerica.set('fullName', "William Burnside"); -captainAmerica.get('firstName') // William -captainAmerica.get('lastName') // Burnside +captainAmerica.get('firstName'); // William +captainAmerica.get('lastName'); // Burnside ``` - -Ember will call the computed property for both setters and getters, so if you want to call use a computed property as a setter, you'll need to check the number of arguments to determine whether it is being called as a getter or a setter. \ No newline at end of file diff --git a/source/guides/routing/defining-your-routes.md b/source/guides/routing/defining-your-routes.md index 9d83dd5..08c9dbe 100644 --- a/source/guides/routing/defining-your-routes.md +++ b/source/guides/routing/defining-your-routes.md @@ -192,7 +192,7 @@ App.Router.map(function() { ```js App.BlogPostsRoute = Ember.Route.extend({ model: function() { - return App.BlogPost.find(); + return this.get('store').find('blogPost'); } }); ``` @@ -215,7 +215,7 @@ App.Router.map(function() { App.PostRoute = Ember.Route.extend({ model: function(params) { - return App.Post.find(params.post_id); + return this.get('store').find('post', params.post_id); } }); ``` diff --git a/source/guides/routing/index.md b/source/guides/routing/index.md index 0643ca9..3ca176a 100644 --- a/source/guides/routing/index.md +++ b/source/guides/routing/index.md @@ -35,3 +35,17 @@ App = Ember.Application.create({ LOG_TRANSITIONS: true }); ``` + +###Specifying a Root URL + +If your Ember application is one of multiple web applications served from the same domain, it may be necessary to indicate to the router what the root URL for your Ember application is. By default, Ember will assume it is served from the root of your domain. + +If for example, you wanted to serve your blogging application from www.emberjs.com/blog/, it would be necessary to specify a root URL of `/blog/`. + +This can be achieved by setting the rootURL on the router: + +```js +App.Router.reopen({ + rootURL: '/blog/' +}); +``` \ No newline at end of file diff --git a/source/guides/routing/rendering-a-template.md b/source/guides/routing/rendering-a-template.md index 81a6983..6892e5b 100644 --- a/source/guides/routing/rendering-a-template.md +++ b/source/guides/routing/rendering-a-template.md @@ -86,14 +86,3 @@ App.PostRoute = App.Route.extend({ } }); ``` - -## 渲染警告 - - -当渲染一个模板时,如果父级路由并没有渲染模版,那么你会看到一条警告: -“直接父级路由没有渲染进主插座……” - -什么意思呢?当前路由试图渲染进父级路由的模板,但父级路由没有渲染模板,或者假使父级路由渲染了模板,但是父级路由提供的模板却没有渲染进主插座(也就是说,一个默认的`{{outlet}}`插座) - -Ember提供这条警告是希望你将模板渲染到主模板中去。 - diff --git a/source/guides/routing/specifying-a-routes-model.md b/source/guides/routing/specifying-a-routes-model.md index 239db9c..bec9f18 100644 --- a/source/guides/routing/specifying-a-routes-model.md +++ b/source/guides/routing/specifying-a-routes-model.md @@ -2,54 +2,210 @@ ## 指定路由的模型 -在路由器中, 每一个URL都会有一个或多个路由处理器(`route handlers`)与之相关联。路由处理器会负责将URL转换成一个模型对象, -并利用一个控制器来表示这个模型,然后渲染绑定于那个控制器的模板。 +Templates in your application are backed by models. But how do templates +know which model they should display? -### 单一模型 +For example, if you have a `photos` template, how does it know which +model to render? -如果一个路由不包含动态段,你就可以通过执行路由处理器的模型(`model`)钩子函数来写死与这个URL关联的模型(`model`): +This is one of the jobs of an `Ember.Route`. You can tell a template +which model it should render by defining a route with the same name as +the template, and implementing its `model` hook. + +For example, to provide some model data to the `photos` template, we +would define an `App.PhotosRoute` object: ```js -App.Router.map(function() { - this.resource('posts'); +App.PhotosRoute = Ember.Route.extend({ + model: function() { + return [{ + title: "Tomster", + url: "http://emberjs.com/images/about/ember-productivity-sm.png" + }, { + title: "Eiffel Tower", + url: "http://emberjs.com/images/about/ember-structure-sm.png" + }]; + } }); +``` + +JS Bin + +### Asynchronously Loading Models + +In the above example, the model data was returned synchronously from the +`model` hook. This means that the data was available immediately and +your application did not need to wait for it to load, in this case +because we immediately returned an array of hardcoded data. + +Of course, this is not always realistic. Usually, the data will not be +available synchronously, but instead must be loaded asynchronously over +the network. For example, we may want to retrieve the list of photos +from a JSON API available on our server. + +In cases where data is available asynchronously, you can just return a +promise from the `model` hook, and Ember will wait until that promise is +resolved before rendering the template. + +If you're unfamiliar with promises, the basic idea is that they are +objects that represent eventual values. For example, if you use jQuery's +`getJSON()` method, it will return a promise for the JSON that is +eventually returned over the network. Ember uses this promise object to +know when it has enough data to continue rendering. + +For more about promises, see [A Word on +Promises](/guides/routing/asynchronous-routing/#toc_a-word-on-promises) +in the Asynchronous Routing guide. -App.PostsRoute = Ember.Route.extend({ +Let's look at an example in action. Here's a route that loads the most +recent pull requests sent to Ember.js on GitHub: + +```js +App.PullRequestsRoute = Ember.Route.extend({ + model: function() { + return Ember.$.getJSON('https://api.github.com/repos/emberjs/ember.js/pulls'); + } +}); +``` + +While this example looks like it's synchronous, making it easy to read +and reason about, it's actually completely asynchronous. That's because +jQuery's `getJSON()` method returns a promise. Ember will detect the +fact that you've returned a promise from the `model` hook, and wait +until that promise resolves to render the `pullRequest` template. + +(For more information on jQuery's XHR functionality, see +[jQuery.ajax](http://api.jquery.com/jQuery.ajax/) in the jQuery +documentation.) + +Because Ember supports promises, it can work with any persistence +library that uses them as part of its public API. You can also use many +of the conveniences built in to promises to make your code even nicer. + +For example, imagine if we wanted to modify the above example so that +the template only displayed the three most recent pull requests. We can +rely on promise chaining to modify the data returned from the JSON +request before it gets passed to the template: + +```js +App.PullRequestsRoute = Ember.Route.extend({ model: function() { - return App.Post.find(); + var url = 'https://api.github.com/repos/emberjs/ember.js/pulls'; + return Ember.$.getJSON(url).then(function(data) { + return data.splice(0, 3); + }); } }); ``` -默认情况下,从模型(`model`)钩子函数返回的值将会赋值给`posts`控制器的`model`属性。 -你可以通过执行[setupControllers hook][1]钩子函数来改变这种默认的方式。`posts`控制器是`posts`模板的上下文环境。 +### Setting Up Controllers with the Model + +So what actually happens with the value you return from the `model` +hook? + +By default, the value returned from your `model` hook will be assigned +to the `model` property of the associated controller. For example, if your +`App.PostsRoute` returns an object from its `model` hook, that object +will be set as the `model` property of the `App.PostsController`. + +(This, under the hood, is how templates know which model to render: they +look at their associated controller's `model` property. For example, the +`photos` template will render whatever the `App.PhotosController`'s +`model` property is set to.) + +See the [Setting Up a Controller guide][1] to learn how to change this +default behavior. Note that if you override the default behavior and do +not set the `model` property on a controller, your template will not +have any data to render! [1]: /guides/routing/setting-up-a-controller ### 动态模型 -如果一个路由包含动态段,你会想用参数来决定用哪个模型(`model`): +Some routes always display the same model. For example, the `/photos` +route will always display the same list of photos available in the +application. If your user leaves this route and comes back later, the +model does not change. + +However, you will often have a route whose model will change depending +on user interaction. For example, imagine a photo viewer app. The +`/photos` route will render the `photos` template with the list of +photos as the model, which never changes. But when the user clicks on a +particular photo, we want to display that model with the `photo` +template. If the user goes back and clicks on a different photo, we want +to display the `photo` template again, this time with a different model. + +In cases like this, it's important that we include some information in +the URL about not only which template to display, but also which model. + +In Ember, this is accomplished by defining routes with _dynamic segments_. + +A dynamic segment is a part of the URL that is filled in by the current +model's ID. Dynamic segments always start with a colon (`:`). Our photo +example might have its `photo` route defined like this: ```js App.Router.map(function() { - this.resource('post', { path: '/posts/:post_id' }); + this.resource('photo', { path: '/photos/:photo_id' }); }); +``` + +In this example, the `photo` route has a dynamic segment `:photo_id`. +When the user goes to the `photo` route to display a particular photo +model (usually via the `{{link-to}}` helper), that model's ID will be +placed into the URL automatically. + +See [Links](/guides/templates/links) for more information about linking +to a route with a model using the `{{link-to}}` helper. -App.PostRoute = Ember.Route.extend({ +For example, if you transitioned to the `photo` route with a model whose +`id` property was `47`, the URL in the user's browser would be updated +to: + +``` +/photos/47 +``` + +What happens if the user visits your application directly with a URL +that contains a dynamic segment? For example, they might reload the +page, or send the link to a friend, who clicks on it. At that point, +because we are starting the application up from scratch, the actual +JavaScript model object to display has been lost; all we have is the ID +from the URL. + +Luckily, Ember will extract any dynamic segments from the URL for +you and pass them as a hash to the `model` hook as the first argument: + +```js +App.Router.map(function() { + this.resource('photo', { path: '/photos/:photo_id' }); +}); + +App.PhotoRoute = Ember.Route.extend({ model: function(params) { - return App.Post.find(params.post_id); + return Ember.$.getJSON('/photos/'+params.photo_id); } }); ``` + +In the `model` hook for routes with dynamic segments, it's your job to +turn the ID (something like `47` or `post-slug`) into a model that can +be rendered by the route's template. In the above example, we use the +photo's ID (`params.photo_id`) to construct a URL for the JSON +representation of that photo. Once we have the URL, we use jQuery to +return a promise for the JSON model data. -由于这种模式很常用,所以上面的模型(`model`)钩子函数就是默认的行为。 +注意:一个具有动态段的路由只有在通过URL访问的时候,`model`钩子才会被调用。如果路由是从一个跳转进入的(例如:使用Handlebars的[link-to][2]助手时),模型上下文已经准备好了,因此`model`钩子这时不会被执行。没有动态段的路由其`model`钩子每次都会被执行。 -例如,如果动态段是`:post_id`,`ember.js`会智能地使用`App.post`(加上`URL`提供的`ID`)。 -特别地,如果你没有重写了模型(`model`),路由将会自动地返回`App.Post.find(params.post_id)`。 +[2]: /guides/templates/links -这不是巧合,而是`Ember Data`所想要的。所以如果你使用`Ember`路由和`Ember Data`, -你的动态段将会以默认的方式工作。 +### Ember Data -注意:一个具有动态段的路由只有在通过URL访问的时候,`model`钩子才会被调用。如果路由是从一个跳转进入的(例如:使用Handlebars的[link-to][2]助手时),模型上下文已经准备好了,因此`model`钩子这时不会被执行。没有动态段的路由其`model`钩子每次都会被执行。 +Many Ember developers use a model library to make finding and saving +records easier than manually managing Ajax calls. In particular, using a +model library allows you to cache records that have been loaded, +significantly improving the performance of your application. -[2]: /guides/templates/links +One popular model library built for Ember is Ember Data. To learn more +about using Ember Data to manage your models, see the +[Models](/guides/models) guide. \ No newline at end of file diff --git a/source/guides/templates/actions.md b/source/guides/templates/actions.md index e2ff6ef..a99155c 100644 --- a/source/guides/templates/actions.md +++ b/source/guides/templates/actions.md @@ -70,6 +70,8 @@ App.PostRoute = Ember.Route.extend({ ![操作冒泡(Action Bubbling)](/images/template-guide/action-bubbling.png) +这样可以创建一个按钮,且该按钮根据当前应用所在位置有不同的行为。例如,有一个在侧栏中的按钮,当在`/posts`路由和`/about`路由时,分别有不同的行为。 + ### 操作参数 Ember.js支持传递参数给操作处理器。任何在操作名称之后传递给`{{action}}`助手的值,都会作为参数传递给操作处理器。 @@ -141,24 +143,6 @@ App.PostController = Ember.ObjectController.extend({ 指定`bubbles=false`时,Ember.js 就会阻止浏览器将此点击事件传递到父级元素。 -### 向目标冒泡 - -如果在当前控制器没有找到指定的操作,当前路由就会接管来处理。经由路由,再冒泡到父级路由处理,最终到达应用程序路由。 - -在路由的`actions`属性里定义操作。 - -```javascript -App.PostsIndexRoute = Ember.Route.extend({ - actions: { - myCoolAction: function() { - // do your business. - } - } -}); -``` - -上面的代码允许你根据你目前在应用程序中的位置来创建具有不同行为的按钮。比如,如果你在 `/posts`路由中,你想在侧边栏创建一个按钮来完成某种操作,而在`/about`路由中时,此按钮却是做另外一件不同的事。 - ### 指定目标 在默认情况下,`{{action}}`助手将操作发送到视图的目标,通常是视图的控制器。(注意:Ember.Component默认的目标是组件自身。) diff --git a/source/guides/templates/binding-element-attributes.md b/source/guides/templates/binding-element-attributes.md index bd0c1b5..dba0a59 100644 --- a/source/guides/templates/binding-element-attributes.md +++ b/source/guides/templates/binding-element-attributes.md @@ -27,13 +27,13 @@ ``` -如果`isAdministrator`的值是`false`,`Handlebars`将生成如下所示的HTML元素: +如果`isAdministrator`的值是`true`,`Handlebars`将生成如下所示的HTML元素: ```html ``` -否则,如果`isAdministrator`是`true`,生成的HTML元素如下: +否则,如果`isAdministrator`是`false`,生成的HTML元素如下: ```html diff --git a/source/guides/testing/integration.md b/source/guides/testing/integration.md index f97362e..0584623 100644 --- a/source/guides/testing/integration.md +++ b/source/guides/testing/integration.md @@ -43,7 +43,7 @@ module("Integration Tests", { - Fills in the selected input with the given text and returns a promise that fulfills when all resulting async behavior is complete. * `click(selector)` - Clicks an element and triggers any actions triggered by the element's `click` event and returns a promise that fulfills when all resulting async behavior is complete. -* `keyDown(selector, type, keyCode)` +* `keyEvent(selector, type, keyCode)` - Simulates a key event type, e.g. `keypress`, `keydown`, `keyup` with the desired keyCode on element found by the selector. * `wait()` - Returns a promise that fulfills when all async behavior is complete. diff --git a/source/guides/understanding-ember/debugging.md b/source/guides/understanding-ember/debugging.md index b8949f6..5225bfd 100644 --- a/source/guides/understanding-ember/debugging.md +++ b/source/guides/understanding-ember/debugging.md @@ -74,18 +74,6 @@ window.App = Ember.Application.create({ ## Ember Data -#### 获取Ember Data记录的状态历史 - -```javascript -record.stateManager.get('currentPath') -``` - -#### 在日志中输出状态转换 - -```javascript -record.set("stateManager.enableLogging", true) -``` - #### 查看ember-data的标示符映射 ```javascript diff --git a/source/guides/understanding-ember/the-view-layer.md b/source/guides/understanding-ember/the-view-layer.md index cf7b322..9e61f42 100644 --- a/source/guides/understanding-ember/the-view-layer.md +++ b/source/guides/understanding-ember/the-view-layer.md @@ -360,13 +360,6 @@ Ember内置支持下列本地的浏览器事件: clickclick dblclickdoubleClick mousemovemouseMove - - - - - - - diff --git a/source/guides/views/built-in-views.md b/source/guides/views/built-in-views.md index 3c57565..246076b 100644 --- a/source/guides/views/built-in-views.md +++ b/source/guides/views/built-in-views.md @@ -19,7 +19,7 @@ Ember中定义了一套用于构建一些非常基础的控件的视图,比如 ```javascript App.MyText = Ember.TextField.extend({ - formBlurredBinding: 'App.adminController.formBlurred', + formBlurred: null, // passed to the view helper as formBlurred=controllerPropertyName change: function(evt) { this.set('formBlurred', true); } @@ -30,11 +30,11 @@ App.MyText = Ember.TextField.extend({ ```handlebars {{view Ember.Select viewName="select" - contentBinding="App.peopleController" + contentBinding="people" optionLabelPath="model.fullName" optionValuePath="model.id" prompt="Pick a person:" - selectionBinding="App.selectedPersonController.person"}} + selectionBinding="selectedPerson"}} ``` #### Ember.TextArea diff --git a/source/guides/views/inserting-views-in-templates.md b/source/guides/views/inserting-views-in-templates.md index daff532..22b0489 100644 --- a/source/guides/views/inserting-views-in-templates.md +++ b/source/guides/views/inserting-views-in-templates.md @@ -26,15 +26,19 @@ App.InfoView = Ember.View.extend({ }); ``` -```handlebars -User: {{view.firstName}} {{view.lastName}} -{{view App.InfoView}} +```html + ``` -```handlebars -Posts: {{view.posts}} -
-Hobbies: {{view.hobbies}} +```html + ``` 如果我们想要创建一个`App.UserView`的实例并渲染它,我们就会得到如下的`DOM`: diff --git a/source/layouts/guide.erb b/source/layouts/guide.erb index 94874a7..25ad517 100644 --- a/source/layouts/guide.erb +++ b/source/layouts/guide.erb @@ -10,6 +10,7 @@ <% wrap_layout(:layout) do %> <% content_for :head do %> + <% end %> <% content_for(:sidebar) do %> <%= index_for(data.guides) %> diff --git a/source/stylesheets/site.css.scss b/source/stylesheets/site.css.scss index 4ab5397..c934cb2 100644 --- a/source/stylesheets/site.css.scss +++ b/source/stylesheets/site.css.scss @@ -1539,6 +1539,14 @@ a.edit-page { } } + ol#toc-list { + li a { + display: block; + overflow: hidden; + @include ellipsis; + } + } + div.method, div.property, div.event { padding-bottom: 1em; border-bottom: solid 1px #e0e0e0;
事件名方法名
focusinfocusIn
focusoutfocusOut
mouseentermouseEnter