Ember 翻译——教程九:创建一个复杂的组件

来源:互联网 发布:linux maven 打包war 编辑:程序博客网 时间:2024/06/06 20:26

当用户搜索租赁信息时,他们或许也想要缩小他们的搜索范围到某一特定城市。让我们创建一个组件来让他们依照城市过滤租赁信息。

一开始,让我们生成新的组件。我们将其命名为 list-filter,因为我们想要我们的组件对租赁信息基于用户输入进行过滤。

ember g component list-filter

和之前一样,这会创建一个 Handlebars 模板(app/templates/components/list-filter.hbs),一个 JavaScript 文件(app/components/list-filter.js),和一个组件继承测试(tests/integration/components/list-filter.js)。

让我们从写一些测试开始,以便于帮助我们思考我们将做什么。过滤组件将生成一系列经过过滤的信息,不管它里面呈现的是什么,成为其内部模板块。我们想要我们的组件调用两种行为:一种是在没提供过滤信息时提供所有的信息,而另一种是依照城市搜索列表。

对于我们的初次测试,我们将检查是否我们提供的所有城市都被呈现了出来以及是否可以从模板中访问列表对象。

我们的依照城市调用过滤的行为将异步完成,而我们的测试也将必须适应这一点。我们将利用这里的 行为 来处理 filterByCity 中异步事件的完成,它是从我们的存根事件中返回一个 promise 实现的。

注意在在测试的末尾,我们也需要添加一个 wait 调用来确保测试结果。Ember 的 wait helper 会在运行给定的回调函数和结束测试之前,先等待所有其它的 promise 切换到 resovle 状态。

/tests/integration/components/list-filter-test.js

import { moduleForComponent, test } from 'ember-qunit';import hbs from 'htmlbars-inline-precompile';import wait from 'ember-test-helpers/wait';import RSVP from 'rsvp';moduleForComponent('list-filter', 'Integration | Component | filter listing', {  integration: true});const ITEMS = [{city: 'San Francisco'}, {city: 'Portland'}, {city: 'Seattle'}];const FILTERED_ITEMS = [{city: 'San Francisco'}];test('should initially load all listings', function (assert) {  // we want our actions to return promises, since they are potentially fetching data asynchronously  this.on('filterByCity', (val) => {    if (val === '') {      return RSVP.resolve(ITEMS);    } else {      return RSVP.resolve(FILTERED_ITEMS);    }  });  // with an integration test, you can set up and use your component in the same way your application  // will use it.  this.render(hbs`    {{#list-filter filter=(action 'filterByCity') as |results|}}      <ul>      {{#each results as |item|}}        <li class="city">          {{item.city}}        </li>      {{/each}}      </ul>    {{/list-filter}}  `);  // the wait function will return a promise that will wait for all promises  // and xhr requests to resolve before running the contents of the then block.  return wait().then(() => {    assert.equal(this.$('.city').length, 3);    assert.equal(this.$('.city').first().text().trim(), 'San Francisco');  });});

对于我们的第二个测试,我们将检查在过滤器中输入文字是否真的会调用适当的过滤行为和更新显示的列表。

我们通过在我们的输入框生成一个 keyUp 事件来触发该行为,然后检查是否只呈现了一条信息。

/tests/integration/components/list-filter-test.js

test('should update with matching listings', function (assert) {  this.on('filterByCity', (val) => {    if (val === '') {      return RSVP.resolve(ITEMS);    } else {      return RSVP.resolve(FILTERED_ITEMS);    }  });  this.render(hbs`    {{#list-filter filter=(action 'filterByCity') as |results|}}      <ul>      {{#each results as |item|}}        <li class="city">          {{item.city}}        </li>      {{/each}}      </ul>    {{/list-filter}}  `);  // The keyup event here should invoke an action that will cause the list to be filtered  this.$('.list-filter input').val('San').keyup();  return wait().then(() => {    assert.equal(this.$('.city').length, 1);    assert.equal(this.$('.city').text().trim(), 'San Francisco');  });});

下一步,在我们的 app/templates/rentals.hbs 文件中,我们将通过类似的方式添加测试中使用的新的 list-filter 组件。我们将使用我们的 rental-listing 组件来展示详细的租赁信息,而非只显示城市。

/app/templates/rentals.hbs

<div class="jumbo">  <div class="right tomster"></div>  <h2>Welcome!</h2>  <p>    We hope you find exactly what you're looking for in a place to stay.  </p>  {{#link-to 'about' class="button"}}    About Us  {{/link-to}}</div>{{#list-filter   filter=(action 'filterByCity')   as |rentals|}}  <ul class="results">    {{#each rentals as |rentalUnit|}}      <li>{{rental-listing rental=rentalUnit}}</li>    {{/each}}  </ul>{{/list-filter}}

既然我们有了失败的测试和关于我们组件的想法,我们这就来创建我们的组件。我盟想要只是简单地提供一个输入字段和一个结果将展示到模板块中的字段,所以我们的模板将会很简单:

/app/remplates/components/list-filter.hbs

{{input value=value key-up=(action 'handleFilterEntry') class="light" placeholder="Filter By City"}}{{yield results}}

这个模板包含了一个 {{input}} helper,它将渲染为一个输入框,用户可以输入一个指令来过滤搜索中使用的城市列表。inputvalue 属性将会绑定到组件的 value 属性上。key-up 属性将会被绑定到 handleFilterEntry 行为上。

组件的 JavaScript 代码如下:

/app/components/list-filter.js

import Ember from 'ember';export default Ember.Component.extend({  classNames: ['list-filter'],  value: '',  init() {    this._super(...arguments);    this.get('filter')('').then((results) => this.set('results', results));  },  actions: {    handleFilterEntry() {      let filterInputValue = this.get('value');      let filterAction = this.get('filter');      filterAction(filterInputValue).then((filterResults) => this.set('results', filterResults));    }  }});

我们通过用空值调用 filter行为来使用 init 钩子,从而设置我们的初始化列表。我们的 handleFilterEntry 行为将基于我们 input helper 设置的 value 属性来调用过滤行为。

filter 行为是被调用对象传入其中的,我们将创建一个 rentals 控制器。控制器能够包含一些行为和属性,这些行为和属性可以被控制器相关的路由的模板所读取。

通过运行下面的代码为 rentals 路由生成一个控制器:

ember g controller rentals

现在,请像这样定义你的新控制器:

/app/controllers/rentals.js

import Ember from 'ember';export default Ember.Controller.extend({  actions: {    filterByCity(param) {      if (param !== '') {        return this.get('store').query('rental', { city: param });      } else {        return this.get('store').findAll('rental');      }    }  }});

当用户在组件的输入框内输入内容时,控制器中的 filterByCity 行为就会被调用。这个行为将取得 value 属性,然后过滤出 data store 中匹配用户输入信息的租赁信息。查询的结果将会被返回到调用程序中。

为了让这个行为工作,我们需要用下面的代码替换我们的 Mirage 的 config.js 文件,以便于它能响应我们的查询。

mirage/config.js

export default function() {  this.namespace = '/api';  let rentals = [{      type: 'rentals',      id: 'grand-old-mansion',      attributes: {        title: 'Grand Old Mansion',        owner: 'Veruca Salt',        city: 'San Francisco',        type: 'Estate',        bedrooms: 15,        image: 'https://upload.wikimedia.org/wikipedia/commons/c/cb/Crane_estate_(5).jpg',        description: "This grand old mansion sits on over 100 acres of rolling hills and dense redwood forests."      }    }, {      type: 'rentals',      id: 'urban-living',      attributes: {        title: 'Urban Living',        owner: 'Mike Teavee',        city: 'Seattle',        type: 'Condo',        bedrooms: 1,        image: 'https://upload.wikimedia.org/wikipedia/commons/0/0e/Alfonso_13_Highrise_Tegucigalpa.jpg',        description: "A commuters dream. This rental is within walking distance of 2 bus stops and the Metro."      }    }, {      type: 'rentals',      id: 'downtown-charm',      attributes: {        title: 'Downtown Charm',        owner: 'Violet Beauregarde',        city: 'Portland',        type: 'Apartment',        bedrooms: 3,        image: 'https://upload.wikimedia.org/wikipedia/commons/f/f7/Wheeldon_Apartment_Building_-_Portland_Oregon.jpg',        description: "Convenience is at your doorstep with this charming downtown rental. Great restaurants and active night life are within a few feet."      }    }];  this.get('/rentals', function(db, request) {    if(request.queryParams.city !== undefined) {      let filteredRentals = rentals.filter(function(i) {        return i.attributes.city.toLowerCase().indexOf(request.queryParams.city.toLowerCase()) !== -1;      });      return { data: filteredRentals };    } else {      return { data: rentals };    }  });}

在更新完我们的 mirage 配置之后,我们应该看到通过的测试以及在首页上出现一个简单的过滤器,它将随你的输入更新租赁信息列表:
image

image


原文地址

0 0