使用CouchDB和Angular跟踪费用

在本教程中,我们将使用CouchDB作为后端并选择Angular作为前端技术来构建应用程序。 CouchDB是NoSQL数据库,而Angular是更新的JavaScript MVC框架之一。 令人兴奋和令人敬畏的是,CouchDB是具有HTTP API的数据库–我们的客户端应用程序将直接与该数据库通信:CouchDB将充当我们客户端应用程序所需的唯一后端!

我们将专注于一个小型应用程序来跟踪我们的费用。 每个步骤都会有一个提交,有时该提交还包括测试。 测试不是本教程中的主题,但是如果您对此感兴趣,则应该看看! 您将在GitHub的存储库中找到本教程中使用的全部代码。

为什么选择CouchDB?

你们中有些人可能会说我们可以使用客户端替代方法。 IndexedDB或Local Storage是在客户端本地工作以持久化数据的技术。 但是使用数据库服务器有几个优点:我们可以将许多客户端连接到我们的应用程序。 当您独自在另一个超市中时,您的伴侣可以更新费用清单,这也会增加费用。

使用CouchDB有很多优点:CouchDB本机“说” HTTP,因此我们在数据库和应用程序之间不需要其他层。 我们的JavaScript应用程序可以使用CouchDB提供的RESTful接口直接与CouchDB数据库对话!

而且,如果我们想对数据库使用复制,那就像切面包一样容易:因为CouchDB是为创建分布式数据库系统而设计的。

要求

对于本教程,您将需要安装最新版本的CouchDB(1.6)和最新的稳定Node.js(当前为0.10.x)版本。

安装Node.js和Yo

作为Mac用户,您可以在Node主页上获得官方安装程序。 在Linux和OSX上管理Node.js安装的另一种方法是Tim Caswell的出色nvm

我们将安装Yo来搭建我们的应用程序。 在创建骨骼的过程中,您会问我们一些问题。 Yo询问我们是否要使用SASS,如果不确定是否要回答“否”,但我们肯定要包括Bootstrap和预选的Angular-Modules。

在我们的外壳中,键入:

npm install -g yo generator-angular grunt-cli couchapp
mkdir expenses && cd expenses
yo angular expenses

作为我们脚手架的一部分,Yo为我们创建了一个Gruntfile(Gruntfile.js)。 Grunt是JavaScript的任务执行者,它具有许多已经编写的插件,可以自动执行任务并使您的生活更轻松。

使用grunt serve命令启动开发服务器,并在grunt任务完成后在浏览器中打开http://127.0.0.1:9000 。 下图显示了一个示例。

脚手架

安装CouchDB

很棒的文档可以在很多平台上安装CouchDB –有适用于所有主要操作系统的软件包,在OSX上,您可以使用brew安装CouchDB。

CouchDB的第一步

让我们开始第一个CouchDB实例并创建一个数据库:

couchdb & # start a CouchDB
curl -X PUT http://127.0.0.1:5984/expenses # create the database expenses

CouchDB回答:

{"ok":true}

我们刚刚使用HTTP创建了第一个数据库!

让我们进一步探讨CouchDB的HTTP API:现在我们可以插入第一个文档,假设我们要跟踪购买的爆米花(稍后我们的应用程序需要对CouchDB的这些调用)。

curl -X POST http://127.0.0.1:5984/expenses -H "Content-Type: application/json" -d '{"name": "Popcorn", "price": "0.99"}'

CouchDB的答案:

{"ok":true,"id":"39414de82e814b6e1ca754c61b000efe","rev":"1-2b0a863dc254239204aa5b132fda8f58"}``

现在,我们可以使用GET请求和CouchDB分配给我们文档的ID来访问文档,因为我们没有提供特定的ID:

curl -X GET http://127.0.0.1:5984/expenses/39414de82e814b6e1ca754c61b000efe

CouchDB的答案:

{"_id":"39414de82e814b6e1ca754c61b000efe","_rev":"1-2b0a863dc254239204aa5b132fda8f58","name":"Popcorn","price":"0.99"}

之后,我们插入另一个文档:

curl -X POST http://127.0.0.1:5984/expenses -H "Content-Type: application/json" -d '{"name": "Washing powder", "price": "2.99"}'

配置:带有CouchDB的CORS

我们的客户将通过HTTP从CouchDB本身以外的其他位置进行通信。 为了使此功能在我们的浏览器中起作用,我们必须在CouchDB中启用CORS(跨域资源共享)。

在这种情况下,我们要为本地自定义更改修改local.ini 。 可以通过HTTP修改配置。 在https部分中,我们启用CORS,然后使用通配符配置源:

curl -X PUT http://localhost:5984/_config/httpd/enable_cors -d '"true"'
curl -X PUT http://localhost:5984/_config/cors/origins -d '"*"'

使用这两个命令,我们正在更改CouchDB的local.ini 。 您可以使用couchdb -c查找local.ini的位置。

重要! 请注意,如果您将应用程序部署到生产环境中,则可能需要更改“来源”部分。 此处提供的所有设置仅用于开发!

角度和依赖注入

app/scripts/app.js我们将找到应用程序的主要JavaScript文件,实际上这就是所谓的Angular模块。 此模块将其他一些模块作为依赖项加载(例如ngCookies )。 在此文件中,我们还使用$routeprovider找到了应用程序的客户端路由。

该文件中的$routeprovider是Angular依赖项注入(DI)的一个很好的例子。 通过定义您要使用的服务的名称,Angular将其注入给定的功能范围。 您可以在docs中找到有关Angular依赖注入的更多信息。

因为我们希望在一个中央位置拥有连接到CouchDB所需的数据,所以让我们尝试使用具有常数的DI。 我们使用链接将它们添加到我们的模块中:

.constant('appSettings', {
  db: 'http://127.0.0.1:5984/expenses'
});

我们迄今为止唯一控制器,它在初始支架期间创建,是MainCtrl位于app/scripts/controllers/main.jsMainCtrl被定义并且$scope被注入。 稍后我们将看到如何使用范围。

现在我们可以将appSettings添加到函数参数中以注入它们,就像我们之前使用$routeprovider看到的$routeprovider

.controller('MainCtrl', function ($scope, appSettings) {
  console.log(appSettings);
});

现在,您应该能够在浏览器的调试控制台上记录输出。 恭喜你! 您已成功使用依赖项注入。 您可以在以下位置找到完整的提交: https : //github.com/robertkowalski/couchdb-workshop/commit/d6b635a182df78bc22a2e93af86162f479d8b351

取得结果

在下一步中,我们将注入$http服务以从CouchDB中获取数据并更新视图。 当传统数据库使用分解为表的数据时,CouchDB使用的是非结构化文档,可以使用地图和归约函数(称为视图)来聚合,过滤和合并非结构化文档。 视图由设计文档(一种特殊的文档)定义。

您可以自行编写视图,然后通过curl将其发送到CouchDB,使用位于http://localhost:5984/_utils的图形界面或通过诸如CouchApp之类的工具–有许多诸如CouchApp之类的工具( npm install -g couchapp ),以npm install -g couchapp视图的开发和部署。

这是我们的视图:

{
  "_id":"_design/expenses",
  "views": {
    "byName": {
      "map": "function (doc) {
        emit(doc.name, doc.price);
      }"
    }
  }
}

_id对我们很重要,因为它定义了以后查询视图的路径。 在创建设计文档时, _id属性以_design为前缀。 我们将视图byName ,它仅包含一个基本的map函数,该函数将发出数据库中每个文档的name属性作为键,并将price作为值。

让我们使用curl将其发送到CouchDB:

curl -X POST http://127.0.0.1:5984/expenses -H "Content-Type: application/json" -d '{"_id":"_design/expenses","views": {"byName": {"map": "function (doc) {emit(doc.name, doc.price);}"}}}'

CouchDB响应:

{"ok":true,"id":"_design/expenses","rev":"1-71127e7155cf2f780cae2f9fff1ef3bc"}

现在,我们可以在以下位置查询:

http://localhost:5984/expenses/_design/expenses/_view/byName

如果您对诸如CouchApp之类的工具感兴趣(提示:您稍后必须使用它),那么这里的提交显示了如何使用它(使用npm run bootstrap部署设计文档)。

您还记得起初的卷曲要求吗? 现在,我们将用JavaScript实现它们。 Angular提供$http服务,可以如下所示进行注入:

.controller('MainCtrl', function ($scope, $http, appSettings) {

然后,我们添加一个函数以使用$http服务获取项目:

function getItems () {
  $http.get(appSettings.db + '/_design/expenses/_view/byName')
    .success(function (data) {
      $scope.items = data.rows;
    });
}
getItems();

$http服务返回一个Promise,它将从CouchDB视图提供给我们JSON数据。 我们正在将数据添加到$scope.items 。 使用$scope我们可以在视图中设置和更新值。 如果模型上的值发生更改,则视图将自动更新。 Angular的双向绑定使视图和模型之间的数据同步。 控制器更改模型后,它将立即更新视图,并且当视图中的值更改时,还将更新模型。

删除大部分样板标记后,让我们添加一些带有表达式的HTML来在app/views/main.html显示我们的项目:

<div>{{ item[0].key }}</div>
<div>{{ item[0].value }}</div>

我们将在“使用CouchDB的第一步”部分中看到添加的第一项:

应用程式检视

该部分的提交可在GitHub上获得。

使用指令: ng-repeat

现在,我们应该看到第一个项目,但是其他所有项目呢?

我们可以在此处使用ng-repeat指令,它将为我们从更长的列表中构建标记。 通常,我们可以说Angular中的指令将行为附加到DOM元素。 Angular中还有许多其他预定义的指令,您也可以定义自己的指令。 在这种情况下,我们将ng-repeat="item in items"到外部div ,然后它将从$scope.items迭代数组items

pull-leftpull-right是Bootstrap CSS的一部分,并为我们提供了浮动元素。 由于元素是浮动的,因此我们正在应用一个clearfix ,它也包含在Bootstrap中:

<div ng-repeat="item in items">
  <div class="clearfix">
    <div class="pull-left">{{ item.key }}</div>
    <div class="pull-right">{{ item.value }}</div>
  </div>
</div>

如果刷新页面,则项目将在DOM检查器中呈现为:

<!-- ngRepeat: item in items -->
<div ng-repeat="item in items" class="ng-scope">
  <div class="clearfix">
    <div class="pull-left ng-binding">Popcorn</div>
    <div class="pull-right ng-binding">0.99</div>
  </div>
</div>
<!-- end ngRepeat: item in items -->
<div ng-repeat="item in items" class="ng-scope">
  <div class="clearfix">
    <div class="pull-left ng-binding">Washing powder</div>
    <div class="pull-right ng-binding">2.99</div>
  </div>
</div>
<!-- end ngRepeat: item in items -->

我们现在有一个不错的小清单,但是除了使用curl之外,还没有办法通过我们的应用程序提交新项目。 到目前为止, 此提交中的应用程序可用,如下图所示。

列表显示

创建用于提交项目的表格

我们将添加一个包含两个输入的表单:一个输入商品名称,另一个输入价格。 该表格还具有一个用于提交我们的物品的按钮。

Bootstrap中带有class="row"div用于以响应方式对我们的应用程序进行样式设置。 Bootstrap类(如form-controlbtn btn-primary用于设置按钮和输入的样式。

表单还获得了novalidate属性:它禁用了浏览器的本机表单验证,因此我们稍后可以使用Angular来验证表单:

<form class="form-inline" role="form" novalidate>
  <div class="row">
    <div class="form-group">
      <label class="sr-only" for="item-name">Your item</label>
      <input
        class="form-control"
        id="item-name"
        name="item-name"
        placeholder="Your item" />
    </div>
    <div class="form-group">
      <label class="sr-only" for="item-price">Price</label>
      <input
        class="form-control"
        id="item-price"
        name="item-price"
        placeholder="Price" />
    </div>
  </div>
  <div class="row">
    <button
      class="btn btn-primary pull-right"
      type="submit">Save</button>
  </div>
</form>

表单的提交位于https://github.com/robertkowalski/couchdb-workshop/commit/d678c51dfff16210f1cd8843fbe55c97dc25a408

在CouchDB中保存数据

使用ng-model我们可以观察并访问控制器中输入的值,然后将它们发送到CouchDB。 对于我们的价格输入,我们将添加属性ng-model="price"

<input
  class="form-control"
  ng-model="price"
  id="item-price"
  name="item-price"
  placeholder="Price" />

名称的输入将获得ng-model="name"属性。 看起来像这样:

<input
  class="form-control"
  ng-model="price"
  id="item-price"
  name="item-price"
  placeholder="Price" />

我们还在最后一项下面添加了一个小状态框。 我们将需要它来显示错误。

<div class="status">
  {{ status }}
</div>

现在,我们可以使用$scope.price$scope.name访问控制器中的值。 范围将视图连接到我们的控制器。 看一下Model-View-Controller(MVC)模式,范围就是我们的模型。 Angular有时也称为MVVM(模型-视图-视图-模型)框架-所有这些JavaScript MVC框架通常被称为MVW(模型-视图-任何模型),因为它们之间有很多细微的差异。

但是我们如何提交表格?

发送表单的常用方法是在$scope上定义一个函数,并在视图中结合ng-submit指令。 我们的函数将构建要发送到CouchDB的JSON。 创建JSON之后, processForm将调用postItem ,它将把JSON发送到CouchDB:

$scope.processForm = function () {
  var item = {
    name: $scope.name,
    price: $scope.price
  };
  postItem(item);
};
function postItem (item) {
  // optimistic ui update
  $scope.items.push({key: $scope.name, value: $scope.price});
  // send post request
  $http.post(appSettings.db, item)
    .success(function () {
      $scope.status = '';
    }).error(function (res) {
      $scope.status = 'Error: ' + res.reason;
      // refetch items from server
      getItems();
    });
}

我们的函数postItem发生了很多事情:

在将HTTP请求发送到数据库之前,我们正在对用户界面进行乐观更新,因此用户可以立即看到更新,并且我们的应用程序感觉更加敏捷。 为此,我们将项目添加到合并范围内的其他项目。 Angular将为我们更新视图。

然后,我们在后台对商品进行POST请求,成功后,我们将从状态字段中删除所有(先前的)错误消息。

如果发生错误,我们正在向视图中写入错误消息。 CouchDB将告诉我们为什么在返回的JSON的reason属性中发生错误。 为了再次获得一致的视图,我们在收到错误后重新获取项目列表。

现在,我们可以在表单上添加指令ng-submit ,当提交表单时,它将在作用域上调用我们的函数:

<form class="form-inline" role="form" novalidate ng-submit="processForm()">

就是这样! Angular帮助我们保持了最新观点! 查看最新提交

添加验证

您可能已经注意到,我们可以在费用应用程序中放入各种值。 人们可以在价格中添加诸如foo之类的无效字符串,然后将其发送到服务器。 因此,让我们添加一些服务器端验证:CouchDB能够在更新时验证文档。 我们只需要在设计文档中添加一个带有函数的validate_doc_update字段即可。 如果数据无效,则此函数应引发异常。

该函数具有四个参数,如下所示:

validate_doc_update: function (newDoc, oldDoc, userCtx, secObj) {
  // ...
}

newDoc是将被创建或用于更新的文档。 还有参数oldDocuserCtxsecObj用于更复杂的验证,但是我们将仅使用newDoc进行验证:

如果您还没有使用已经提到的CouchApp,我真的建议您现在使用,因为它使处理更大的设计文档变得更加容易。 这是CouchApp的设计文档:

var ddoc = {
  _id: '_design/expenses',
  views: {},
  lists: {},
  shows: {},
  validate_doc_update: function (newDoc, oldDoc, userCtx, secObj) {
    if (newDoc._deleted === true) {
      return;
    }
    if (!newDoc.name) {
      throw({forbidden: 'Document must have an item name.'});
    }
    if (!newDoc.price) {
      throw({forbidden: 'Document must have a price.'});
    }
    if (!/\d+\.\d\d/.test(newDoc.price)) {
      throw({forbidden: 'Price must be a number and have two decimal places after a dot.'});
    }
  }
};

// _design/expenses/_view/byName
ddoc.views.byName = {
  map: function (doc) {
    emit(doc.name, doc.price);
  }
};

module.exports = ddoc;

在我们的验证中不能undefined字段nameprice 。 此外,我们正在使用正则表达式测试价格格式。 如果我们只想删除文档,则不需要任何验证。 我们正在使用以下命令更新设计文档:

couchapp push couchdb/views.js http://localhost:5984/expenses

现在,当我们尝试保存无效值时,应该看到错误,如下图所示:

错误处理

这是相关的提交

向前端添加验证

我们现在在服务器上进行了一些验证真是太棒了,但是如果我们不需要请求来验证我们的文档,那岂不是更棒了吗? 让我们使用Angular添加一些验证。

我们的两个输入都是必需的,因此它们都具有required属性。 您还记得设计文档的validate函数中的正则表达式吗? 指令ng-pattern使用正则表达式检查我们的输入:

<input
  class="form-control"
  ng-model="price"
  id="item-price"
  name="item-price"
  placeholder="Price"
  required
  ng-pattern="/\d+\.\d\d$/"/>

使用name-of-the-form.$invalid我们可以测试输入之一是否无效。 由于我们的表单具有名称-属性form我们将使用form.$invalid 。 我们可以将此值与ng-disabled类的指令结合使用,如果表单的值无效或缺失,它将禁用我们的提交按钮:

<button
  class="btn btn-primary pull-right"
  type="submit"
  ng-disabled="form.$invalid">Save</button>

而已! 仅用几行HTML,我们就获得了很好的验证。 查看最新的提交 ,包括测试。

结论

我们已经学习了如何使用CouchDB和Angular构建一个小型应用程序。 Angular和CouchDB为我们做了很多繁重的工作。 我们看了看:

  • CouchDB HTTP接口
  • CouchDB视图和验证
  • Angular的依赖注入
  • Angular的双向数据绑定
  • 角度指令
  • 在Angular中使用验证

Angular和CouchDB是很好的开发工具,它们在帮助我们正常工作的应用程序方面大有帮助。 希望您对CouchDB和Angular有一个初步的了解,并且如果您感兴趣的话,您仍然可以查看许多主题:

  • 在CouchDB本身上托管应用程序
  • 更新文件
  • 编写自己的指令
  • 复写
  • 在我们看来使用reduce函数
  • 测试Angular应用

From: https://www.sitepoint.com/tracking-expenses-couchdb-angular/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值