内容


构建 CouchApps

创建存储在 Apache CouchDB 数据库中的 web 应用程序

Comments

开始之前

本教程适用于那些对只用 HTML、CSS 和 JavaScript 就能创建数据库驱动的应用程序感兴趣的 web 应用程序开发人员。您应该知道如何编写 JavaScript 以及如何使用 JavaScript 操控 HTML 页面的 Document Object Model (DOM)。您应该还有使用库工具,如 jQuery 或 Dojo 的经验。

关于本教程

Apache CouchDB 是一个基于开源文档的数据库管理系统,它将数据存储为 JSON 对象。传统数据库系统可以使用一系列的 SQL 语句,通过某种形式的专有客户端软件或 API 来执行数据检索和更新功能。而 Apache CouchDB 不同 — 可以使用 RESTful HTTP API 发送查询或更新,从而几乎能在任何编程语言中与 Apache CouchDB 轻松地交互。

Apache CouchDB 所使用的架构实际上允许构建整个驻留在 Apache CouchDB 数据库中的 web 应用程序。我们把这些应用程序称为 CouchApps。CouchApps 能让您创建只用到 HTML、CSS 和 JavaScript 的完全由数据库驱动的应用程序。这些应用程序的好处就是您可以完全利用 Apache CouchDB 强大的复制功能在 Apache CouchDB 实例之间复制 CouchApp。这可以让 CouchApp 在几种设备上运行,使其同步,从而可以自动进行增量复制,保证每种设备上都是最新数据。

在本教程中,您将学习如何使用 HTML、CSS 和 JavaScript 创建自己的 CouchApp。您的应用程序将使用 jQuery 框架提供的 Ajax 来执行数据库操作。 您所构建的应用程序是一个联系管理器,它能让您查看、创建、编辑和删除联系。 最后,您还要学习如何在两个 Apache CouchDB 实例之间复制应用程序。

先决条件

学习本教程,需要用到以下工具:

  • An Apache CouchDB 数据库实例,v1.0.1 或更高版本
  • CouchApp 工具,0.7.0 或更高版本

参阅 参考资料 获取下载信息,并 下载 应用程序示例的源代码。

Apache CouchDB 和 CouchApps 简介

在这一节中,我们看一下在一个传统数据库解决方案上使用 Apache CouchDB 的优势。

Apache CouchDB 与传统数据库解决方案

Apache CouchDB 数据库系统的工作方式与传统数据库方案如 IBM DB2、Oracle 或 MySQL 完全不同。与这些方案中的结构化格式的数据库、表和列不同的是,Apache CouchDB 存储的是文档。文档是自由格式,这意味着可能会有任何字段和字段结构的组合,而且数据库中每个文档也不尽相同。

例如,在传统数据库中,您可能使用如 清单 1 所示的语句定义一个联系表,保存关于联系人的基本信息。

清单 1. 在传统关系型数据库中存储基本联系信息
CREATE TABLE contact (
  id int(11) NOT NULL AUTO_INCREMENT,
  title char(20) DEFAULT NULL,
  firstname char(20) DEFAULT NULL,
  lastname char(20) DEFAULT NULL,
  PRIMARY KEY (id)
)

这为数据设置了刚性结构,这很有效,同时又具有限制性。例如,如果要将中间名加入表格怎么办?那么您就要添加一个新字段来容纳新数据。另一个要考虑的方面就是额外信息 — 例如,用来联系某个人的电话号码 — 就可能在另一个表中。要取得某人的电话号码,您可能需要执行联系查询(或子查询)将基础联系表与电话号码表匹配。与其他数据连接也要这么做,例如地址和 email 账号,或是更灵活的数据,如重要日期、配偶和其他关联信息等。

在 Apache CouchDB 中存储信息

在 Apache CouchDB 中,信息被存储在文档中,文档格式自由、且使用 JavaScript Object Notation (JSON) 编写,允许您构建一个包含列表、哈希表以及传统字段的文档,并将所有的都包含在一个文档中。例如,清单 2 显示的是 JSON 格式的联系信息。

清单 2. JSON 格式的联系信息
{
   "firstname" : "Martin",
   "address" : {
      "home" : [
         "Some road",
         "Some town",
         "Postcode",
         "Country"
      ]
   },
   "title" : "Mr",
   "lastname" : "Brown",
   "phones" : {
      "home" : "09874978",
      "mobile" : "0892374908"
   }
}

清单 2 中,关于联系的所有信息都保存在一个文档中。但是,文档结构却不是固定的。清单 3 显示的是同一个数据库中不同的联系。

清单 3. 不同的联系
{
   "email" : {
      "work" : "sample@example.com",
      "home" : "other@example.com"
   },
   "firstname" : "Paulie"
}

不要认为对数据库内容使用格式自由的结构会失去执行结构的能力。可以使用一个验证例程,检查文档结构以及字段内容。

Apache CouchDB 中的文档使用一个文档 ID 存储。可以使用任意字符串作为文档 ID,您的联系信息可用文档 ID 'MartinBrown' 保存,或者可以让 Apache CouchDB 创建一个 UUID(通用惟一 ID)。

Apache CouchDB 系统与传统数据库还有一点不同,不需要专门的库或接口系统来访问或更新数据库。整个接口是构建在类似 REST 的接口上的,只要能访问 HTTP 的 web 页面,就能访问此接口。因此,我们可以通过打开一个 web 浏览器访问 http://127.0.0.1:5984/contacts/MartinBrown 网址来访问保存在 'Apache CouchDB' 机器上 'contacts' 数据库中的 MartinBrown 文档。'Paulie' 的联系方式存储在 URL http://127.0.0.1:5984/contacts/Paulie 中。

什么是 CouchApp?

这个简单的数据库 web 接口也提供 CouchApp 的基础。CouchApp 是基于 HTML5 和 JavaScript 的应用程序,它支持存储在 Apache CouchDB 数据库中的文档。组成接口和应用程序的文档和代码作为设计文档 存储在 Apache CouchDB 中。其结果是一个应用程序(包括显示元素)可以在提供数据的数据库中完全独立运行,从使得在整个构建应用程序以及与应用程序交互的过程中,您可以将精力集中在您想要表达的信息上。

使用 JavaScript 作为 CouchApps 核心部分还可以扩展到服务器,在这里使用 JavaScript 来构建服务器视图。在传统数据库,如 Oracle,SQL 的数据库结构能提供提取信息的功能。这样就很容易能够提取 firstname 字段为 'Martin' 的所有记录。但是,在 Apache CouchDB 中,数据存储在文档而不是表格中。要实现同样的效果,需要打开数据库中的每个文档,弄清楚其是否包含特定字段,如果该字段就是您所需要的,然后将此文档添加到列表中。这是一个耗费时间(和 CPU)的过程,尤其是数据库包含成千上万条记录时。

Apache CouchDB 使用视图来提升这些类型操作的性能。视图执行遍历每个文档以及建立含有特定字段的文档列表的操作。视图在服务器上构建,而结果视图存储在磁盘上,作为底层文档的索引。这样,以这种方式检索文档列表时就提升了性能,并且使根据特定字段值是否匹配来提取记录非常容易。

为了让构建 CouchApp 的过程更加容易,提供了一个名为 CouchApp 的命令行工具,它能为 Apache CouchDB 应用程序创建存根和模板代码,同时能在本地文件系统上创建文件,您可以使用 CouchApp 命令行工具编辑这些文件并将它们 “推送(push)” 到 Apache CouchDB 服务器中。这可以简化整个过程并意味着您可以将精力集中在应用程序上,而不必担心将应用程序上传到 Apache CouchDB 上。

还有一些不同之处,在此不详细讨论,在本教程其余部分会涉及到。例如,文档还会包含附件(与文档相关的文件),保存了所有文档修改,对文档的每一次更新都形成一个新的 “修订版”。CouchApp 工具隐藏了此复杂性,使得整个系统更易使用。

安装 CouchApp

在这一节我们将安装和配置 CouchApp

安装 Apache CouchDB

安装 CouchApp 之前,需要先安装 Apache CouchDB。您可以以多种形式下载 Apache CouchDB,包括作为源文件,然后您自己构建,或者作为独立应用程序在 Windows™、Mac OS X 和 Linux® 上运行。例如,Mac OS X 应用程序 Apache CouchDBX 作为标准应用程序运行。

在 Linux 和 UNIX® 上,会得到一个二进制文件 couchdb,可以从命令行运行(见 清单 4)。

清单 4. 从命令行运行二进制文件 couchdb
$ couchdb
Apache CouchDB 1.0.1 (LogLevel=info) is starting.
Apache CouchDB has started. Time to relax.
[info] [<0.33.0>] Apache CouchDB has started on http://127.0.0.1:5984/

现在一切就绪!可以通过给定的 URL 来检查数据库。如果想要打开服务器让它能从网络访问,那么就更改 local.ini 配置文件中的绑定参数来匹配 Apache CouchDB 服务器中的 IP 地址(不是主机名)。

安装 CouchApp

对于 CouchApp 命令行工具,需要从 GitHub 下载 CouchApp 的 tar 或 Zip 包(见 参考资料)。您还需要在机器上安装 Python(如果未安装的话),但 CouchApp 安装程序会解决所有附加库的依赖关系。

下载完包以后,解压缩,并放入 CouchApp 目录:$ cd couchapp

现在运行 Python 安装工具来安装所有依赖关系和 couchapp 工具: $ python setup.py install

您可以通过运行 CouchApp 来测试安装,它会返回工具的帮助信息: $ couchapp

请注意:通常情况下,您不会创建一个允许任何人访问和更新的数据库。Apache CouchDB 不支持认证,以及执行不同操作的安全和权限,本教程中不做介绍。

配置数据库,创建 CouchApp

您可以使用 curl 命令行工具通过命令在 Apache CouchDB 实例中创建一个新的数据库,这会帮助您了解 Apache CouchDB 的易用性。要创建一个数据库,您可以向您想要创建的数据库的 URL 发送一个 PUT HTTP 命令。例如,要创建一个联系数据库,您可以使用如下命令行:$ curl -X PUT http://127.0.0.1:5984/contacts

如果您查看联系文件内容,它应该说明操作成功完成。

或者使用 http://127.0.0.1:5984/_utils 进入 Futon。您可以使用 Futon 管理界面创建一个新数据库。

现在有了一个空数据库。要创建一个存根应用程序,您可以使用 CouchApp 来生成在您的文件系统中要上传到 Apache CouchDB 数据库中的所有基本文件。可以通过运行: $ couchapp generate app contacts

这样将创建一个 contacts 目录,它包含用来构建联系应用程序的文件数组和内容。您可以在 清单 5 中看到顶级文件列表。

清单 5. 顶级文件列表
$ ls -al contacts/
total 605-
drwxrwxrwx 9 mcco mcco 4096 Dec  1 14:49 ./
drwxrwxrwx 3 mcco mcco 4096 Dec  1 14:49 ../
-rw-rw-rw- 1 mcco mcco  174 Dec  1 14:49 .couchappignore
-rw-rw-rw- 1 mcco mcco    2 Dec  1 14:49 .couchapprc
-rw-rw-rw- 1 mcco mcco 1660 Dec  1 11:51 README.md
drwxrwxrwx 3 mcco mcco 4096 Dec  1 14:49 _attachments/
-rw-rw-rw- 1 mcco mcco   16 Dec  1 14:49 _id
-rw-rw-rw- 1 mcco mcco   70 Dec  1 11:51 couchapp.json
drwxrwxrwx 4 mcco mcco 4096 Dec  1 14:49 evently/
-rw-rw-rw- 1 mcco mcco   10 Dec  1 11:51 language
drwxrwxrwx 2 mcco mcco 4096 Dec  1 14:49 lists/
drwxrwxrwx 2 mcco mcco 4096 Dec  1 14:49 shows/
drwxrwxrwx 2 mcco mcco 4096 Dec  1 14:49 updates/
drwxrwxrwx 3 mcco mcco 4096 Dec  1 14:49 vendor/
drwxrwxrwx 3 mcco mcco 4096 Dec  1 14:49 views/

一些主要的元素有:

  • views— 包含数据库上的视图。它们是 JavaScript 函数,这些函数构建了您想要返回的键和数据的列表,等同于一般的数据库查询。
  • lists— 包含用来构建视图输出格式化版本的列表。 列表是 JavaScript 函数,它读取视图信息并格式化信息(通常是 HTML)用于显示。
  • shows— 显示单个文档,而不是视图中提供的一组文档。和列表一样,显示也是定义为 JavaScript 函数。
  • attachments— 包含应用程序的附件,包括 index.html 和 JavaScript 文件。

如果您不知道想要加载文档的文档 ID 时,视图对于访问数据库信息是至关重要的。列表和显示提供了内置的方法来显示信息。尽管如此,您也可以使用其他方法,如 jQuery 库,用视图从数据库提取信息,返回文档列表,然后用 jQuery Apache CouchDB 库处理。

编辑 index.html

默认文档 index.html 包含在数据库目录 contacts/_attachments/index.html 下。您应该编辑此文件,从而包含对数据库的默认链接。

为了能完全利用 jQuery 和 Apache CouchDB 提供的环境,我们动态定义 Contacts 应用程序的全部接口。这将会使用 JavaScript 来动态提供不同的元素,包括显示用来编辑联系信息的视图和表单的结果。

要使它起作用,按 清单 6 所示,编辑 index.html 文件。

清单 6. 编辑 index.html 文件
<!DOCTYPE html>
<html>
  <head>
    <title>Contacts</title>
    <link rel="stylesheet" href="style/main.css" type="text/css">
    <script src="vendor/couchapp/loader.js"></script>
    <script src="recordedit.js"></script>

  </head>
  <body>
    <div id="account"></div>

    <h1>Contacts</h1>

    <div id="items"><div id="add"><a href="#" 
class="add">Add Contact</a></div>
      <div id="contacts"></div>
      <div id="contactform"></div>

  </body>
</html>

结构的主要元素有:

  • 加载 vendor/couchapp/loader.js 脚本。然后它会加载 jQuery 和 jQuery Couch 库。
  • 加载 recordedit.js 脚本。此脚本中包含用来构建应用程序的 JavaScript 函数。
  • 用于触发 Add 表单创建新联系的按钮。
  • 包含 id 联系的 div 元素,它用来显示联系列表。
  • 带有 id 联系表单的 div 元素,它用来显示联系表单。

文件编辑完成后,您需要使用 CouchApp 命令行工具将它推送 到 Apache CouchDB 数据库: $ couchapp push contacts http://127.0.0.1:5984/contacts

第一个参数是推送(发布)应用程序,第二个参数是本地目录 contacts,其中保存着应用程序,第三个参数是您想要上传数据库的 Apache CouchDB 数据库的 URL。如果推送完全成功,您就可以使用如下 URL 查看上传的应用程序:http://127.0.0.1:5984/contacts/_design/contacts/index.html

URL 分析如下:

  • 127.0.0.1:5984 Apache CouchDB 服务器的主机名和端口号。默认情况下服务器运行在 5984 端口。
  • contacts 是数据库名称。
  • _design 是特定的 Apache CouchDB 标识符,它表示您想要访问设计文档。设计文档包含视图、列表和显示定义。 对于一个特定的数据库,有一个或多个设计文档。
  • contacts 是设计文档名称。CouchApp 工具默认创建与应用程序同名的设计文档。
  • index.html 是联系设计文档附件的名称。

CouchDB 还包含一个重写模块,它会使这些 URL 显得更加友好。清查阅 参考资料 中本主题的文章。

基本文档准备就绪,可以开始构建应用程序其余部分了。

显示联系列表

本节中,您将创建并显示联系列表。

创建视图

要创建联系列表,首先要创建视图。这是一个 JavaScript 函数,只有一个参数用来接收文档。在数据库中每个文档中该函数由 Apache CouchDB 执行,它会输出对于该视图中每个文档您想要为输出的键(用来显示和过滤信息)和对应值。此过程称为映射,因为您是将文档内容映射到想要提取的信息上。还有一个步骤,称为缩减,它用于概括或简化信息,但我们在联系应用程序中用不到它。

例如,要从联系文档中输出姓名作为键,整个联系记录作为值,您需要按 清单 7 所示编写视图。

清单 7. 编写视图
function(doc) {
    if (doc.name) {
emit(doc.name,doc);
    }
}

此匿名函数只有一个参数,就是文档。函数确认记录是否有 name 字段,如果有,则 emit() 函数会返回两个值:第一个是键,第二个是值,本例中,此值是文档的副本。键和值可以是任何有效的 JSON 结构。Apache CouchDB 在搜索和分页时会用到键。这些值仅包含视图被访问时要显示的信息。

在 CouchApp 中,可以使用 generate 命令通过命令行生成新视图:$ couchapp generate view contacts byname

这样就创建了视图 creates the view byname,并存放在 contacts/views/byname 目录,同时还创建了两个文件 map.js 和 reduce.js。编辑 map.js 文件并在清单 6 的函数中修改。

现在可以再次将应用程序推送到 Apache CouchDB 数据库中。可以从浏览器界面访问视图。您可以通过访问 URL http://127.0.0.1:5984/contacts/_design/contacts/_view/byname 来访问联系设计文档的 byname 视图。我们再次使用了设计文档联系,这次我们在路径中使用视图名称 byname 来请求视图(由 _view 来标识)。

在这个阶段,视图将是空的: {"total_rows":0,"offset":0,"rows":[]}

在应用程序中显示视图

要在应用程序中显示视图,我们可以使用 jQuery Couch 库来访问视图,遍历视图返回的每条记录,然后打印记录信息。

相应的函数如 清单 8 所示。

清单 8. 在应用程序中显示视图
db = $.couch.db("contacts");
function updatecontacts() {
$("#contacts").empty();

db.view("contacts/byname", {success: function(data) {
    for (i in data.rows) {
        $("#contacts").append('<div id="' + data.rows[i].value._id 
+ '" class="contactrow"><span>' +
                              data.rows[i].value.name +
                              "</span><span>" +
                              data.rows[i].value.phone +
                              "</span><span>" +
                              '<a href="#" id="' + data.rows[i].value._id 
+ '" class="edit">Edit Contact</a>' +
                              "</span><span>" +
                              '<a href="#" id="' + data.rows[i].value._id 
+ '" class="remove">Remove Contact</a>' +
                              "</span></div>"
                             );
    }
}
});
}

清单 7 中第一行设置一个用来访问数据库的变量。updatecontacts() 函数首先清空用来显示联系列表的 div 元素,然后访问刚刚创建的视图结果。如果视图可以成功访问,会调用一个匿名函数,并返回 JSON 结构的视图数据。函数会遍历内容,然后会构建一个联系行输出联系名称电话号码。

视图结果显示为一个数组(行),数组的每个元素都包含视图返回的键和值的内容。因此,我们可以通过访问数组元素的 value.field 部分来访问联系记录的电话号码。

输出会生成一个 Edit Contact 和一个 Remove Contact 链接,也包含底层 Apache CouchDB 文档的 ID。这会用来在更新和删除联系时提供信息。

要将函数添加到联系应用程序中,要创建一个新文件 contacts/_attachments/recordedit.js,并将函数加入文件。

第二步是保证文档已加载,并且调用了 updatecontacts() 函数来显示当前联系列表。jQuery 在文档上提供了一个 ready() 函数,从而变得很简单。当加载完成后,在函数中的所有设置都会执行。您可以在 清单 9 中看到定义。

清单 9. ready() 函数
$(document).ready(function() {

updatecontacts();

}

再次推送应用程序,重新加载 index 页面。显示的内容不应该有任何变化,但是最好是确保在添加新组件时应用程序不被破坏。

当然,列表仍然是空的,因此我们创建一个能用来查看联系的表单。

创建、编辑和删除联系

在本节中,我们将演示如何创建、编辑和删除联系。

创建新联系

作为一个 web 应用程序,创建一个新的联系,包括提供一个表单给用户填入,然后将表单内容写入 Apache CouchDB 数据库。完成第一步的是一个新的函数,它会动态生成表单 HTML,然后将它添加到 index.html 中定义的 contactform div 元素中(见 清单 10)。

函数如 清单 9 所示。

清单 10. 动态生成表单的 HTML
function contactform(doctoedit) {
var formhtml;
formhtml =
'<form name="update" id="update" action="">';

if (doctoedit) {
formhtml = formhtml +
    '<input name="docid" id="docid" type="hidden" value="' 
+ doctoedit._id + '"/>';
}

formhtml = formhtml +
'<table>';

formhtml = formhtml +
'<tr><td>Name</td>' +
'<td><input name="name" type="text" id="name" value="' +
(doctoedit ? doctoedit.name : '') +
'"/></td></tr>';
formhtml = formhtml +
'<tr><td>Phone</td>' +
'<td><input name="phone" type="text" id="phone" value="' +
(doctoedit ? doctoedit.phone : '') +
'"/></td></tr>';
formhtml = formhtml + '<tr><td>Email</td>' +
'<td><input name="email" type="text" id="email" value="' +
(doctoedit ? doctoedit.email : '') +
'"/></td></tr>';

formhtml = formhtml +
'</table>' +
'<input type="submit" name="submit" class="update" value="' +
(doctoedit ? 'Update' : 'Add') + '"/>' +
'</form>';
$("#contactform").empty();
$("#contactform").append(formhtml);
}

清单 9 的最后一行是键。$() 标记是使用 jQuery 函数的简写符号,此处我们用它将表单追加到现有元素中。使用 jQuery 访问 HTML 页面的 DOM 元素很容易。本例中,使用 # 前缀根据特定的 id 属性查找网页 DOM 中的元素。它遵循与 CSS 一样的格式,从而查找不同元素会很容易。句号前缀是根据特定类查找项目。我们稍后会看到示例。

函数本身接收一个参数,即要编辑的文档。 在编辑一个现有联系时会用到它。它在函数中,首先在引入联系的文档 ID(更新联系记录时会用到)时使用,然后是设置表单每个字段值时使用。

然而,表单的基本内容很简单;我们生成带有字段名称和 ID 文本输入元素(姓名、电话、 email 等等)。

要激活表单,需要启用 index.html 文件中的 Add Contact 链接,从而可以调用此函数。您可以通过将操作添加到 ready() 函数中来实现。这样就确保文档加载后按钮才会激活。见 清单 11 中的 jQuery 代码,在点击链接时更新操作。

清单 11. 显示联系表单
$("a.add").live('click', function(event) {
contactform();
});

再次将应用程序推送到 Apache CouchDB。您会看到 Add Contact 按钮弹出空值表单。可以在 图 1 中看到样例。

图 1. 空的联系表单
空的联系表单
空的联系表单

创建联系的第二步实际是在点击 Submit 按钮时将文档保存到数据库中。实现此功能的函数如 清单 12 所示。

清单 12. 当点击 Submit 按钮时,将文档保存到数据库
$("input.update").live('click', function(event) {
var form = $(event.target).parents("form#update");

    db.saveDoc(builddocfromform(null,form), {
        success: function() {
            $("form#update").remove();
            updatecontacts();
        },
    });
return false;
});

应当将此 JavaScript 片段添加到现有的 ready() 函数中。此代码片段加入了点击 Update 按钮时调用的函数。内联函数的第一行创建了一个变量,通过它我们可以访问表单的字段。

saveDoc() 函数将会把一个 JSON 结构保存为 Apache CouchDB 文档。第一个参数应该是文档数据,第二个是 JavaScript 对象,它定义了成功保存文档后如何处理。请记住,访问信息的 JavaScript 操作是异步的,即,请求发送到主机(Apache CouchDB),然后需要等待影响返回,然后才能处理信息。

此函数的第一个参数是另一个函数 builddocfromform() 的返回值。它是用来简化从表单数据构建文档的操作,无论您是创建新文档,还是编辑已有文档。相应的代码如 清单 13 所示。

清单 13. builddocfromform() 函数
function builddocfromform(doc,form) {
if (!doc) {
doc = new Object;
}
doc.name = form.find("input#name").val();
doc.phone = form.find("input#phone").val();
doc.email = form.find("input#email").val();

return(doc);
}

此函数接收现有的文档对象,如果文档未定义,就将它初始化为空的 JavaScript 对象。然后,它使用提供的表单 jQuery 对象访问表单中每个字段,并弹出文档对象,然后运行。您还可以在这里加入更多字段(如果你需要向表单 HTML 中加入更多字段)。

如果文档成功写入数据库,就会调用成功字段附加的异步函数。如果成功写入,就会先通过清空 contactform div 元素内容来移除联系表单的 HTML ,然后调用 updatecontacts() 函数,这将在显示中更新联系的活动列表。

现在可以再次将应用程序推送到 Apache CouchDB 实例,并向系统中加入联系。最终 Apache CouchDB 中有一个或多个联系,如 图 2 所示。

图 2. 一些联系记录
屏幕截图显示浏览器中显示联系记录,还有 Add Contact 链接
屏幕截图显示浏览器中显示联系记录,还有 Add Contact 链接

编辑现有联系

我们已经为编辑现有联系做了大量准备工作。输出表单的 JavaScript 函数已经接收了一个现有文档,表单也会从对象中弹出 Apache CouchDB 文档 ID 和现有值。

需要进行两项更改,首先是启用列表中每个联系的 Edit Contact。分别创建这些链接将是一个噩梦,好在 jQuery 提供了确定链接何时被点击的功能,它通过确定目标 DOM 对象来确定。此信息可用来访问文档的 ID,它包含在链接中,然后会从 Apache CouchDB 加载记录并调用表单函数。如 清单 14 所示。

清单 14. 通过确定目标 DOM 对象来确定链接何时被点击
$("#contacts").click(function(event) {

var target = $(event.target);
if (target.is('a')) {
id = target.attr("id");

if (target.hasClass("edit")) {
    db.openDoc(id, { success: function(doc) {
        contactform(doc);
    }});
}

}
});

这些应该被添加到 ready() 函数中。以下是相应的代码行:

  • 第一行确定 #contacts DOM 元素中的单击事件。
  • 第二行确定单击的目标。
  • 第三行将所点击的内容作为 “一个” 可点击元素。
  • 第四行确定 id 属性。在联系列表中,每个链接的 id 属性包含相应联系的文档 ID。
  • 第五行确定被点击的链接的类。Edit Contact 链接有一个 edit 类,移除链接有一个 remove 类。如果是一个 'edit' 链接,访问 Apache CouchDB 来加载文档(用 openDoc()),当文档成功加载后,会对文档数据调用 contactform() 函数。此时会显示一个已有联系信息的联系表单用于编辑。

其结果是当您单击联系的 Edit Contact 链接时,会显示联系细节的表单。

您也许想知道为什么显示的联系信息不会直接弹出表单。原因是,作为一个多用户应用程序,您会想要保证文档不会在您编辑之前就被另一个用户更新(或删除)。因此,您要保证文件存在,并且具有保存在数据库中的最新版本。

另一半过程是更改点击表单上的 Submit 按钮时调用的函数,这里您要确定现有记录是否被更新。如果我们更新现有文档,则 contactform() 函数只包含文档 ID,您可以用它来确定操作类型。如果我们更新现有文档,那么文档会从数据库加载,然后表单值被更新,最后文档被保存回数据库中。结果代码如 清单 15 所示。

清单 15. 更改单击表单上 Submit 按钮时调用的函数
$("input.update").live('click', function(event) {
var form = $(event.target).parents("form#update");

var id = form.find("input#docid").val();
if (id) {
    db.openDoc(id, {
        success: function(doc) {
            db.saveDoc(builddocfromform(doc,form), {
                success: function() {
                    $("form#update").remove();
                    updatecontacts();
                }});
        },
    });
}
else
{
    db.saveDoc(builddocfromform(null,form), {
        success: function() {
            $("form#update").remove();
            updatecontacts();
        },
    });
}
return false;
});

我们再次使用 builddocfromform() 函数加载现有文档并更新内容。这保证了我们更新的是最新版本的文档。这很重要,因为 Apache CouchDB 记录了所有文档的修改和更改。因此,您要保证更新的是最新版本 — 可检查修订号以确保您更新的是最新的版本。

我们加载文档,然后更新字段,最后保存回去。这还有一个原因。目前的表单只支持文档的姓名、电话和 email 字段。那么如果文档包含其他字段,而表单不支持会怎么样呢?通过加载整个现有文档,并更新表单上的字段,您不会丢失表单无法识别的任何字段。

当然,有时候您也会想删除记录。

删除现有联系

删除现有联系很简单。像 Edit Contact 链接一样,您可以向 Remove Contact 链接多添加一个对号。但是,您又不希望 Remove Contact 链接被误点,因此要提供确认过程以保证确实是要进行删除。

您可以使用以上提到的方法来输出一组新链接,然后使用单击事件对这些新链接发出确认、取消或删除请求。

代码应该加在清单 14 中的 edit 函数后面。代码如 清单 16 所示。

清单 16. 删除现有联系
if (target.hasClass("remove")) {
    html = '<span class="confirm">Really Delete? ' +
        '<a href="#" id="' + id + '" class="actuallydel">Delete</a>' +
        '<a href="#" id="' + id + '" class="canceldel">Cancel</a></span>';
    target.parent().append(html);
}

if (target.hasClass("actuallydel")) {

    db.openDoc(id, {
        success: function(doc) {
            db.removeDoc(doc, { success: function() {
            target.parents("div.contactrow").remove();
            }
                               });
        }
    }
               );
}

if (target.hasClass("canceldel")) {
    target.parents("span.confirm").remove();
}

当用户点击 Remove Contact 链接,会有两个链接加在联系后面。如果点击了 Delete 链接,文档就会加载(确认它仍然存在),然后会调用 removeDoc() 函数来删除它。如果成功,就移除整个联系行,这是通过查找父 DOM 元素来确定的。如果用户单击 Cancel,就只移除确定链接。

图 3 中,您可以看到有一个联系等待删除确认。

图 3. 联系等待删除确认
屏幕截图显示,浏览器中运行的应用程序确认删除某一个联系
屏幕截图显示,浏览器中运行的应用程序确认删除某一个联系

最终的应用程序

在此过程中,我们处理了各种不同的元素,却还没看到整个应用程序。 清单 17 显示的是 recordedit.js 文件,它包含应用程序所有的 JavaScript。

清单 17. recordedit.js 文件
db = $.couch.db("contacts");
function updatecontacts() {
    $("#contacts").empty();

    db.view("contacts/byname", {
        success: function(data) {
            for (i in data.rows) {
                $("#contacts").append('<div id="' + data.rows[i].value._id
+ '" class="contactrow"><span>' +
                                      data.rows[i].value.name +
                                      "</span><span>" +
                                      data.rows[i].value.phone +
                                      "</span><span>" +
                                      '<a href="#" id="' + data.rows[i].value._id 
+ '" class="edit">Edit Contact</a>' +
                                      "</span><span>" +
                                      '<a href="#" id="' + data.rows[i].value._id 
+ '" class="remove">Remove Contact</a>' +
                                      "</span></div>"
                                     );
            }
        }
    });
}

function contactform(doctoedit) {
var formhtml;
    formhtml =
'<form name="update" id="update" action="">';

    if (doctoedit) {
formhtml = formhtml +
    '<input name="docid" id="docid" type="hidden" value="' + doctoedit._id 
+ '"/>';
    }

    formhtml = formhtml +
'<table>';

    formhtml = formhtml +
        '<tr><td>Name</td>' +
'<td><input name="name" type="text" id="name" value="' +
(doctoedit ? doctoedit.name : '') +
'"/></td></tr>';
    formhtml = formhtml +
'<tr><td>Phone</td>' +
'<td><input name="phone" type="text" id="phone" value="' +
(doctoedit ? doctoedit.phone : '') +
'"/></td></tr>';
    formhtml = formhtml + '<tr><td>Email</td>' +
'<td><input name="email" type="text" id="email" value="' +
(doctoedit ? doctoedit.email : '') +
'"/></td></tr>';

formhtml = formhtml +
'</table>' +
'<input type="submit" name="submit" class="update" value="' +
        (doctoedit ? 'Update' : 'Add') + '"/>' +
'</form>';
    $("#contactform").empty();
    $("#contactform").append(formhtml);
}

function builddocfromform(doc,form) {
if (!doc) {
        doc = new Object;
    }
    doc.name = form.find("input#name").val();
    doc.phone = form.find("input#phone").val();
    doc.email = form.find("input#email").val();

return(doc);
}

$(document).ready(function() {

    updatecontacts();

    $("#contacts").click(function(event) {

    var target = $(event.target);
    if (target.is('a')) {
        id = target.attr("id");

        if (target.hasClass("edit")) {
            db.openDoc(id, { success: function(doc) {
        contactform(doc);
            }});
        }

if (target.hasClass("remove")) {
            html = '<span class="confirm">Really Delete? ' +
        '<a href="#" id="' + id + '" class="actuallydel">Delete</a>' +
                '<a href="#" id="' + id + '" class="canceldel">Cancel</a>
</span>';
            target.parent().append(html);
        }

if (target.hasClass("actuallydel")) {

    db.openDoc(id, {
        success: function(doc) {
            db.removeDoc(doc, { success: function() {
                    target.parents("div.contactrow").remove();
                    }
                                       });
                }
            }
                       );
        }

if (target.hasClass("canceldel")) {
            target.parents("span.confirm").remove();
        }
    }
    });

    $("a.add").live('click', function(event) {
contactform();
    });

    $("input.update").live('click', function(event) {
var form = $(event.target).parents("form#update");

        var id = form.find("input#docid").val();
        if (id) {
            db.openDoc(id, {
                success: function(doc) {
            db.saveDoc(builddocfromform(doc,form), {
                        success: function() {
                    $("form#update").remove();
                            updatecontacts();
                        }});
                },
            });
        }
        else
        {
            $db.saveDoc(builddocfromform(null,$form), {
                success: function() {
            $("form#update").remove();
                    updatecontacts();
                },
            });
        }
        return false;
    });
});

更新文件后,使用 CouchApp 将应用程序推送到 Apache CouchDB 实例,并试用一下。

还有一点 — 复制您的 CouchApp

Apache CouchDB 还有一个重要功能,是能将您数据库中的文档复制到另一个数据库中,无论数据库存放在相同的 Apache CouchDB 实例还是远程实例上。可以双向同步,这意味着,您可以将联系数据库从台式电脑复制到笔记本上,然后在笔记本上修改,然后将修改同步回台式电脑,从而两个数据库通过同步保持一致。

还有额外的惊喜,刚刚作为文档存储在 Apache CouchDB 数据库中的 CouchApps,当您复制联系数据库时,同时也在复制构成 CouchApp 应用程序的应用程序代码。在更为传统的环境中,这很难做到。而 CouchApps 是将其作为 Apache CouchDB 功能的一部分。

您可以通过使用一个如 curl 的命令行工具将请求发送到 Apache CouchDB 服务器来进行复制,还有另外一个方法,使用 Futon 工具,它是 CouchApp 在每个 Apache CouchDB 实例中内置的,为 Apache CouchDB 和存储在 Apache CouchDB 数据库中的文档提供一整套管理和编辑界面。

可以通过访问 http://127.0.0.1:5984/_utils 来获取 Futon。图 4 中显示了 Futon 界面。

图 4. Futon 界面
屏幕截图显示浏览器中运行的 Futon,左边是现有数据库列表,右边是工具菜单
屏幕截图显示浏览器中运行的 Futon,左边是现有数据库列表,右边是工具菜单

点击右边的 Replicator 链接,您会看到如 图 5 所示的表单。

图 5. Replicator 表单
屏幕截图显示复制一个数据库,其中有若干字段让用户选择源和目的
屏幕截图显示复制一个数据库,其中有若干字段让用户选择源和目的

在推送(从当前 Apache CouchDB 实例到远程数据库)或提取(从远程 Apache CouchDB 实例到本地 Apache CouchDB 实例)过程中都可以进行复制。也可以复制一次,或者设置连续复制,这就意味着,对于一个数据库的修改可以复制到其他的数据库,如果其他数据库可访问的话。

例如,您可以在您的笔记本上启动一个 Apache CouchDB 实例,然后可以将联系数据库从 CouchDB 服务器复制到本地联系数据库。您会看到表单已填充,以及复制过程已经启动,如 图 6 所示。

图 6: 复制成功
屏幕截图显示的是复制成功后的复制窗口,在窗口底部的 Event 框中显示了控制台信息
屏幕截图显示的是复制成功后的复制窗口,在窗口底部的 Event 框中显示了控制台信息

应用程序复制到笔记本的 Apache CouchDB 实例后,您可以使用完全一样的界面来编辑和更新数据库。 还有,您回到家后可以将更改复制回台式电脑中。

改善建议

本教程中截图样例可能与您的应用程序不大一样,因为已经对 CSS 进行了更新来改善布局。幸运的是,我们对所有不同的部件(包括表单、联系列表和链接)都应用了类和 ID,更改格式就很简单了。CSS 是在本地文件系统的 contacts/_attachments/style/main.css 文件中定义的,在使用 couchapp 推送应用程序时会包含进去。更改 CSS 也许是能改善应用程序的最简单的方法。

然后,您会想改善提取的数据(可以通过更新表单和存储数据的结构来调整)。例如,在简介中提到的,您可以通过在文档的单独结构中存储电话号码来添加对多个号码的支持。

在扩展了显示信息之后,您可能想要开始改进联系列表,从而能够在页面中显示。Apache CouchDB 中的分页功能使用返回的键作为视图的一部分。您也许还想构建不同的视图,并提供搜索功能,这些都可以通过构建额外的视图实现,这些视图会以不同的方式来显示联系列表。

结束语

CouchApps 和 Apache CouchDB 为构建 web 应用程序提供了丰富的环境。构建表单、保存数据和生成数据库内容报告的全部过程都保存在 Apache CouchDB 数据库中。使用 JavaScript 和 jQuery 库能降低构建应用程序的复杂度。同时,使用 Apache CouchDB 不必担心定义表或构造复杂查询来获取保存的信息。最后,您可以使用 Apache CouchDB 将整个应用程序,包括所有数据,复制到另一个 Apache CouchDB 实例中,包括笔记本和手机。


下载资源


相关主题


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Open source, Web development
ArticleID=751323
ArticleTitle=构建 CouchApps
publish-date=08082011