十一、与其他库的整合
UI 框架永远不会独立存在,尤其是 web 框架。web 是如此丰富的平台,由如此动态的社区驱动,有成千上万的库、小部件和组件可以在无数场景中使用,这为开发人员节省了大量时间。
在本章中,我们将了解如何将各种库集成到联系人管理应用中。我们将从 Bootstrap 和 jQueryUI 中添加 UI 小部件,使用sortable.js
添加一些拖放支持,使用 D3 添加图形。我们还将看到如何利用 SASS 而不是 CSS。最后,我们还将看到如何集成聚合物组件。
使用引导窗口小部件
从本书开始,我们就依赖于引导程序来设计应用的样式和布局。但是,我们没有使用该库的 JS 小部件。让我们看看如何将这些小部件集成到应用中。
加载库
由于 jQuery 由 Bootstrap 的 JS 小部件使用,我们首先需要安装它:
> npm install jquery --save
接下来,我们需要将 jQuery 和引导 JS 资源添加到供应商包中:
aurelia_project/aurelia.json
{
//Omitted snippet...
{
"name": "vendor-bundle.js",
"prepend": [
"node_modules/bluebird/js/browser/bluebird.core.js",
"scripts/require.js"
],
"dependencies": [
//Omitted snippet...
"jquery",
{
"name": "bootstrap",
"path": "../node_modules/bootstrap/dist",
"main": "js/bootstrap.min",
"deps": ["jquery"],
"exports": "$",
"resources": [
"css/bootstrap.min.css"
]
},
//Omitted snippet...
]
//Omitted snippet...
}
//Omitted snippet...
}
在这里,我们将 jQuery 添加到 bundle 的依赖项中,然后更新 Bootstrap 的条目,以便在 jQuery 完成后加载 JS 小部件。
应用中的bootstrap
模块也配置为导出全局jQuery
对象。这意味着我们将能够在 JS 代码中从bootstrap
导入jQuery
对象,并且我们将确保引导窗口小部件已经在 jQuery 上注册。
创建 bs 工具提示属性
让我们看一个简单的例子,使用 Aurelia 的 Bootstrap JS 小部件。我们将创建一个自定义属性,该属性将封装引导tooltip
小部件:
src/resources/attributes/bs-tooltip.js
import {inject, DOM, dynamicOptions} from 'aurelia-framework';
import $ from 'bootstrap';
const properties = [
'animation', 'container', 'delay', 'html',
'placement', 'title', 'trigger', 'viewport'
];
@dynamicOptions
@inject(DOM.Element)
export class BsTooltipCustomAttribute {
isAttached = false;
constructor(element) {
this.element = element;
}
attached() {
const init = {};
for (let property of properties) {
init[property] = this[property];
}
$(this.element).tooltip(init);
this.isAttached = true;
}
detached() {
this.isAttached = false;
$(this.element).tooltip('destroy');
}
}
这里,我们首先从引导导入 jQuery 全局对象开始。这将确保引导 JS 库已正确加载并注册到 jQuery 命名空间。我们还声明了tooltip
小部件支持的属性列表,因此该属性可以使用动态选项,而忽略不支持的选项。
我们将使用动态选项而不是显式选项,只是为了编写更少的代码。接下来我们将编写一些更改处理程序方法,如果我们使用一个显式的属性列表,所有属性都在BsTooltipCustomAttribute
类上声明为可绑定的,那么我们必须为每个属性编写一个不同的更改处理程序。所有这些更改处理程序都将做几乎相同的事情:更新引导程序小部件上的相应选项。相反,因为我们使用动态选项,所以我们可以编写一个更改处理程序来调用所有选项。
我们现在可以创建一个名为bs-tooltip
的自定义属性。它作为构造函数参数接收放置它的 DOM 元素。当连接到 DOM 时,它将绑定到每个受支持属性的属性的值分配给一个init
对象。然后将该对象传递给tooltip
初始化方法,该方法在承载该属性的元素上调用。最后一行将创建tooltip
小部件。
最后,当与 DOM 分离时,它只调用tooltip
小部件上的destroy
方法。
bs-tooltip
属性的第一个版本不支持更新属性。这可以通过使用propertyChanged
回调方法来更新tooltip
小部件来添加:
src/resources/attributes/bs-tooltip.js
//Omitted snippet...
export class BsTooltipCustomAttribute {
//Omitted snippet...
propertyChanged(name) {
if (this.isAttached && properties.indexOf(name) >= 0) {
$(this.element).data('bs.tooltip').options[name] = this[name];
}
}
}
在这里,当一个属性的值发生变化并且该属性当前已附加到 DOM 时,我们首先确保该属性受到小部件的支持,然后简单地更新小部件的属性。
使用属性
我们现在可以向任何元素添加引导tooltip
。让我们用list-editor
组件中的引导tooltip
替换删除按钮的title
属性:
src/resources/elements/list-editor.html
<!-- Omitted snippet... -->
<button type="button" class="btn btn-danger le-remove-btn"
click.delegate="removeItem($index)"
bs-tooltip="title.bind: 'resources.actions.remove' & t;
placement: right">
<i class="fa fa-times"></i>
</button>
<!-- Omitted snippet... -->
在这里,我们只需从删除按钮中删除t="[title]..."
属性,并将其替换为bs-tooltip
属性。在这个属性中,我们定义了一个title
选项,我们将与前面相同的翻译结果绑定到该选项。我们使用.bind
命令和t
绑定行为的事实将导致工具提示的title
在当前区域设置更改时更新。我们还指定使用placement
选项将tooltip
放置到托管元素的right
上。
不要忘记加载bs-tooltip
属性,无论是作为resources
功能的configure
函数中的全局资源,还是使用require
语句加载到list-editor
模板中。
如果此时运行应用,并将鼠标悬停在list-editor
实例之一的移除按钮上,则会显示一个引导tooltip
小部件。
创建 bs 日期选择器元素
我们的联系人管理应用可以从中受益匪浅的一个小部件是日期选择器。这将使大多数用户进入生日更加舒适。
Bootstrap 本身不包括日期选择器,但有些可以作为插件使用。在本节中,我们将安装bootstrap-datepicker
插件,加载它,并创建一个新的自定义元素,该元素将封装托管日期选择器的input
元素。
安装引导日期选择器插件
我们将首先安装引导插件:
> npm install bootstrap-datepicker --save
接下来,我们需要将其添加到供应商捆绑包中:
aurelia_project/aurelia.json
{
//Omitted snippet...
{
"name": "vendor-bundle.js",
"prepend": [
"node_modules/bluebird/js/browser/bluebird.core.js",
"scripts/require.js"
],
"dependencies": [
//Omitted snippet...
{
"name": "bootstrap-datepicker",
"path": "../node_modules/bootstrap-datepicker/dist",
"main": "js/bootstrap-datepicker.min",
"deps": ["jquery"],
"resources": [
"css/bootstrap-datepicker3.standalone.css"
]
},
//Omitted snippet...
]
}
//Omitted snippet...
}
在这里,我们将bootstrap-datepicker
库添加到供应商包中。就像标准的引导窗口小部件一样,这个插件在 jQuery 对象上添加了新的函数,因此它需要依赖 jQuery 才能注册自己。它还将自己的样式表作为附加资源加载。
创建自定义元素
现在插件已经准备好使用,我们可以开始构建自定义元素了。我们的bs-datepicker
元素将公开一个双向可绑定date
属性,该属性将被指定所选日期作为Date
对象。它还将公开一个可绑定的options
属性,该属性将用于提供传递给底层bootstrap-datepicker
小部件实例的选项。
首先,让我们编写它的模板:
src/resources/elements/bs-datepicker.html
<template>
<require from="bootstrap-datepicker/css/
bootstrap-datepicker3.standalone.css"></require>
<input ref="input" class="form-control" />
</template>
这个模板只需要样式表 bootstrap-datepicker
,然后声明一个input
元素。对该input
的引用将分配给绑定上下文的input
属性,因此视图模型可以使用它来承载日期选择器。
接下来,让我们编写视图模型类:
src/resources/elements/bs-datepicker.js
import {bindable, bindingMode} from 'aurelia-framework';
import $ from 'bootstrap';
import 'bootstrap-datepicker';
export class BsDatepickerCustomElement {
static defaultOptions = { autoclose: true, zIndexOffset: 1050 };
@bindable({ defaultBindingMode: bindingMode.twoWay }) date;
@bindable options;
isAttached = false;
isUpdating = false;
createDatepicker() {
const options = Object.assign({},
BsDatepickerCustomElement.defaultOptions,
this.options);
$(this.input).datepicker(options)
.on('clearDate', this.updateDate)
.on('changeDate', this.updateDate);
if (this.date) {
this.updateDatepickerDate();
}
}
destroyDatepicker() {
$(this.input)
.datepicker()
.off('clearDate', this.updateDate)
.off('changeDate', this.updateDate)
.datepicker('destroy');
}
updateDate = function() {
if (!this.isUpdating) {
this.date = $(this.input).datepicker('getUTCDate');
}
}.bind(this);
updateDatepickerDate() {
$(this.input).datepicker('setUTCDate', this.date);
}
optionsChanged() {
if (this.isAttached) {
this.destroyDatepicker();
this.createDatepicker();
}
}
dateChanged() {
if (this.isAttached) {
this.isUpdating = true;
this.updateDatepickerDate();
this.isUpdating = false;
}
}
attached() {
this.createDatepicker();
this.isAttached = true;
}
detached() {
this.isAttached = false;
this.destroyDatepicker();
}
}
我们首先从引导导入全局 jQuery 对象;记住,我们配置了引导库,这样当我们将 jQuery 对象添加到供应商包以写入bs-tooltip
属性时,它就会导出 jQuery 对象。
接下来,我们加载bootstrap-datepicker
插件,使其正确注册到 jQuery,然后创建自定义元素的类。
它首先声明一个静态defaultOptions
属性,用于设置创建小部件时传递给小部件的选项的默认值。
当元素附加到 DOM 时,它会在input
上创建一个datepicker
小部件实例。它还订阅小部件的clearDate
和changeDate
事件,因此当小部件选择的日期更改时,它可以更新自己的date
属性;然后初始化小部件的选定日期。
您可能想知道为什么我们要添加这些事件侦听器,为什么我们不只是绑定到input
的值。这是因为小部件已经处理了input
值的验证及其作为Date
对象的解析,因此我们的自定义元素依赖于datepicker
的选定日期要简单得多。基本上,我们的定制元素只是将其date
可绑定属性与所选日期datepicker
连接起来。当小部件的选定日期更改时,会触发其中一个事件侦听器,并将小部件的新值分配给元素的date
属性。类似地,由于元素的date
属性默认使用双向绑定,当date
属性更改时,主要是在模板中使用时初始化元素时,dateChanged
方法由绑定系统调用,小部件的选定日期将更新。我们还使用了一个isUpdating
属性来防止元素和小部件之间的无限更新循环。
当元素与 DOM 分离时,它首先取消订阅小部件的clearDate
和changeDate
事件,然后调用其destroy
方法。
最后,当元素的options
属性更改时,小部件将被销毁,然后重新创建。这是因为,在撰写本文时,bootstrap-datepicker
插件没有提供任何 API 来在创建小部件后更新其选项。
注
如您所见,此元素手动处理 Aurelia 和引导小部件之间的数据绑定。在 Aurelia 中集成外部 UI 库时,您在这里看到的模式(在小部件上注册事件处理程序以及来回同步数据)非常常见。
奥雷利亚社区的一个小组正在这方面做一些非常有趣的工作。他们开发了他们称之为桥的东西,允许我们在 Aurelia 应用中使用各种 UI 框架。他们已经为剑道 UI 发布了这样一个桥接器,并且正在为引导和物化等桥接器进行工作。如果你对这个主题感兴趣,我建议你看看他们的作品:https://github.com/aurelia-ui-toolkits 。
使用该元件
现在,我们可以轻松地将form
组件中绑定到触点生日的input
替换为新的bs-datepicker
元素:
src/contacts/components/form.html
<!-- Omitted snippet... -->
<div class="form-group">
<label class="col-sm-3 control-label"
t="contacts.birthday"></label>
<div class="col-sm-9">
<bs-datepicker date.bind="contact.birthday & validate">
</bs-datepicker>
</div>
</div>
<!-- Omitted snippet... -->
这里,我们简单地用一个bs-datepicker
元素替换前面的input
元素。我们将元素的date
属性绑定到contact
的birthday
属性,用validate
绑定行为装饰绑定,因此属性仍然有效。
由于我们新元素的date
属性需要一个Date
对象而不是字符串值,因此我们需要更改Contact
模型类,因此当从 JS 对象创建时,它会将其birthday
属性解析为Date
实例。此外,我们需要将birthday
的默认值从空字符串更改为null
:
src/contacts/models/contact.js
//Omitted snippet...
export class Contact {
static fromObject(src) {
const contact = Object.assign(new Contact(), src);
if (contact.birthday) {
contact.birthday = new Date(contact.birthday);
}
//Omitted snippet...
}
//Omitted snippet...
birthday = null;
//Omitted snippet...
}
现在,Contact
实例的birthday
属性将是null
值或Date
对象。
此时,如果运行应用,导航到 creation 或 edition 组件,并将焦点放在生日input
,则日期选择器应该会出现。您应该能够浏览日历并选择日期。
不要忘记加载bs-datepicker
元素,可以将其作为resources
功能的configure
函数中的全局资源,也可以使用require
语句加载到form
模板中。
bs 日期选择器元素国际化
此时,我们的bs-datepicker
元素不支持国际化。在典型的现实应用中,输入中显示的日期格式以及日历的文本和属性(如一周的第一天)应本地化。
谢天谢地,bootstrap-datepicker
包含本地化数据作为附加的 JS 模块。我们只需要在 bundle 中包含我们需要的区域设置的模块。
重新配置 jQuery 和 Bootstrap 的捆绑
但是,在编写本文时,本地化模块不支持模块加载机制,而是完全依赖于全局范围内的 jQuery 对象。因此,我们需要改变使用 jQuery 和引导窗口小部件的方式,方法是使用供应商包的prepend
属性,将它们加载为全局库,而不是作为 AMD 模块:
aurelia_project/aurelia.json
//Omitted snippet...
{
"name": "vendor-bundle.js",
"prepend": [
"node_modules/bluebird/js/browser/bluebird.core.js",
"node_modules/jquery/dist/jquery.min.js",
"node_modules/bootstrap/dist/js/bootstrap.min.js",
"node_modules/bootstrap-datepicker/dist/js/bootstrap-datepicker.min.js",
"node_modules/bootstrap-datepicker/dist/locales/
bootstrap-datepicker.fr.min.js",
"scripts/require.js"
],
"dependencies": [
//Omitted snippet...
]
}
//Omitted snippet...
在这里,我们将 jQuery、引导窗口小部件、bootstrap-datepicker
插件和它的法语本地化模块添加到包的预置库中(英语本地化数据内置在插件本身中,因此我们不需要包含它)。这意味着这些库只需在包的开头合并,而不作为 AMD 模块加载,而是使用全局window
范围。当然,这意味着 jQuery、Bootstrap 和日期选择器插件的条目必须从dependencies
数组中删除。
由于前置库只能是 JS 文件,这也意味着我们必须更改加载引导样式表的方式:
index.html
<!-- Omitted snippet... -->
<head>
<title>Learning Aurelia</title>
<link href="node_modules/bootstrap/dist/css/bootstrap.min.css"
rel="stylesheet">
<link href="node_modules/bootstrap-datepicker/dist/css/
bootstrap-datepicker3.standalone.css"
rel="stylesheet">
<!-- Omitted snippet... -->
<head>
<!-- Omitted snippet... -->
当然,bootstrap.css
和bootstrap-datepicker3.standalone.css
的require
语句必须分别从src/app.html
和src/resources/elements/bs-datepicker.html
模板中删除。
最后,必须从bs-tooltip.js
和bs-datepicker.js
文件中删除bootstrap
和bootstrap-datepicker
的import
语句,因为 jQuery、Bootstrap 和日期选择器插件将从全局范围访问。
更新元素
要本地化日期选择器小部件,我们只需设置language
选项:
src/contacts/components/form.html
<!-- Omitted snippet... -->
<bs-datepicker date.bind="contact.birthday & validate"
options.bind="{ language: locale }">
</bs-datepicker>
<!-- Omitted snippet... -->
这意味着我们需要将这个locale
属性添加到form
的视图模型中。我们还需要订阅适当的事件,以便在当前区域设置更改时更新属性:
src/contacts/components/form.js
//Omitted snippet...
import {I18N} from 'aurelia-i18n';
import {EventAggregator} from 'aurelia-event-aggregator';
@inject(DOM.Element, Animator, I18N, EventAggregator)
export class ContactForm {
@bindable contact;
constructor(element, animator, i18n, eventAggregator) {
this.element = element;
this.animator = animator;
this.i18n = i18n;
this.eventAggregator = eventAggregator;
}
bind() {
this.locale = this.i18n.getLocale();
this._localeChangedSubscription = this.eventAggregator
.subscribe('i18n:locale:changed', () => {
this.locale = this.i18n.getLocale();
});
}
unbind() {
this._localeChangedSubscription.dispose();
this._localeChangedSubscription = null;
}
//Omitted snippet...
}
在这里,我们首先从aurelia-i18n
库导入I18N
类,从aurelia-event-aggregator
库导入EventAggregator
类。然后我们向 DIC 提示它们都应该被注入视图模型的构造函数中。
当组件进行数据绑定时,我们使用I18N
实例的getLocale
方法初始化locale
属性,并订阅i18n:locale:changed
事件,以便保持locale
属性的最新状态。
最后,当组件解除绑定时,我们将处理事件订阅。
此时,如果您运行应用并在法语和英语之间来回切换当前区域设置时使用生日日期选择器,则input
中显示的日期格式以及日历文本和设置应相应更新。
使用 jQuery UI 小部件
jQueryUI 小部件库仍然相当流行。在 Aurelia 应用中集成这些小部件与我们刚刚使用引导程序小部件所做的非常相似,尽管不像我们在下一节中看到的那样轻松。
让我们使用 jQueryUI 创建一个tooltip
属性,这样我们就可以将它与 Bootstrap 进行比较。
注
以下代码片段摘自本书资产中的chapter-11/samples/using-jqueryui
示例。
安装库
我们首先需要通过在项目目录中打开控制台并运行以下命令来安装 jQuery 和 jQuery UI:
> npm install jquery --save
> npm install github:components/jqueryui#1.12.1 --save
接下来,我们需要将这些库添加到供应商包中。最简单的方法是将它们放在prepend
部分:
aurelia_project/aurelia.json
//Omitted snippet...
{
"name": "vendor-bundle.js",
"prepend": [
"node_modules/bluebird/js/browser/bluebird.core.js",
"node_modules/jquery/dist/jquery.min.js",
"node_modules/components-jqueryui/jquery-ui.min.js",
"scripts/require.js"
],
"dependencies": [
//Omitted snippet...
]
}
//Omitted snippet...
因为 CSS 文件不能全局加载到prepend
部分,所以让我们将它们加载到index.html
文件中:
index.html
<!-- Omitted snippet... -->
<head>
<title>Aurelia</title>
<link href="node_modules/bootstrap/dist/css/bootstrap.min.css"
rel="stylesheet">
<link href="node_modules/components-jqueryui/themes/base/all.css"
rel="stylesheet">
<!-- Omitted snippet... -->
</head>
<!-- Omitted snippet... -->
现在,我们可以创建属性了。
创建 jq 工具提示属性
首先,我们的新属性与使用引导的属性非常相似:
src/resources/attributes/jq-tooltip.js
import {inject, DOM, dynamicOptions} from 'aurelia-framework';
const properties = [
'classes', 'content', 'disabled', 'hide', 'position',
'show', 'track',
];
@dynamicOptions
@inject(DOM.Element)
export class JqTooltipCustomAttribute {
isAttached = false;
constructor(element) {
this.element = element;
}
attached() {
const options = {};
for (let property of properties) {
options[property] = this[property];
}
$(this.element).tooltip(options);
this.isAttached = true;
}
detached() {
this.isAttached = false;
$(this.element).tooltip('destroy');
}
}
我们首先定义 jQueryUItooltip
小部件支持的options
,这样该属性就可以使用动态选项,而忽略在此过程中不支持的选项;jq-tooltip
属性的行为与我们在上一节中创建的bs-tooltip
属性完全相同。接下来,我们向 DI 容器提示,应该将承载该属性的 DOM 元素注入构造函数。
当属性附加到 DOM 时,它检索绑定到每个受支持属性的属性实例的值,以构建一个options
对象。然后将该对象传递给tooltip
初始化方法,该方法应用于承载该属性的元素。
当属性与 DOM 分离时,小部件的destroy
方法被调用到承载该属性的元素上。
此时,该属性不支持属性更改。由于 jQuery 的tooltip
小部件提供了一个用于更新选项的 API,因此此实现不必像bs-tooltip
属性那样销毁并重新创建小部件来更新属性:
src/resources/attributes/jq-tooltip.js
//Omitted snippet...
propertyChanged(name) {
if (this.isAttached && properties.indexOf(name) >= 0) {
$(this.element).tooltip('option', name, this[name]);
}
}
//Omitted snippet...
在这里,我们只需添加propertyChanged
回调方法,如果属性附加到 DOM 并且小部件支持更新的属性,则该方法将更新小部件实例。
现在我们的属性已经准备好了,让我们将移除按钮的title
属性替换为list-editor
组件中的jq-tooltip
自定义属性:
src/resources/elements/list-editor.html
<!-- Omitted snippet.. -->
<button type="button" class="btn btn-danger le-remove-btn"
click.delegate="removeItem($index)"
jq-tooltip="content.bind: 'resources.actions.remove' & t">
<i class="fa fa-times"></i>
</button>
<!-- Omitted snippet.. -->
在这里,我们只需在适当的button
元素上添加一个jq-tooltip
属性。我们将它的content
属性绑定到适当的翻译,这是由t
绑定行为修饰的。
不要忘记加载jq-tooltip
属性,可以将其作为resources
功能的configure
函数中的全局资源,也可以使用require
语句加载到list-editor
模板中。
但是,如果您运行应用并用鼠标悬停在list-editor
元素的移除按钮上,您将看到tooltip
不会显示。
这是由一个众所周知的限制引起的;社区中的一些人会说这是tooltip
小部件中的一个 bug(我也同意),它强制宿主元素具有title
属性,即使它没有被使用。
因此,如果宿主元素上不存在空的title
属性,那么让我们更新该属性并添加一个方法来创建空的title
属性:
src/resources/attributes/jq-tooltip.js
//Omitted snippet...
attached() {
if (!this.element.hasAttribute('title')) {
this.element.setAttribute('title', '');
}
//Omitted snippet...
}
//Omitted snippet...
现在您可以运行应用,tooltip
应该会正确显示。
使用 SASS 代替 CSS
SASS是语法上非常棒的样式表,根据他们的网站,它是世界上最成熟、最稳定、最强大的专业级 CSS 扩展语言。不管这种说法是否属实,它都是最受欢迎的说法之一,至少我可以说我经常使用它。
在 Aurelia 应用中使用 SASS 而不是 CSS 非常简单,至少对于基于 CLI 的项目来说是如此。CLI 已经提供了对许多 CSS 处理器的支持,如 SASS、LESS 和手写笔。
让我们使用 CLI 重新创建联系人管理应用,并在创建过程中启用 SASS 处理器:
您可以为所有其他问题选择默认值。
创建项目并获取依赖项后,我们可以将以下目录和文件从应用的工作副本移动到新创建的项目:
aurelia_project/environments
locales
src
index.html
我们还需要从package.json
文件中复制dependencies
,并运行另一个npm install
以获取所有应用依赖项。最后,我们需要从aurelia_project/aurelia.json
文件复制供应商包配置。
您可以查看该账簿资产中的chapter-11/samples/using-sass
样本作为参考。
将 CSS 替换为 SASS
让我们将应用中的 CSS 文件转换为 SASS 文件,将.css
扩展名替换为.scss
扩展名:
src/resources/elements/list-editor.scss
list-editor .animated .le-item {
&.au-enter-active {
animation: blindDown 0.2s;
overflow: hidden;
}
&.au-leave-active {
animation: blindUp 0.2s;
overflow: hidden;
}
}
@keyframes blindDown {
0% { max-height: 0px; }
100% { max-height: 80px; }
}
@keyframes blindUp {
0% { max-height: 80px; }
100% { max-height: 0px; }
}
由于 CLI 创建的构建任务现在包含一个 SASS 处理器,src
目录中的每个.scss
文件都将转换为具有相同路径的.css
文件,并将包含在该路径下的app-bundle
中。
例如,resources/elements/list-editor.scss
文件将被转换为 CSS,结果将被捆绑为app-bundle
中的resources/elements/list-editor.css
。
这意味着require
语句必须使用.css
扩展继续引用样式表:
src/resources/elements/list-editor.html
<template>
<require from="./list-editor.css"></require>
<!-- Omitted snippet... -->
</template>
如果此时运行应用,则所有内容都应该像以前一样进行样式设置。
可排序拖放
可排序(https://github.com/RubaXa/Sortable 是一个著名的拖放库。其简单而强大的 API 使其集成非常容易。
让我们在我们的联系人管理应用中使用它,允许用户通过拖放为list-editor
元素重新排序项目。
安装库
首先,我们需要通过在项目目录中打开控制台并运行以下命令来安装库:
> npm install sortablejs --save
接下来,我们需要将其添加到供应商捆绑包中:
aurelia_project/aurelia.json
//Omitted snippet...
{
"name": "vendor-bundle.js",
"prepend": [
//Omitted snippet...
],
"dependencies": [
"sortablejs",
//Omitted snippet...
]
},
//Omitted snippet...
此时,我们可以开始在应用中使用该库。
添加拖放到列表编辑器
让我们首先为列表项添加一个句柄。此句柄是用户可以在列表中上下拖动项目的区域。此外,我们需要添加一个div
元素,它将充当可排序项目的容器:
src/resources/elements/list-editor.html
<!-- Omitted snippet... -->
<div ref="container">
<div class="form-group le-item ${animated ? 'au-animate' : ''}"
repeat.for="item of items">
<template with.bind="item">
<div class="col-sm-1">
<i class="fa fa-bars fa-2x sort-handle pull-right"></i>
</div>
<template replaceable part="item">
<div class="col-sm-2">
<template replaceable part="label"></template>
</div>
<!-- Omitted snippet... -->
</template>
<!-- Omitted snippet... -->
</template>
</div>
</div>
<!-- Omitted snippet... -->
在这里,我们首先将包含列表项的div
元素上的引用分配给视图模型的container
属性。sortable
API 将需要此container
来启用对其子项的拖放。接下来,我们从 label 列中删除col-sm-offset-1
CSS 类,并添加一个大小为 1 的列,使用 Bootstrap 的col-sm-1
CSS 类,该类包含一个bars
字体超级图标,并充当sort-handle
,使用相同名称的 CSS 类。
我们还要添加一个 CSS 规则来更改拖动手柄的鼠标光标:
src/resources/elements/list-editor.css
/* Omitted snippet... */
list-editor .sort-handle {
cursor: move;
}
我们现在可以使用sortable
添加拖放支持:
src/resources/elements/list-editor.js
//Omitted snippet...
import sortable from 'sortablejs';
export class ListEditor {
//Omitted snippet...
moveItem(oldIndex, newIndex) {
const item = this.items[oldIndex];
this.items.splice(oldIndex, 1);
this.items.splice(newIndex, 0, item);
}
attached() {
this.sortable = sortable.create(this.container, {
sort: true,
draggable: '.le-item',
handle: '.sort-handle',
animation: 150,
onUpdate: (e) => {
if (e.newIndex != e.oldIndex) {
this.animated = false;
this.moveItem(e.oldIndex, e.newIndex);
setTimeout(() => { this.animated = true; });
}
}
});
setTimeout(() => { this.animated = true; });
}
detached() {
this.sortable.destroy();
this.sortable = null;
}
//Omitted snippet...
}
这里,我们从导入sortable
API 开始。然后,当元素附加到 DOM 时,我们在具有le-item
CSS 类的container
项上创建一个sortable
实例。我们向sortable
指定具有sort-handle
CSS 类的项的子元素应用作拖动句柄。最后,当一个项目在列表中的不同位置被删除时,会触发onUpdate
回调,我们从items
数组中先前的位置删除删除的项目,然后将其插入到新位置。
我们需要使用splice
删除然后添加移动的项,因为 Aurelia 无法观察数组的索引设置器。它只能通过覆盖Array.prototype
的方法,如splice
来对数组的更改做出反应。
此外,我们需要在移动项目之前从项目中删除animated
CSS 类,这样触发动画的 CSS 规则就不会匹配。然后,我们使用setTimeout
将其添加回来,因此只有在模板引擎完成删除旧视图并添加新视图后,才会添加它。这样,移除或添加项目时播放的动画在拖放项目时不会播放,这看起来很奇怪。
最后,当list-editor
与 DOM 分离时,我们对sortable
实例调用destroy
方法,以防止内存泄漏。
此时,您可以运行应用,对联系人列表属性之一的项目重新排序,并保存表单。在“详细信息”视图中,项目应以新的顺序显示。
用 D3 作图
以图形形式呈现数据是现代应用的另一个常见需求。说到 Web,D3.js是一个众所周知的库,它提供了一个非常强大的 API 来显示 DOM 中的数据。
在下一节中,我们将向联系人管理应用添加一个树状视图,它将显示按地址部分分组的联系人。考虑所有联系人的所有地址,第一级节点将是国家,然后每个国家将其州作为子国,然后每个国家将其城市,依此类推。
注
我们将在本节中构建的树状图只是一个简单、糟糕的例子,说明了 D3 可以实现什么。转到https://d3js.org/ 浏览数百个样本,亲自体验这个库的强大功能。
安装库
让我们首先通过在项目目录中打开控制台并运行以下命令来安装库:
> npm install d3 --save
与往常一样,我们需要将其添加到供应商捆绑包中:
aurelia_project/aurelia.json
//Omitted snippet...
{
"name": "vendor-bundle.js",
"prepend": [
//Omitted snippet...
],
"dependencies": [
{
"name": "d3",
"path": "../node_modules/d3/build",
"main": "d3.min"
},
//Omitted snippet...
]
}
//Omitted snippet...
此时,D3 已准备好使用。
准备申请
在创建树本身之前,让我们先围绕它准备应用。我们将添加一个route
组件,该组件将使用网关加载联系人,我们将在其中显示树。我们还将在联系人main
中为该组件添加一个route
,然后添加允许在列表和树之间来回导航的链接。
让我们从route
开始:
src/contacts/main.js
//Omitted snippet...
config.map([
{ route: '', name: 'contacts', moduleId: './components/list',
title: 'contacts.contacts' },
{ route: 'by-address', name: 'contacts-by-address',
moduleId: './components/by-address',
title: 'contacts.byAddress' },
{ route: 'new', name: 'contact-creation',
moduleId: './components/creation',
title: 'contacts.newContact' },
{ route: ':id', name: 'contact-details',
moduleId: './components/details' },
{ route: ':id/edit', name: 'contact-edition',
moduleId: './components/edition' },
{ route: ':id/photo', name: 'contact-photo',
moduleId: './components/photo' },
]);
//Omitted snippet...
在这里,我们只需添加一个名为contacts-by-address
的route
与by-address
路径匹配,并指向by-address
组件,我们将在一分钟内创建该组件。
接下来,让我们向列表组件添加一个链接,该链接指向尚不存在的树组件:
src/contacts/components/list.html
<template>
<section class="container au-animate">
<h1 t="contacts.contacts"></h1>
<p>
<a route-href="route: contacts-by-address"
t="contacts.viewByAddress"></a>
</p>
<!-- Omitted snippet... -->
</section>
</template>
注
您可能注意到新的route
的title
属性和新链接的文本都使用了新的翻译,我将其添加作为练习留给读者。和往常一样,本章的示例应用可以作为参考。
最后,我们将创建by-address
组件。为了使事情尽可能地解耦,我们将在一个名为contact-address-tree
的自定义元素中隔离与 D3 相关的代码。by-address
组件的唯一责任是将此自定义元素与应用的其余部分连接起来。
让我们从视图模型开始:
src/contacts/components/by-address.js
import {inject} from 'aurelia-framework';
import {Router} from 'aurelia-router';
import {ContactGateway} from '../services/gateway';
@inject(ContactGateway, Router)
export class ContactsByAddress {
contacts = [];
constructor(contactGateway, router) {
this.contactGateway = contactGateway;
this.router = router;
}
activate() {
return this.contactGateway.getAll().then(contacts => {
this.contacts.splice(0);
this.contacts.push.apply(this.contacts, contacts);
});
}
navigateToDetails(contact) {
this.router
.navigateToRoute('contact-details', { id: contact.id });
}
}
这个视图模型非常简单。激活时,它使用注入网关检索联系人的完整列表。它还公开了一个方法,该方法触发对给定联系人详细信息组件的导航。单击树中的联系人节点时将调用此方法。
正如您可以想象的那样,该模板非常简单:
src/contacts/components/by-address.html
<template>
<require from="./by-address.css"></require>
<require from="../elements/address-tree"></require>
<section class="container au-animate">
<h1 t="contacts.byAddress"></h1>
<p>
<a route-href="route: contacts" t="contacts.viewByName"></a>
</p>
<contact-address-tree contacts.bind="contacts"
click.call="navigateToDetails(contact)">
</contact-address-tree>
</section>
</template>
该模板只声明一个contact-address-tree
元素,绑定加载的contacts
,并在点击联系人节点时调用navigateToDetails
。
CSS 文件只是设置contact-address-tree
元素的大小:
src/contacts/components/by-address.css
contact-address-tree {
display: block;
width: 100%;
min-height: 400px;
}
创建联系人地址树自定义元素
现在一切都准备好使用新元素了,让我们创建它。
注
由于我们正在添加更多联系人专用自定义元素,我建议我们在contacts
功能中创建一个新的elements
目录,将联系人form
移动到其中,并在其中创建这些新元素。本章完整的应用示例可作为参考。
我们将首先列出一些 CSS 规则,这些规则将设置各种树部分的样式,例如分支节点、叶节点和链接:
src/contacts/elements/address-tree.css
contact-address-tree .node circle {
fill: #d9edf7;
stroke: #337ab7;
stroke-width: 1.5px;
}
contact-address-tree .node text {
font: 15px;
}
contact-address-tree .node text {
text-shadow: 0 1px 0 #fff, 0 -1px 0 #fff, 1px 0 0 #fff, -1px 0 0 #fff;
}
contact-address-tree .leaf {
cursor: pointer;
}
contact-address-tree .leaf circle {
fill: #337ab7;
}
contact-address-tree .leaf text {
font-weight: bold;
}
contact-address-tree .link {
fill: none;
stroke: #777;
stroke-width: 1.5px;
}
由于树视图的呈现将由 D3API 处理,因此定制元素不需要模板。因此,它将使用noView
装饰器声明,CSS 文件的路径将传递到该装饰器,因此它将作为资源加载:
src/contacts/elements/address-tree.js
import {inject, DOM, noView, bindable} from 'aurelia-framework';
import * as d3 from 'd3';
@inject(DOM.Element)
@noView(['./address-tree.css'])
export class ContactAddressTreeCustomElement {
@bindable contacts;
@bindable click;
constructor(element) {
this.element = element;
}
}
此外,视图模型的构造函数将被注入 DOM 元素本身,因此 D3API 可以将其用作视口来渲染树。它还公开了一个contacts
和click
可绑定属性。
这是奥雷利亚的部分。现在,让我们添加一个attached
方法,该方法将渲染元素内部的树。此方法中的代码将完全忽略 Aurelia,只需使用d3
API 和 DOMelement
本身即可:
src/contacts/elements/address-tree.js
//Omitted snippet...
export class ContactAddressTreeCustomElement {
//Omitted snippet...
attached() {
// Calculate the size of the viewport
const margin = { top: 20, right: 200, bottom: 20, left: 12 };
const height = this.element.clientHeight
- margin.top - margin.bottom;
const width = this.element.clientWidth
- margin.right - margin.left;
// Create the host elements and the tree factory
const tree = d3.tree().size([height, width]);
const svg = d3.select(this.element).append('svg')
.attr('width', width + margin.right + margin.left)
.attr('height', height + margin.top + margin.bottom);
const g = svg.append('g')
.attr('transform',
`translate(${margin.left}, ${margin.top})`);
// Create the hierarchy, then initialize the tree from it
const rootNode = this.createAddressTree(this.contacts);
const hierarchy = d3.hierarchy(rootNode);
tree(hierarchy);
// Render the nodes and links
const link = g.selectAll('.link')
.data(hierarchy.descendants().slice(1))
.enter().append('path')
.attr('class', 'link')
.attr('d', d => `M${d.y},${d.x}C${(d.y + d.parent.y) / 2},
${d.x} ${(d.y + d.parent.y) / 2},
${d.parent.x} ${d.parent.y},
${d.parent.x}`);
const node = g.selectAll('.node')
.data(hierarchy.descendants())
.enter().append('g')
.attr('class', d => 'node ' + (d.children ? 'branch' : 'leaf'))
.attr('transform', d => `translate(${d.y}, ${d.x})`)
.on('click', e => { this.onNodeClicked(e); });
node.append('title')
.text(d => d.data.name);
node.append('circle')
.attr('r', 10);
node.append('text')
.attr('dy', 5)
.attr('x', d => d.children ? -15 : 15)
.style('text-anchor', d => d.children ? 'end' : 'start')
.text(d => d.data.name);
}
}
注
这段代码是迈克·博斯托克(Mike Bostock)在中的样本的简化改编 https://bl.ocks.org/mbostock/4339083 。
详细解释d3
API 的工作原理远远超出了本书的范围。但是,前面代码段中的内联注释可以让您很好地了解它的工作原理。
你可能注意到了一些缺失的部分:createAddressTree
和onNodeClicked
方法还不存在。
后者相当简单:
src/contacts/elements/address-tree.js
//Omitted snippet...
export class ContactAddressTreeCustomElement {
//Omitted snippet...
onNodeClicked(node) {
if (node.data.contact && this.click) {
this.click({ contact: node.data.contact });
}
}
}
此方法只是确保单击的节点是联系人节点,并且在使用单击的contact
对象调用它之前,click
属性已正确绑定。这将使用.call
命令执行绑定到click
属性的表达式,并将节点的联系人作为contact
参数传递给它。
前者稍微复杂一点。其工作是将联系人列表转换为树型数据结构,作为d3
API 的数据源:
src/contacts/elements/address-tree.js
//Omitted snippet...
export class ContactAddressTreeCustomElement {
//Omitted snippet...
createAddressTree(contacts) {
const rootNode = { name: '', children: [] };
for (let contact of contacts) {
for (let address of contact.addresses) {
const path = this.getOrCreateAddressPath(
rootNode, address);
const pathTail = path[path.length - 1];
pathTail.children.push({
name: contact.fullName,
contact
});
}
}
return rootNode;
}
getOrCreateAddressPath(rootNode, address) {
const countryNode = this.getOrCreateNode(
rootNode, address.country);
const stateNode = this.getOrCreateNode(
countryNode, address.state);
const cityNode = this.getOrCreateNode(
stateNode, address.city);
const streetNode = this.getOrCreateNode(
cityNode, address.street);
const numberNode = this.getOrCreateNode(
streetNode, address.number);
return [countryNode, stateNode, cityNode,
streetNode, numberNode];
}
getOrCreateNode(parentNode, name) {
name = name || '?';
const normalizedName = this.normalizeNodeName(name);
let node = parentNode.children
.find(n => n.normalizedName === normalizedName);
if (!node) {
node = { name, normalizedName, children: [] };
parentNode.children.push(node);
}
return node;
}
normalizeNodeName(name) {
return name.toLowerCase().trim().replace(/\s+/, ' ');
}
}
这里,createAddressTree
方法首先创建一个根节点,其空列表为children
。然后,它在每个联系人的addresses
上循环,并为每个联系人创建地址的节点路径,从国家开始,向下搜索到街道号码。不会再次创建整个路径或已存在的部分路径的节点,而只是简单地检索。最后,联系人自身的叶节点将附加到路径中的最后一个节点,即街道编号的节点。
此时,如果您运行应用并转到地址树视图,您应该会看到联系人显示在树中。
使用聚合物组件
Polymer是一个流行的库,它严重偏向 web 组件。它的社区提供了广泛的组件,其中一个google-map
元素封装了谷歌地图 API,以便以 HTML 声明方式显示地图。
Aurelia 提供了一个名为aurelia-polymer
的集成库,允许在 Aurelia 应用中使用聚合物组件。在下一节中,我们将把它集成到联系人管理应用中。在 details 组件中,我们将显示一个显示联系人地址的小地图。
安装库
聚合物及其库通常使用Bower进行安装。Bower 和 NPM 可以并排使用,没有任何问题,因此,如果您的开发环境中还没有 Bower 和 NPM,那么我们首先通过打开控制台并运行以下命令来安装它:
> npm install -g bower
Bower 是 web 库的另一个包管理器,可以在上找到 https://bower.io/ 。
完成后,让我们创建 Bower 的项目文件:
bower.json
{
"name": "learning-aurelia",
"private": true,
"dependencies": {
"polymer": "Polymer/polymer#^1.2.0",
"google-map": "GoogleWebComponents/google-map#^1.1.13",
"webcomponentsjs": "webcomponents/webcomponentsjs#^0.7.20"
}
}
此文件与package.json
非常相似。它描述了由 Bower 管理的项目的依赖关系。这里,我们包括聚合物和谷歌地图组件。
我们还包括了webcomponentjs
,它是各种 web 组件 API 的 polyfill,例如定制元素 API 和 HTML 导入 API。由于这两个 API 是 Polymer 所必需的,因此如果您所针对的浏览器本机不支持这两个 API,则需要使用此 polyfill。
注
您可以在此处检查您喜爱的浏览器是否支持所需的 API:http://caniuse.com/#feat=custom-元素 SV1和http://caniuse.com/#feat=imports 。
与 NPM 一样,必须安装项目文件中列出的包。因此,在项目目录中打开控制台并运行以下命令:
> bower install
完成此操作后,我们需要安装的最后一件事是 Polymer 和 Aurelia 之间的桥梁,通过在项目目录中打开控制台并运行以下命令来完成:
> npm install aurelia-polymer --save
配置应用
现在所有的东西都安装好了,我们需要配置我们的应用,以便它可以加载聚合物组件。
首先,我们将aurelia-polymer
库添加到供应商包中:
aurelia_project/aurelia.json
//Omitted snippet...
{
"name": "vendor-bundle.js",
"prepend": [
//Omitted snippet...
],
"dependencies": [
{
"name": "aurelia-polymer",
"path": "../node_modules/aurelia-polymer/dist/amd",
"main": "index"
},
//Omitted snippet...
]
}
//Omitted snippet...
当然,由于这个库是一个 Aurelia 插件,我们需要将它加载到应用的主configure
函数中:
src/main.js
//Omitted snippet...
export function configure(aurelia) {
aurelia.use
.standardConfiguration()
.plugin('aurelia-polymer')
.plugin('aurelia-animator-css')
//Omitted snippet...
}
如前所述,Polymer 依赖于 HTML 导入。在撰写本文时,基于 CLI 的 Aurelia 应用不支持使用 HTML 导入加载视图。因此,我们将无法在模板中加载需要它们的组件。我们别无选择,只能将它们加载到index.html
文件中:
index.html
<!-- Omitted snippet... -->
<head>
<!-- Omitted snippet... -->
<script src="bower_components/webcomponentsjs/
webcomponents-lite.js"></script>
<link rel="import" href="bower_components/polymer/polymer.html">
<link rel="import"
href="bower_components/google-map/google-map.html">
</head>
<!-- Omitted snippet... -->
在这里,我们首先加载 Web 组件 API polyfill。如果您不需要 polyfill,可以删除此线。接下来,我们进口聚合物和google-map
组件。
在准备生产的应用中,单独进口聚合物和每个组分是次优的。强烈建议将组件硫化成一束,可加载到index.html
文件中:https://github.com/Polymer/vulcanize 。
此时,与聚合物的集成已启动并运行。google-map
元件已准备好使用。
显示谷歌地图
首先,我们创建一个自定义元素,用于显示固定有单个地址的地图,以确保一切正常:
src/contacts/elements/address-map.html
<template>
<button class="btn btn-default"
click.delegate="isMapVisible = !isMapVisible">
${isMapVisible ? 'contacts.hideMap' : 'contacts.showMap' & t}
</button>
<google-map if.bind="isMapVisible"
style="display: block; height: 400px;"
api-key="your_key">
</google-map>
</template>
注
google-map
聚合物组件在幕后加载谷歌地图 API。为了正确加载,您需要一个 Google Maps API 密钥。您可以按照中的说明创建一个 https://developers.google.com/maps/documentation/javascript/get-api-key#key 。
在这里,我们首先添加一个按钮来切换isMapVisible
属性的值。接下来,我们添加一个google-map
聚合物元素。它的api-key
属性应该设置为您自己的 Google Maps API 密钥。
至于视图模型,目前几乎为空:
src/contacts/elements/address-map.js
export class AddressMapCustomElement {
isMapVisible = false;
}
最后,我们需要将此address-map
元素添加到触点的details
组件中:
src/contacts/components/details.html
<!-- Omitted snippet... -->
<div class="form-group" repeat.for="address of contact.addresses">
<label class="col-sm-2 control-label">
${'contacts.types.' + address.type & t}
</label>
<div class="col-sm-10">
<p class="form-control-static">
${address.number} ${address.street}</p>
<p class="form-control-static">
${address.postalCode} ${address.city}</p>
<p class="form-control-static">
${address.state} ${address.country}</p>
<address-map address.bind="address"></address-map>
</div>
</div>
<!-- Omitted snippet... -->
此时,如果您运行应用并导航到联系人的详细信息,您应该会在每个地址下看到一个按钮。如果你点击它,地图就会出现。
地理编码地址
为了在地图上显示地址作为标记,我们需要获得地址的地理坐标。因此,我们将创建一个名为Geocoder
的新服务,它将使用提名,这是一种基于OpenStreetMap
数据(的搜索服务 http://www.openstreetmap.org/ ),查找给定地址的纬度和经度:
src/contacts/services/geocoder.js
import {HttpClient} from 'aurelia-fetch-client';
export class Geocoder {
http = new HttpClient().configure(config => {
config
.useStandardConfiguration()
.withBaseUrl('http://nominatim.openstreetmap.org/');
});
search(address) {
const query = {
format: 'json',
street: `${address.number} ${address.street}`,
city: address.city,
state: address.state,
country: address.country,
postalcode: address.postalCode,
limit: 1,
};
return this.http.fetch(`search?${toQueryString(query)}`)
.then(response => response.json())
.then(dto => dto.length === 0 ? null : dtoToResult(dto[0]));
}
}
function toQueryString(query) {
return Object.getOwnPropertyNames(query)
.map(name => {
const key = encodeURIComponent(name);
const value = encodeURIComponent(query[name]);
return `${key}=${value}`;
})
.join('&');
}
function dtoToResult(dto) {
return {
latitude: parseFloat(dto.lat),
longitude: parseFloat(dto.lon)
};
}
这个类首先使用 Namingm 的 URL 和标准配置创建一个HttpClient
实例。然后,它公开了一个search
方法,该方法期望一个Address
对象作为参数向 Namingm 端点发送请求并返回结果Promise
。如果找不到地址,则使用null
解析此Promise
,或者使用包含匹配位置的latitude
和longitude
的对象解析此Promise
。
显示标记
现在我们可以对地址进行地理编码,让我们更新address-map
元素以显示其标记:
src/contacts/elements/address-map.js
import {inject, bindable} from 'aurelia-framework';
import {Geocoder} from '../services/geocoder';
@inject(Geocoder)
export class AddressMapCustomElement {
@bindable address;
isAttached = false;
isMapVisible = false;
isGeocoded = false;
latitude = null;
longitude = null;
constructor(geocoder) {
this.geocoder = geocoder;
}
addressChanged() {
if (this.isAttached) {
this.geocode();
}
}
attached() {
this.isAttached = true;
this.geocode();
}
detached() {
this.isAttached = false;
}
geocode() {
if (this.address) {
this.geocoder.search(this.address).then(position => {
if (position) {
this.latitude = position.latitude;
this.longitude = position.longitude;
this.isGeocoded = true;
} else {
this.isMapVisible = false;
this.isGeocoded = false;
this.latitude = null;
this.longitude = null;
}
});
}
}
}
在这里,我们首先向视图模型中注入一个Geocoder
实例。我们还添加了一个可绑定的address
属性。当元素附加到 DOM 时,我们对地址进行地理编码,如果找到了它的坐标,我们将设置latitude
和longitude
属性的值。我们还将isGeocoded
设置为true
。该标志最初设置为false
,如果地址无法本地化,将用于禁用切换按钮。如果找不到地址,我们隐藏地图,禁用切换按钮,并将latitude
和longitude
重置为null
。
在元素连接到 DOM 后,每当address
发生变化时,我们都会对其进行地理编码,以使latitude
和longitude
属性保持最新。
至于模板,我们不需要做太多更改:
src/contacts/elements/address-map.html
<template>
<button class="btn btn-default"
click.delegate="isMapVisible = !isMapVisible"
disabled.bind="!isGeocoded">
${isMapVisible ? 'contacts.hideMap' : 'contacts.showMap' & t}
</button>
<google-map if.bind="isMapVisible"
latitude.bind="latitude"
longitude.bind="longitude"
zoom="15"
style="display: block; height: 400px;"
api-key="your_key">
<google-map-marker latitude.bind="latitude"
longitude.bind="longitude"
open="true">
${address.number} ${address.street}
${address.postalCode} ${address.city}
${address.state} ${address.country}
</google-map-marker>
</google-map>
</template>
在这里,我们首先在isGeocoded
为false
时禁用切换按钮。接下来,我们绑定google-map
元素的latitude
和longitude
,并将其zoom
设置为15
,使其以地址的位置为中心。
最后,我们在google-map
元素中添加一个google-map-marker
元素。我们还将该标记的latitude
和longitude
绑定,并将其open
属性设置为true
,以便在渲染时打开其信息窗口。在标记中,我们将完整地址显示为文本,文本将在信息窗口中呈现。
你可能想知道这个google-map-marker
元素来自哪里。事实上,HTML 导入机制允许从单个文件加载多个组件。当我们在index.html
中导入bower_components/google-map/google-map.html
文件时,许多组分注册到了 Polymer,其中包括 map 和标记。
如果此时运行应用,导航到联系人的详细信息,并单击地址的查看地图按钮,地图应在适当位置显示标记,并显示完整地址的信息窗口。
总结
将 UI 库集成到 Aurelia 应用中几乎总是遵循相同的过程:在其周围创建自定义元素或属性。通过利用 Aurelia 的双向数据绑定,大多数情况下并不太复杂。
对于遵循良好实践和社区标准的库尤其如此,例如支持公共模块加载器、公开数据更改事件以及在其公共 API 中使用析构函数。较旧的库或不遵循这些标准的库,集成起来可能会更痛苦。奥雷莉亚则尽其所能让事情变得简单。
版权属于:月萌API www.moonapi.com,转载请注明出处