Ember 翻译——教程十:服务和功能

服务和功能

对于“超级租赁”,我们想要一个能够展示租赁信息所在的网站地图。为了实现这个功能,我们将利用如下几个 Ember 概念:
1. 一个组件,用来在每个租赁列表展示一个地图。
2. 一个服务,用来缓存已经渲染的地图使得它能够在应用的不同地方能够使用。
3. 一个功能性函数,用来使用 Google 的地图 API 来创建一个地图。

首先,我们会显示一个地图,然后转回使用 Google 地图 API。

使用组件显示地图

一开始,我们将添加一个组件,用来在地图上显示租赁信息所在城市。

app/templates/components/rental-listing.hbs

<article class="listing">
  <a {{action 'toggleImageSize'}} class="image {{if isWide "wide"}}">
    <img src="{{rental.image}}" alt="">
    <small>View Largersmall>
  a>
  <h3>{{rental.title}}h3>
  <div class="detail owner">
    <span>Owner:span> {{rental.owner}}
  div>
  <div class="detail type">
    <span>Type:span> {{rental-property-type rental.type}} - {{rental.type}}
  div>
  <div class="detail location">
    <span>Location:span> {{rental.city}}
  div>
  <div class="detail bedrooms">
    <span>Number of bedrooms:span> {{rental.bedrooms}}
  div>
  {{location-map location=rental.city}}
article>

紧接着,我们使用 Ember CLI 生成一个地图组件。

ember g component location-map

运行这条命令将生成如下三个文件:一个组件的 JavaScript 文件,一个模板,一个测试文件。我们将首先完成测试,用以帮助我们构思我们的组件的功能。

在这种情况下,我们打算使用 Google 地图服务处理地图展示问题。我们组件的任务将是从地图服务(一个地图元素)中获取结果,然后将其附到组件模板的一个元素中。

为了限制测试文件来验证这个行为,我们将利用注册 API 来提供一个存根地图服务。一个存根将替代应用中的真实对象,同时将模拟它的行为。请在这个存根服务中,定义一个名叫 getMapElement 的方法,它将基于位置来获取相应地图。

tests/integration/components/location-map-test.js

import { moduleForComponent, test } from 'ember-qunit';
import hbs from 'htmlbars-inline-precompile';
import Ember from 'ember';

let StubMapsService = Ember.Service.extend({
  getMapElement(location) {
    this.set('calledWithLocation', location);
    // We create a div here to simulate our maps service,
    // which will create and then cache the map element
    return document.createElement('div');
  }
});

moduleForComponent('location-map', 'Integration | Component | location map', {
  integration: true,
  beforeEach() {
    this.register('service:maps', StubMapsService);
    this.inject.service('maps', { as: 'mapsService' });
  }
});

test('should append map element to container element', function(assert) {
  this.set('myLocation', 'New York');
  this.render(hbs`{{location-map location=myLocation}}`);
  assert.equal(this.$('.map-container').children().length, 1, 'the map element should be put onscreen');
  assert.equal(this.get('mapsService.calledWithLocation'), 'New York', 'a map of New York should be requested');
});

执行于每个测试之前的 beforeEach 函数中,我们使用隐式函数 this.register 来注册我们的存根服务来代替地图服务。注册将生成一个可应用到 Ember 应用中的对象,它将包含一些列内容,比如在这里可以从模板中加载组件以及注入服务等。

调用 this.inject.service 将会把我们刚注册的服务注入测试的环境中,因此每个测试都可以通过 this.get('mapService') 访问它。例子中,我们预计存根中的 calledWithLocation 将会被设置到我们传递到组件中的位置上。

为了使这个测试顺利通过,请将以下容器元素添加到组件模板中。

app/templates/components/location-map.hbs

<div class="map-container">div>

随后更新组件,将地图输出内容附加到它内部容器元素中。我们将添加一个地图服务注入,然后在提供的位置上调用 getMapElement 函数。

我们随后将我们通过安装 didInsertElemebt (一个组件生命周期钩子函数) 来将从服务中获取到的地图元素添加(到页面中)。在页面渲染时组件的标记插入到 DOM 之中后,这个函数将被执行。

app/components/location-map.js

import Ember from 'ember';

export default Ember.Component.extend({
  maps: Ember.inject.service(),

  didInsertElement() {
    this._super(...arguments);
    let location = this.get('location');
    let mapElement = this.get('maps').getMapElement(location);
    this.$('.map-container').append(mapElement);
  }
});

通过一个服务获取地图

此时,我们的组件集成测试将能够通过测试了,但是我们的验收测试现在应该仍旧会因为不能找到我们的地图服务而失败。不仅我们的验收测试失败了,在我们浏览我们网页的时候,同样也没有地图显示出来。

通过服务来获取我们的地图 API 将使我们得到许多好处:

  • 它将被注入一个服务定位器,这意味着它将从使用它的代码中将地图 API 抽象出来,以便于重构和维护。

  • 它是懒加载的,这意味着它将直到它第一次被调用的时候才开始初始化。同等情况下,这将能减少你应用的加载器加载内容以及内存消耗。

  • 它是一个单例模型,这将让我们能够缓存地图数据。

  • 它遵循一个生命周期,这意味着我们能在服务终止的时候,拥有对应的钩子函数来清楚代码,防止类似内存泄漏和不必要的处理等情况发生。

让我们开始通过 Ember CLI 创建我们的服务,这将为其创建服务文件以及测试文件。

ember g service maps

这个服务将基于位置缓存地图元素。如果地图元素存在于缓存中,服务将直接返回它,否则它将创建一个新的地图元素,然后将其添加到缓存中。

为了测试我们的服务,我们希望曾经加载过的位置将从缓存中获取,而新的位置则使用功能来创建。

tests/unite/services/maps-test.js

import { moduleFor, test } from 'ember-qunit';
import Ember from 'ember';

const DUMMY_ELEMENT = {};

let MapUtilStub = Ember.Object.extend({
  createMap(element, location) {
    this.assert.ok(element, 'createMap called with element');
    this.assert.ok(location, 'createMap called with location');
    return DUMMY_ELEMENT;
  }
});

moduleFor('service:maps', 'Unit | Service | maps', {
  needs: ['util:google-maps']
});

test('should create a new map if one isnt cached for location', function (assert) {
  assert.expect(4);
  let stubMapUtil = MapUtilStub.create({ assert });
  let mapService = this.subject({ mapUtil: stubMapUtil });
  let element = mapService.getMapElement('San Francisco');
  assert.ok(element, 'element exists');
  assert.equal(element.className, 'map', 'element has class name of map');
});

test('should use existing map if one is cached for location', function (assert) {
  assert.expect(1);
  let stubCachedMaps = Ember.Object.create({
    sanFrancisco: DUMMY_ELEMENT
  });
  let mapService = this.subject({ cachedMaps: stubCachedMaps });
  let element = mapService.getMapElement('San Francisco');
  assert.equal(element, DUMMY_ELEMENT, 'element fetched from cache');
});

注意测试将使用一个虚拟对象作为地图元素的返回值。这可以是任何对象,因为它只是用来验证缓存已经被成功连接上了。同时也注意,位置名称在缓存对象中已经“驼峰化”了,所以它或许可以被用作一个键值。

现在,请像如下所示一样实现服务,请注意如果我们检查到一个给定对象对应的地图已经存在的时候将使用缓存,否则我们调用一个 Google 的地图功能来创建一个。我们基于 Ember 的功能之上,利用地图 API 来抽象我们的交互,以便于我们可以不向 Google 发起网络请求就能测试我们的服务。

app/services/maps.js

import Ember from 'ember';
import MapUtil from '../utils/google-maps';

export default Ember.Service.extend({

  init() {
    if (!this.get('cachedMaps')) {
      this.set('cachedMaps', Ember.Object.create());
    }
    if (!this.get('mapUtil')) {
      this.set('mapUtil', MapUtil.create());
    }
  },

  getMapElement(location) {
    let camelizedLocation = location.camelize();
    let element = this.get(`cachedMaps.${camelizedLocation}`);
    if (!element) {
      element = this.createMapElement();
      this.get('mapUtil').createMap(element, location);
      this.set(`cachedMaps.${camelizedLocation}`, element);
    }
    return element;
  },

  createMapElement() {
    let element = document.createElement('div');
    element.className = 'map';
    return element;
  }

});

获取 Google 地图

在实现地图功能前,我们需要使第三方地图 API 可用于我们的 Ember app。Ember 一共有好几种方式来引入第三方库。在你需要添加一个第三方库的时候,请把引导部分的 管理依赖 作为切入点。


未完待续

原文地址

你可能感兴趣的:(前端,Ember)