diff --git a/Gemfile b/Gemfile index 6aeb544..8da76eb 100644 --- a/Gemfile +++ b/Gemfile @@ -12,7 +12,7 @@ gem "rack" gem "listen" gem "builder" -group :development do +group :development, :test do gem "pry" end diff --git a/lib/toc.rb b/lib/toc.rb index d382276..81803a2 100644 --- a/lib/toc.rb +++ b/lib/toc.rb @@ -163,11 +163,19 @@ def next_chapter_link(scope = :guides) } elsif whats_next = next_guide next_chapter = whats_next[1][0] - %Q{ - - #{current_section[0]}结束 下一章: #{whats_next[0]} - #{next_chapter.title} \u2192 - - } + if section_slug == 'index.html' + %Q{ + + #{next_chapter.title} \u2192 + + } + else + %Q{ + + 本章#{current_section[0]}完毕。下一章: #{whats_next[0]} - #{next_chapter.title} \u2192 + + } + end else '' end diff --git a/source/bilingual_guides/concepts/naming-conventions.md b/source/bilingual_guides/concepts/naming-conventions.md index a305507..f790b2f 100644 --- a/source/bilingual_guides/concepts/naming-conventions.md +++ b/source/bilingual_guides/concepts/naming-conventions.md @@ -35,9 +35,15 @@ the controller. If your app provides an `App.ApplicationRoute`, Ember.js will invoke [the][1] [router's][2] [hooks][3] first, before rendering the -`application` template. - -如果你的应用提供了`App.ApplicationRoute`,`Ember.js`将在渲染`application`模板前先调用[此][1][路由][2]的[钩子程序][3]。 +`application` template. Hooks are implemented as methods and provide +you access points within an Ember objects lifecycle to intercept and +execute code to modify the default behavior at these points to meet +your needs. Ember provides several hooks for you to utilize for +various purposes (e.g. `model`, `setupController`, etc). In the example below +`App.ApplicationRoute`, which is a `Ember.Route` object, implements +the `setupController` hook. + +如果你的应用提供了`App.ApplicationRoute`,`Ember.js`将在渲染`application`模板前先调用[此][1][路由][2]的[钩子程序][3]。钩子都作为方法来实现,提供了对Ember对象生命周期的访问点,可以拦截和执行代码来改变这些点的缺省行为,以符合实际需求。Ember为各种需求提供了一系列钩子(例如:`model`,`setupController`等等)。在下面的实例中,`App.ApplicationRoute`是一个实现了`setupController`钩子的`Ember.Route`对象。 [1]: /guides/routing/specifying-a-routes-model [2]: /guides/routing/setting-up-a-controller diff --git a/source/bilingual_guides/configuring-ember/disabling-prototype-extensions.md b/source/bilingual_guides/configuring-ember/disabling-prototype-extensions.md index 855d99c..93c5460 100644 --- a/source/bilingual_guides/configuring-ember/disabling-prototype-extensions.md +++ b/source/bilingual_guides/configuring-ember/disabling-prototype-extensions.md @@ -174,7 +174,7 @@ fullNameDidChange: function() { // Instead, do this: //你需要这样做: -fullNameDidChange: Ember.observer(function() { +fullNameDidChange: Ember.observer('fullName', function() { console.log("Full name changed"); -}, 'fullName') +}) ``` diff --git a/source/bilingual_guides/cookbook/user_interface_and_interaction/displaying_formatted_dates_with_moment_js.md b/source/bilingual_guides/cookbook/user_interface_and_interaction/displaying_formatted_dates_with_moment_js.md index 0b8d871..c6a5f81 100644 --- a/source/bilingual_guides/cookbook/user_interface_and_interaction/displaying_formatted_dates_with_moment_js.md +++ b/source/bilingual_guides/cookbook/user_interface_and_interaction/displaying_formatted_dates_with_moment_js.md @@ -25,7 +25,7 @@ We will use [MomentJs](http://momentjs.com) for formatting dates. 这里将使用[Moment.js](http://momentjs.com)来做日期格式化。 Let's look at a simple example. You're working on a website for your -client, and one of the requirements is to have the current date on the index page in human readble format. This is a perfect place to use a +client, and one of the requirements is to have the current date on the index page in human readable format. This is a perfect place to use a Handlebars helper that "pretty prints" the current date: 下面来看一个简单的例子。假设正在给客户开发一个网站,其中的一个需求是在首页显示一个易读的当前时间。这是一个非常适合使用Handlebars助手来实现的应用场景: diff --git a/source/bilingual_guides/cookbook/user_interface_and_interaction/displaying_relative_dates_with_moment_js.md b/source/bilingual_guides/cookbook/user_interface_and_interaction/displaying_relative_dates_with_moment_js.md new file mode 100644 index 0000000..3b65f24 --- /dev/null +++ b/source/bilingual_guides/cookbook/user_interface_and_interaction/displaying_relative_dates_with_moment_js.md @@ -0,0 +1,61 @@ +## Problem + +You want to display dates in relative form. + +## Solution + +[Moment.js](http://momentjs.com) will be used for formatting the dates. + +Start by creating a new view, which would just render the result of +`moment().fromNow()` and register a helper for it. + +```javascript +App.FromNowView = Ember.View.extend({ + tagName: 'time', + template: Ember.Handlebars.compile('{{view.fromNow}}'), + + fromNow: function() { + return moment(this.get('value')).fromNow(); + }.property('value') +}); + +Ember.Handlebars.helper('fromNow', App.FromNowView); +``` + +Relative dates, however, need to be updated periodically, so we'll +trigger a view re-render every second. + +```javascript +App.FromNowView = Ember.View.extend({ + // ... + nextTick: null, + + didInsertElement: function() { + this.tick(); + }, + + tick: function() { + var nextTick = Ember.run.later(this, function() { + this.notifyPropertyChange('value'); + this.tick(); + }, 1000); + this.set('nextTick', nextTick); + }, + + willDestroyElement: function() { + Ember.run.cancel(this.get('nextTick')); + } +}); +``` + +Now the template can be like this: + +```html +created {{fromNow value=createdAt}} +``` + +## Discussion + +#### Example + +JS Bin diff --git a/source/bilingual_guides/cookbook/user_interface_and_interaction/focusing_a_textfield_after_its_been_inserted.md b/source/bilingual_guides/cookbook/user_interface_and_interaction/focusing_a_textfield_after_its_been_inserted.md index 4debfb3..2b6c457 100644 --- a/source/bilingual_guides/cookbook/user_interface_and_interaction/focusing_a_textfield_after_its_been_inserted.md +++ b/source/bilingual_guides/cookbook/user_interface_and_interaction/focusing_a_textfield_after_its_been_inserted.md @@ -24,6 +24,14 @@ App.FocusInputComponent = Ember.TextField.extend({ }); ``` +For the component's template: + +组件模板: + +```handlebars +Focus Input component! +``` + ```handlebars {{focus-input}} ``` diff --git a/source/bilingual_guides/models/finding-records.md b/source/bilingual_guides/models/finding-records.md index 71a19f3..92efa32 100644 --- a/source/bilingual_guides/models/finding-records.md +++ b/source/bilingual_guides/models/finding-records.md @@ -1,62 +1,81 @@ -`store.find()` allows you to find all records, single records, and query for records. -The first argument to `store.find()` is always the type of record in question, e.g. `post`. The second -argument is optional and can either be a plain object of search options or an id. Below are some examples: +The Ember Data store provides a simple interface for finding records of +a single type through the `store` object's `find` method. Internally, the +`store` uses `find`, `findAll`, and `findQuery` based on the supplied +arguments. The first argument to `store.find()` is always the record type. The +optional second argument determines if a request is made for all records, a single +record, or a query. -`store.find()`用于查询所有的记录,包括单一记录,或基于条件查询的记录。方法的第一个参数是需要查询的记录的类型,例如:`post`,第二个参数是可选参数,可以是一个用作查询条件的对象,也可以只是一个记录的ID。下面给出一些具体的例子: +Ember Data仓库提供了一个非常简单的查询一类记录的接口,该接口就是`store`对象的`find`方法。在内部,`store`根据传入的参数使用`find`、`findAll`和`findQuery`完成查询。`store.find()`的第一个参数是记录的类型,第二个可选参数确定查询是获取所有记录,还是一条记录,还是特定的记录。 ### Finding All Records of a Type ### 查询一个类型的所有记录 -```js +```javascript var posts = this.store.find('post'); ``` -This will return an instance of `DS.RecordArray`. As with records, the -record array will start in a loading state with a `length` of `0`. -When the server responds with results, any references to the record array -will update automatically. +To get a list of records already loaded into the store, without making +another network request, use `all` instead. -这里将返回一个`DS.RecordArray`对象。跟记录一样,记录数组对象一开始也是正在加载的状态,其`length`为`0`。当服务器返回结果时,所有引用了记录数组对象的地方都会自动更新。 +如果希望获取已经加载到仓库中的记录的列表,而不希望通过一个网络请求去获取,可以使用`all`方法。 -**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]. +```javascript +var posts = this.store.all('post'); // => no network request +``` + +`find` returns a `DS.PromiseArray` that fulfills to a `DS.RecordArray` +and `all` directly returns a `DS.RecordArray`. -**注意**:`DS.RecordArray`不是一个Javascript的数组,它是一个实现了`Ember.Enumerable`的对象。如果希望通过索引来获取记录,那么需要使用`objectAt(index)`方法来实现。由于记录数组对象不是一个Javascript数组,因此不能是`[]`来获取。更多的信息请参看[Ember.Enumerable][1]和[Ember.Array][2]。 +`find`会返回一个将使用`DS.RecordArray`来履行的`DS.PromiseArray`,而`all`直接返回`DS.RecordArray`。 -To get a list of records already loaded into the store, without firing -another network request, use `store.all('post')` instead. +It's important to note that `DS.RecordArray` is not a JavaScript array. +It is an object that implements [`Ember.Enumerable`][1]. This is +important because, for example, if you want to retrieve records by index, the +`[]` notation will not work--you'll have to use `objectAt(index)` instead. -获取已经加载到仓库中的记录,而不触发一个新的网络请求,可以使用`store.all('post')`方法。 +需要重点注意的一点是`DS.RecordArray`不是一个Javascript数组。它是一个实现了[`Ember.Enumerable`][1]的对象。这一点非常重要,因为例如希望通过索引获取记录,那么`[]`将无法工作,需要使用`objectAt(index)`来获取。 [1]: http://emberjs.com/api/classes/Ember.Enumerable.html -[2]: http://emberjs.com/api/classes/Ember.Array.html ### Finding a Single Record ### 查询一个记录 -You can retrieve a record by passing its model and unique ID to the `find()` -method. The ID can be either a string or a number. This will return a promise that -fulfills with the requested record: +If you provide an number or string as the second argument to +`store.find()`, Ember Data will attempt to retrieve a record of that with that ID. +This will return a promise that fulfills with the requested record: -调用`find()`方法时,指定记录模型的名称和记录唯一的ID就可以获取对应的这个记录。ID可以是字符串或者数字。`find()`方法会返回一个会使用被请求的对象来履行的承诺: +如果调用`store.find()`方法时,第二个参数是一个数字或者字符串,Ember Data将尝试获取对应ID的记录。`find()`方法将返回一个用请求的记录来履行的承诺。 -```js -this.store.find('post', 1).then(function(post) { - post.set('title', "My Dark Twisted Fantasy"); -}); +```javascript +var aSinglePost = this.store.find('post', 1); // => GET /posts/1 +``` + +### Querying For Records + +### 查询记录 + +If you provide a plain object as the second argument to `find`, Ember +Data will make a `GET` request with the object serialized as query params. This +method returns `DS.PromiseArray` in the same way as `find` with no second argument. + +如果传递给`find`方法的第二个参数是一个对象,Ember Data会发送一个使用该对象来序列化出来的查询参数的`GET`请求。这是方法返回与不加第二个参数时候一样的`DS.PromiseArray`。 + +For example, we could search for all `person` models who have the name of +`Peter`: + +例如,可以查询名为`Peter`的`person`模型的所有记录: + +```javascript +var peters = this.store.find('person', { name: "Peter" }); // => GET to /persons?name='Peter' ``` #### Integrating with the Route's Model Hook #### 与路由的模型钩子集成 -As discussed in [Specifying a Route's -Model](/guides/routing/specifying-a-routes-model), routes are +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. 如同在[指定路由的模型](/guides/routing/specifying-a-routes-model)一节中讨论的一样,路由是负责告诉模板将渲染哪个模型。 @@ -70,59 +89,27 @@ 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: +Ember deal with figuring out whether a network request is needed or +not. 这使得使用Ember -Data的异步数据来编写应用变得容易。只需要通过`model`钩子返回请求的记录,交给Ember来处理是否需要一个网络请求: +Data的异步数据来编写应用变得容易。只需要通过`model`钩子返回请求的记录,交给Ember来处理是否需要一个网络请求。 -```js +```javascript App.Router.map(function() { + this.resource('posts'); this.resource('post', { path: ':post_id' }); }); +App.PostsRoute = Ember.Route.extend({ + model: function() { + return this.store.find('post'); + } +}); + App.PostRoute = Ember.Route.extend({ model: function(params) { return this.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. - -由于上述的场景非常常用,上面的`model`钩子是具有动态端的路由的一个缺省实现。如果使用Ember Data,且希望使用一个不是与提供的ID对应的模型时,可以重现`model`钩子来实现。 - -### Querying For Records - -### 查询记录 - -You can query the server by calling the store's `find()` method and -passing a hash of search options. This method returns a promise that -fulfills with an array of the search results. - -通过传递一个哈希值作为`find()`方法的第二个参数,就能实现发起一个到服务器端的查询请求。该方法返回一个将使用返回的查询结果数组来履行的承诺。 - -For example, we could search for all `person` models who have the name of -`Peter`: - -例如,可以查询名为`Peter`的`person`模型的所有记录: - -```js -this.store.find('person', { name: "Peter" }).then(function(people) { - console.log("Found " + people.get('length') + " people named Peter."); -}); -``` - -The hash of search options that you pass to `find()` is opaque to Ember -Data. By default, these options will be sent to your server as the body -of an HTTP `GET` request. - -传递给`find()`方法的查询参数对于Ember -Data是不透明的。默认情况下,这些参数将作为HTTP的`GET`请求的`body`来发送到服务器端。 - -**Using this feature requires that your server knows how to interpret -query responses.** - -**使用查询特性需要服务器端能够正确解析查询参数。** diff --git a/source/bilingual_guides/models/the-fixture-adapter.md b/source/bilingual_guides/models/the-fixture-adapter.md index 1a24cfa..16cf1ee 100644 --- a/source/bilingual_guides/models/the-fixture-adapter.md +++ b/source/bilingual_guides/models/the-fixture-adapter.md @@ -17,11 +17,11 @@ Using the fixture adapter entails three very simple setup steps: Simply attach it to your instance of `Ember.Store`: -``` +```javascript var App = Ember.Application.create(); App.Store = DS.Store.extend({ revision: 13, - adapter: DS.FixtureAdapter.create() + adapter: DS.FixtureAdapter }); ``` @@ -31,7 +31,7 @@ You should refer to [Defining a Model][1] for a more in-depth guide on using Ember Data Models, but for the purposes of demonstration we'll use an example modeling people who document Ember.js. -``` +```javascript App.Documenter = DS.Model.extend({ firstName: DS.attr( 'string' ), lastName: DS.attr( 'string' ) @@ -43,7 +43,7 @@ App.Documenter = DS.Model.extend({ Attaching fixtures couldn't be simpler. Just attach a collection of plain JavaScript objects to your Model's class under the `FIXTURES` property: -``` +```javascript App.Documenter.FIXTURES = [ { id: 1, firstName: 'Trek', lastName: 'Glowacki' }, { id: 2, firstName: 'Tom' , lastName: 'Dale' } @@ -53,8 +53,14 @@ App.Documenter.FIXTURES = [ That's it! You can now use all of methods for [Finding Models][2] in your application. For example: -``` -App.Documenter.find(1); // returns the record representing Trek Glowacki +```javascript +App.DocumenterRoute = Ember.Route.extend({ + model: function() { + var store = this.get('store'); + return store.find('documenter', 1); // returns a promise that will resolve + // with the record representing Trek Glowacki + } +}); ``` #### Naming Conventions diff --git a/source/bilingual_guides/object-model/observers.md b/source/bilingual_guides/object-model/observers.md index 8b2a5de..1488674 100644 --- a/source/bilingual_guides/object-model/observers.md +++ b/source/bilingual_guides/object-model/observers.md @@ -137,9 +137,9 @@ are using Ember without prototype extensions: ```javascript Person.reopen({ - fullNameChanged: Ember.observer(function() { + fullNameChanged: Ember.observer('fullName', function() { // 这是内联式版本的 .addObserver - }, 'fullName') + }) }); ``` diff --git a/source/bilingual_guides/routing/defining-your-routes.md b/source/bilingual_guides/routing/defining-your-routes.md index 0af22e8..79eb95d 100644 --- a/source/bilingual_guides/routing/defining-your-routes.md +++ b/source/bilingual_guides/routing/defining-your-routes.md @@ -23,11 +23,11 @@ template. Visiting `/favs` will render the `favorites` template. 现在当用户访问'/about'时,Ember.js就会渲染`about`的模板。访问'/favs'将渲染`favorites`的模板。 Note that you can leave off the path if it is the same as the route @@ -96,7 +96,7 @@ automatically generate one for you.) (如果你没有显式的声明`IndexController`,`Ember.js`会自动生成一个。) -Ember.js automatically figures out the names of routes and controllers based on +Ember.js automatically figures out the names of the routes and controllers based on the name you pass to `this.route`. `Ember.js`会自动地根据你在`this.route`设置的名字来找出对应的路由与控制器。 @@ -247,9 +247,12 @@ then render the `posts/new` template into its outlet. NOTE: You should use `this.resource` for URLs that represent a **noun**, and `this.route` for URLs that represent **adjectives** or **verbs** -modifying those nouns. +modifying those nouns. For example, in the code sample above, when +specifying URLs for posts (a noun), the route was defined with +`this.resource('posts')`. However, when defining the `new` action +(a verb), the route was defined with `this.route('new')`. -注意:你应该使用this.resource来定义一个URL中的**名词**字段,而对于用来改变名词字段的**形容词**或**动词**字段 ,使用this.route来定义。 +注意:你应该使用this.resource来定义一个URL中的**名词**字段,而对于用来改变名词字段的**形容词**或**动词**字段 ,使用this.route来定义。例如,在上例中的代码,当指定`posts`(名词)的URL时,路由被定义为`this.resource('posts')`。然而,当定义`new`操作(动词)时,那么路由被定义为`this.route('new')`。 ### 动态段(Dynamic Segments) diff --git a/source/bilingual_guides/routing/index.md b/source/bilingual_guides/routing/index.md index ed4a121..8517901 100644 --- a/source/bilingual_guides/routing/index.md +++ b/source/bilingual_guides/routing/index.md @@ -74,12 +74,20 @@ App = Ember.Application.create({ ###Specifying a Root URL +### 指定根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. +如果Ember应用是部署在同一域下的多个Web应用中的一个,这时需要在路由器处指定Ember应用的根URL。在默认情况下,Ember假定部署在域的根路径下。 + 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/`. +例如如果需要将博客应用部署在`www.emberjs.com/blog`下,那么需要指定根URL为`/blog/`。 + This can be achieved by setting the rootURL on the router: +这可以通过在路由器中设置`rootURL`来实现: + ```js App.Router.reopen({ rootURL: '/blog/' diff --git a/source/bilingual_guides/templates/actions.md b/source/bilingual_guides/templates/actions.md index a1103f4..df1f8ca 100644 --- a/source/bilingual_guides/templates/actions.md +++ b/source/bilingual_guides/templates/actions.md @@ -94,6 +94,26 @@ that when executed, `this` is the route, not the `actions` hash. 正如在上例中所示,操作处理器在执行的时候被调用,`this`是路由的实例,而非`actions`这个哈希。 +To continue bubbling the action, you must return true from the handler: + +为了记录将操作冒泡,需要在处理器中返回`true`: + +```js +App.PostRoute = Ember.Route.extend({ + actions: { + expand: function() { + this.controller.set('isExpanded', true); + }, + + contract: function() { + // ... + if (actionShouldAlsoBeTriggeredOnParentRoute) { + return true; + } + } +}); +``` + If neither the template's controller nor its associated route implements a handler, the action will continue to bubble to any parent routes. Ultimately, if an `ApplicationRoute` is defined, it will have an diff --git a/source/builds/index.html.erb b/source/builds/index.html.erb index 657e0c7..15397b4 100644 --- a/source/builds/index.html.erb +++ b/source/builds/index.html.erb @@ -35,21 +35,21 @@ title: Builds
{{#link-to 'release.latest'}} - Release Builds +
{{/link-to}}

Release

Release builds have been hardened through our multi-tiered release process. These builds are perfect for use in production environments.

{{#link-to 'beta.latest'}} - Beta Builds +
{{/link-to}}

Beta

Beta builds will incorporate new features that are ready for more review. They will include new features that have been more thoroughly tested. We do not recommend using Beta builds in production.

{{#link-to 'canary.latest'}} - Canary Builds +
{{/link-to}}

Canary

Canary builds are the bleeding edge of Ember development. Please do not use these builds unless you are absolutely certain that you need fixes and/or features not available in the Release or Beta builds.

@@ -94,6 +94,9 @@ title: Builds production (min) | debug + {{#if lastReleaseChangelogUrl }} | + changelog + {{/if}}
diff --git a/source/guides/concepts/naming-conventions.md b/source/guides/concepts/naming-conventions.md index e561339..90d3768 100644 --- a/source/guides/concepts/naming-conventions.md +++ b/source/guides/concepts/naming-conventions.md @@ -17,7 +17,7 @@ `Ember.js`会将`application`模板作为主模板来渲染。如果存在`App.ApplicationController`, `Ember.js`将使用`App.ApplicationController`的一个实例作为此模板的控制器。这意味着此模板将从这个控制器获得属性。 -如果你的应用提供了`App.ApplicationRoute`,`Ember.js`将在渲染`application`模板前先调用[此][1][路由][2]的[钩子程序][3]。 +如果你的应用提供了`App.ApplicationRoute`,`Ember.js`将在渲染`application`模板前先调用[此][1][路由][2]的[钩子程序][3]。钩子都作为方法来实现,提供了对Ember对象生命周期的访问点,可以拦截和执行代码来改变这些点的缺省行为,以符合实际需求。Ember为各种需求提供了一系列钩子(例如:`model`,`setupController`等等)。在下面的实例中,`App.ApplicationRoute`是一个实现了`setupController`钩子的`Ember.Route`对象。 [1]: /guides/routing/specifying-a-routes-model [2]: /guides/routing/setting-up-a-controller diff --git a/source/guides/configuring-ember/disabling-prototype-extensions.md b/source/guides/configuring-ember/disabling-prototype-extensions.md index a27a377..77a9ed6 100644 --- a/source/guides/configuring-ember/disabling-prototype-extensions.md +++ b/source/guides/configuring-ember/disabling-prototype-extensions.md @@ -107,7 +107,7 @@ fullNameDidChange: function() { // Instead, do this: //你需要这样做: -fullNameDidChange: Ember.observer(function() { +fullNameDidChange: Ember.observer('fullName', function() { console.log("Full name changed"); -}, 'fullName') +}) ``` diff --git a/source/guides/cookbook/user_interface_and_interaction/displaying_relative_dates_with_moment_js.md b/source/guides/cookbook/user_interface_and_interaction/displaying_relative_dates_with_moment_js.md new file mode 100644 index 0000000..3b65f24 --- /dev/null +++ b/source/guides/cookbook/user_interface_and_interaction/displaying_relative_dates_with_moment_js.md @@ -0,0 +1,61 @@ +## Problem + +You want to display dates in relative form. + +## Solution + +[Moment.js](http://momentjs.com) will be used for formatting the dates. + +Start by creating a new view, which would just render the result of +`moment().fromNow()` and register a helper for it. + +```javascript +App.FromNowView = Ember.View.extend({ + tagName: 'time', + template: Ember.Handlebars.compile('{{view.fromNow}}'), + + fromNow: function() { + return moment(this.get('value')).fromNow(); + }.property('value') +}); + +Ember.Handlebars.helper('fromNow', App.FromNowView); +``` + +Relative dates, however, need to be updated periodically, so we'll +trigger a view re-render every second. + +```javascript +App.FromNowView = Ember.View.extend({ + // ... + nextTick: null, + + didInsertElement: function() { + this.tick(); + }, + + tick: function() { + var nextTick = Ember.run.later(this, function() { + this.notifyPropertyChange('value'); + this.tick(); + }, 1000); + this.set('nextTick', nextTick); + }, + + willDestroyElement: function() { + Ember.run.cancel(this.get('nextTick')); + } +}); +``` + +Now the template can be like this: + +```html +created {{fromNow value=createdAt}} +``` + +## Discussion + +#### Example + +JS Bin diff --git a/source/guides/cookbook/user_interface_and_interaction/focusing_a_textfield_after_its_been_inserted.md b/source/guides/cookbook/user_interface_and_interaction/focusing_a_textfield_after_its_been_inserted.md index a60e80c..2859c8b 100644 --- a/source/guides/cookbook/user_interface_and_interaction/focusing_a_textfield_after_its_been_inserted.md +++ b/source/guides/cookbook/user_interface_and_interaction/focusing_a_textfield_after_its_been_inserted.md @@ -16,6 +16,12 @@ App.FocusInputComponent = Ember.TextField.extend({ }); ``` +组件模板: + +```handlebars +Focus Input component! +``` + ```handlebars {{focus-input}} ``` @@ -34,4 +40,4 @@ App.FocusInputComponent = Ember.TextField.extend({ #### 示例 -JS Bin +JS Bin diff --git a/source/guides/models/finding-records.md b/source/guides/models/finding-records.md index 29fd154..1a6f15b 100644 --- a/source/guides/models/finding-records.md +++ b/source/guides/models/finding-records.md @@ -1,30 +1,41 @@ 英文原文:[http://emberjs.com/guides/models/finding-records/](http://emberjs.com/guides/models/finding-records/) -`store.find()`用于查询所有的记录,包括单一记录,或基于条件查询的记录。方法的第一个参数是需要查询的记录的类型,例如:`post`,第二个参数是可选参数,可以是一个用作查询条件的对象,也可以只是一个记录的ID。下面给出一些具体的例子: +Ember Data仓库提供了一个非常简单的查询一类记录的接口,该接口就是`store`对象的`find`方法。在内部,`store`根据传入的参数使用`find`、`findAll`和`findQuery`完成查询。`store.find()`的第一个参数是记录的类型,第二个可选参数确定查询是获取所有记录,还是一条记录,还是特定的记录。 ### 查询一个类型的所有记录 -```js +```javascript var posts = this.store.find('post'); ``` -这里将返回一个`DS.RecordArray`对象。跟记录一样,记录数组对象一开始也是正在加载的状态,其`length`为`0`。当服务器返回结果时,所有引用了记录数组对象的地方都会自动更新。 +如果希望获取已经加载到仓库中的记录的列表,而不希望通过一个网络请求去获取,可以使用`all`方法。 -**注意**:`DS.RecordArray`不是一个Javascript的数组,它是一个实现了`Ember.Enumerable`的对象。如果希望通过索引来获取记录,那么需要使用`objectAt(index)`方法来实现。由于记录数组对象不是一个Javascript数组,因此不能是`[]`来获取。更多的信息请参看[Ember.Enumerable][1]和[Ember.Array][2]。 +```javascript +var posts = this.store.all('post'); // => no network request +``` + +`find`会返回一个将使用`DS.RecordArray`来履行的`DS.PromiseArray`,而`all`直接返回`DS.RecordArray`。 -获取已经加载到仓库中的记录,而不触发一个新的网络请求,可以使用`store.all('post')`方法。 +需要重点注意的一点是`DS.RecordArray`不是一个Javascript数组。它是一个实现了[`Ember.Enumerable`][1]的对象。这一点非常重要,因为例如希望通过索引获取记录,那么`[]`将无法工作,需要使用`objectAt(index)`来获取。 [1]: http://emberjs.com/api/classes/Ember.Enumerable.html -[2]: http://emberjs.com/api/classes/Ember.Array.html ### 查询一个记录 -调用`find()`方法时,指定记录模型的名称和记录唯一的ID就可以获取对应的这个记录。ID可以是字符串或者数字。`find()`方法会返回一个会使用被请求的对象来履行的承诺: +如果调用`store.find()`方法时,第二个参数是一个数字或者字符串,Ember Data将尝试获取对应ID的记录。`find()`方法将返回一个用请求的记录来履行的承诺。 -```js -this.store.find('post', 1).then(function(post) { - post.set('title', "My Dark Twisted Fantasy"); -}); +```javascript +var aSinglePost = this.store.find('post', 1); // => GET /posts/1 +``` + +### 查询记录 + +如果传递给`find`方法的第二个参数是一个对象,Ember Data会发送一个使用该对象来序列化出来的查询参数的`GET`请求。这是方法返回与不加第二个参数时候一样的`DS.PromiseArray`。 + +例如,可以查询名为`Peter`的`person`模型的所有记录: + +```javascript +var peters = this.store.find('person', { name: "Peter" }); // => GET to /persons?name='Peter' ``` #### 与路由的模型钩子集成 @@ -33,36 +44,23 @@ this.store.find('post', 1).then(function(post) { `Ember.Route`的`model`钩子支持立即可用的异步值。如果`model`钩子返回一个承诺,路由将等待承诺履行条件满足时才渲染模板。 -这使得使用Ember -Data的异步数据来编写应用变得容易。只需要通过`model`钩子返回请求的记录,交个Ember来处理是否需要一个网络请求: +这使得使用Ember Data的异步数据来编写应用变得容易。只需要通过`model`钩子返回请求的记录,交个Ember来处理是否需要一个网络请求。 -```js +```javascript App.Router.map(function() { + this.resource('posts'); this.resource('post', { path: ':post_id' }); }); +App.PostsRoute = Ember.Route.extend({ + model: function() { + return this.store.find('post'); + } +}); + App.PostRoute = Ember.Route.extend({ model: function(params) { return this.store.find('post', params.post_id); } }); ``` - -由于上述的场景非常常用,上面的`model`钩子是具有动态端的路由的一个缺省实现。如果使用Ember Data,且希望使用一个不是与提供的ID对应的模型时,可以重现`model`钩子来实现。 - -### 查询记录 - -通过传递一个哈希值作为`find()`方法的第二个参数,就能实现发起一个到服务器端的查询请求。该方法返回一个将使用返回的查询结果数组来履行的承诺。 - -例如,可以查询名为`Peter`的`person`模型的所有记录: - -```js -this.store.find('person', { name: "Peter" }).then(function(people) { - console.log("Found " + people.get('length') + " people named Peter."); -}); -``` - -传递给`find()`方法的查询参数对于Ember -Data是不透明的。默认情况下,这些参数将作为HTTP的`GET`请求的`body`来发送到服务器端。 - -**使用查询特性需要服务器端能够正确解析查询参数。** diff --git a/source/guides/models/the-fixture-adapter.md b/source/guides/models/the-fixture-adapter.md index 1a24cfa..16cf1ee 100644 --- a/source/guides/models/the-fixture-adapter.md +++ b/source/guides/models/the-fixture-adapter.md @@ -17,11 +17,11 @@ Using the fixture adapter entails three very simple setup steps: Simply attach it to your instance of `Ember.Store`: -``` +```javascript var App = Ember.Application.create(); App.Store = DS.Store.extend({ revision: 13, - adapter: DS.FixtureAdapter.create() + adapter: DS.FixtureAdapter }); ``` @@ -31,7 +31,7 @@ You should refer to [Defining a Model][1] for a more in-depth guide on using Ember Data Models, but for the purposes of demonstration we'll use an example modeling people who document Ember.js. -``` +```javascript App.Documenter = DS.Model.extend({ firstName: DS.attr( 'string' ), lastName: DS.attr( 'string' ) @@ -43,7 +43,7 @@ App.Documenter = DS.Model.extend({ Attaching fixtures couldn't be simpler. Just attach a collection of plain JavaScript objects to your Model's class under the `FIXTURES` property: -``` +```javascript App.Documenter.FIXTURES = [ { id: 1, firstName: 'Trek', lastName: 'Glowacki' }, { id: 2, firstName: 'Tom' , lastName: 'Dale' } @@ -53,8 +53,14 @@ App.Documenter.FIXTURES = [ That's it! You can now use all of methods for [Finding Models][2] in your application. For example: -``` -App.Documenter.find(1); // returns the record representing Trek Glowacki +```javascript +App.DocumenterRoute = Ember.Route.extend({ + model: function() { + var store = this.get('store'); + return store.find('documenter', 1); // returns a promise that will resolve + // with the record representing Trek Glowacki + } +}); ``` #### Naming Conventions diff --git a/source/guides/object-model/observers.md b/source/guides/object-model/observers.md index 1f14ca7..8189932 100644 --- a/source/guides/object-model/observers.md +++ b/source/guides/object-model/observers.md @@ -127,9 +127,9 @@ just get it in your init method. ```javascript Person.reopen({ - fullNameChanged: Ember.observer(function() { + fullNameChanged: Ember.observer('fullName', function() { // 这是内联式版本的 .addObserver - }, 'fullName') + }) }); ``` diff --git a/source/guides/routing/defining-your-routes.md b/source/guides/routing/defining-your-routes.md index a3e963e..1975045 100644 --- a/source/guides/routing/defining-your-routes.md +++ b/source/guides/routing/defining-your-routes.md @@ -15,7 +15,7 @@ App.Router.map(function() { 现在当用户访问'/about'时,Ember.js就会渲染`about`的模板。访问'/favs'将渲染`favorites`的模板。 提示:如果路径(path)的名字跟路由(route)的名字是一样的话,你可以不用写上路径。 @@ -180,7 +180,7 @@ App.Router.map(function() { 最后,访问`/posts/new`会先渲染`posts`模板,然后渲染`posts/new`模板到它的出口上。 -注意:你应该使用this.resource来定义一个URL中的**名词**字段,而对于用来改变名词字段的**形容词**或**动词**字段 ,使用this.route来定义。 +注意:你应该使用this.resource来定义一个URL中的**名词**字段,而对于用来改变名词字段的**形容词**或**动词**字段 ,使用this.route来定义。例如,在上例中的代码,当指定`posts`(名词)的URL时,路由被定义为`this.resource('posts')`。然而,当定义`new`操作(动词)时,那么路由被定义为`this.route('new')`。 ### 动态段 diff --git a/source/guides/routing/index.md b/source/guides/routing/index.md index 3ca176a..bf244a1 100644 --- a/source/guides/routing/index.md +++ b/source/guides/routing/index.md @@ -36,16 +36,16 @@ App = Ember.Application.create({ }); ``` -###Specifying a Root URL +### 指定根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. +如果Ember应用是部署在同一域下的多个Web应用中的一个,这时需要在路由器处指定Ember应用的根URL。在默认情况下,Ember假定部署在域的根路径下。 -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/`. +例如如果需要将博客应用部署在`www.emberjs.com/blog`下,那么需要指定根URL为`/blog/`。 -This can be achieved by setting the rootURL on the router: +这可以通过在路由器中设置`rootURL`来实现: ```js App.Router.reopen({ rootURL: '/blog/' }); -``` \ No newline at end of file +``` diff --git a/source/guides/templates/actions.md b/source/guides/templates/actions.md index 2080768..5944e1a 100644 --- a/source/guides/templates/actions.md +++ b/source/guides/templates/actions.md @@ -62,6 +62,24 @@ App.PostRoute = Ember.Route.extend({ 正如在上例中所示,操作处理器在执行的时候被调用,`this`是路由的实例,而非`actions`这个哈希。 +为了记录将操作冒泡,需要在处理器中返回`true`: + +```js +App.PostRoute = Ember.Route.extend({ + actions: { + expand: function() { + this.controller.set('isExpanded', true); + }, + + contract: function() { + // ... + if (actionShouldAlsoBeTriggeredOnParentRoute) { + return true; + } + } +}); +``` + 如果模板对应的控制器和关联的路由都没有实现操作处理器,这个操作将被冒泡到其父级的路由。如果应用定义了`ApplicationRoute`,这里是能处理该操作的最后地方。 当一个操作被触发时,如果在控制器中没有实现对应的操作处理器,当前路由或当前路由的任意一个父节点也没有实现时,将抛出一个错误。 diff --git a/source/images/beta.png b/source/images/beta.png new file mode 100644 index 0000000..2f215ac Binary files /dev/null and b/source/images/beta.png differ diff --git a/source/images/beta_x2.png b/source/images/beta_x2.png new file mode 100644 index 0000000..d0a1f41 Binary files /dev/null and b/source/images/beta_x2.png differ diff --git a/source/images/canary.png b/source/images/canary.png new file mode 100644 index 0000000..11f0cc3 Binary files /dev/null and b/source/images/canary.png differ diff --git a/source/images/canary_x2.png b/source/images/canary_x2.png new file mode 100644 index 0000000..906dac9 Binary files /dev/null and b/source/images/canary_x2.png differ diff --git a/source/images/release.png b/source/images/release.png new file mode 100644 index 0000000..1186501 Binary files /dev/null and b/source/images/release.png differ diff --git a/source/images/release_x2.png b/source/images/release_x2.png new file mode 100644 index 0000000..65ab4b3 Binary files /dev/null and b/source/images/release_x2.png differ diff --git a/source/javascripts/app/builds/app.js b/source/javascripts/app/builds/app.js index ae386bd..d8a9469 100644 --- a/source/javascripts/app/builds/app.js +++ b/source/javascripts/app/builds/app.js @@ -188,7 +188,8 @@ App.Project.reopenClass({ lastRelease: "1.2.0", futureVersion: "1.2.1", channel: "release", - date: "2013-11-23" + date: "2013-11-23", + changelogPath: "CHANGELOG" }, { projectName: "Ember", projectFilter: "ember", @@ -196,7 +197,8 @@ App.Project.reopenClass({ lastRelease: "1.3.0-beta.1", futureVersion: "1.3.0-beta.2", channel: "beta", - date: "2013-11-25" + date: "2013-11-25", + changelogPath: "CHANGELOG.md" }, { projectName: "Ember Data", projectFilter: "ember-data", @@ -204,7 +206,8 @@ App.Project.reopenClass({ lastRelease: "1.0.0-beta.3", futureVersion: "1.0.0-beta.4", channel: "beta", - date: "2013-09-28" + date: "2013-09-28", + changelogPath: "CHANGELOG" }, { projectName: "Ember", projectFilter: "ember", @@ -315,7 +318,7 @@ App.ProjectsMixin = Ember.Mixin.create({ if (project.channel === 'canary') project.lastRelease = 'latest'; else if (project.changelog !== 'false') - project.lastReleaseChangelogUrl = 'https://github.com/' + project.projectRepo + '/blob/v' + project.lastRelease + '/CHANGELOG.md'; + project.lastReleaseChangelogUrl = 'https://github.com/' + project.projectRepo + '/blob/v' + project.lastRelease + '/' + project.changelogPath; }); return projects; diff --git a/source/stylesheets/about.css.scss b/source/stylesheets/about.css.scss index 5467c67..1dce007 100644 --- a/source/stylesheets/about.css.scss +++ b/source/stylesheets/about.css.scss @@ -1,5 +1,7 @@ @import "mixins/hidpi"; +$hidpi-min-pixel-ratio: 1.3 !default; + body.index, body.about { background: no-repeat top center #faf4f1; background-image: url('/images/background-shades.png'); @@ -9,25 +11,34 @@ body.index, body.about { margin-top: 1.5em; } + @media (-webkit-min-device-pixel-ratio: $hidpi-min-pixel-ratio), (min-resolution: $hidpi-min-pixel-ratio), (min-resolution: $hidpi-min-pixel-ratio) { + .feature .handlebars { + background-size: 221px 210px !important; + } + .feature .structure { + background-size: 221px 210px !important; + } + .feature .productivity { + background-size: 221px 220px !important; + } + } + .feature .handlebars { @include hidpi('about/ember-handlebars-sm', 'png'); - background-size: 221px 210px !important; width: 221px; - height: 210px; + height: 177px; } .feature .structure { @include hidpi('about/ember-structure-sm', 'png'); - background-size: 221px 210px !important; - width: 221px; - height: 210px; + width: 193px; + height: 184px; } .feature .productivity { @include hidpi('about/ember-productivity-sm', 'png'); - background-size: 221px 220px !important; width: 221px; - height: 210px; + height: 184px; } .getting-started { diff --git a/source/stylesheets/builds.css.scss b/source/stylesheets/builds.css.scss index e4fbec6..047e3d3 100644 --- a/source/stylesheets/builds.css.scss +++ b/source/stylesheets/builds.css.scss @@ -1,4 +1,5 @@ @import "compass"; +@import "mixins/hidpi"; #builds-application { #sidebar { @@ -11,6 +12,39 @@ } } + @media (-webkit-min-device-pixel-ratio: $hidpi-min-pixel-ratio), (min-resolution: $hidpi-min-pixel-ratio), (min-resolution: $hidpi-min-pixel-ratio) { + .feature .release { + background-size: 198px 263px !important; + } + .feature .beta { + background-size: 198px 263px !important; + } + .feature .canary { + background-size: 198px 263px !important; + } + } + + .feature .release { + @include hidpi('builds/release', 'png'); + width: 198px; + height: 263px; + margin: 0 auto; + } + + .feature .beta { + @include hidpi('builds/beta', 'png'); + width: 198px; + height: 263px; + margin: 0 auto; + } + + .feature .canary { + @include hidpi('builds/canary', 'png'); + width: 198px; + height: 263px; + margin: 0 auto; + } + .features { margin-top: 15%; } diff --git a/spec/toc_spec.rb b/spec/toc_spec.rb index eca6995..739ee7c 100644 --- a/spec/toc_spec.rb +++ b/spec/toc_spec.rb @@ -2,6 +2,7 @@ require 'capybara/rspec' require 'capybara/poltergeist' +require 'pry' unless `which phantomjs`.empty? Capybara.register_driver :poltergeist do |app| @@ -24,8 +25,8 @@ end it "Within-guide page link" do - visit "/guides/models/finding-models" - find('a.next-guide').text.should =~ /Modifying Attributes/ + visit "/guides/models/finding-records" + find('a.next-guide').text.should =~ /Working with Records/ end it "Another within-guide page link " do @@ -36,8 +37,9 @@ it "Shows next section link" do visit "/guides/views/manually-managing-view-hierarchy" find('a.next-guide').text.should =~ /We're done with Views/ - find('a.next-guide').text.should =~ /The Object Model/ - find('a.next-guide').text.should =~ /Classes and Instances/ + find('a.next-guide').text.should =~ /Next up: / + find('a.next-guide').text.should =~ /Enumerables/ + find('a.next-guide').text.should =~ /Introduction/ end it "Shows previous section link" do @@ -46,26 +48,17 @@ end it "Chills on the first page" do - # Note: "/guides/concepts/core-concepts" isn't the first page visit "/guides/index.html" page.should_not have_css('a.previous-guide') end it "First page should have a link to the first guide" do visit "/guides/index.html" - find('a.next-guide').text.should =~ /Core Concepts/ + find('a.next-guide').text.should =~ /Getting Started/ end it "Chills on the last page" do - visit "/guides/understanding-ember/keeping-templates-up-to-date" + visit "/guides/contributing/adding-new-features" page.should_not have_css('a.next-guide') end - - it "should have an ember-data warning on model pages but not on other pages" do - visit "/guides/models" - find('.under_construction_warning').text.should =~ - /WARNING: EMBER-DATA IS A WORK IN PROGRESS AND UNDER RAPID DEVELOPMENT. USE WITH CAUTION!!!/ - visit "/guides/views" - page.should_not have_css('.under_construction_warning') - end end