Meteor-JS-框架入门指南-全-

Meteor JS 框架入门指南(全)

原文:zh.annas-archive.org/md5/A6A998711E02B953FECB90E097CD1168

译者:飞龙

协议:CC BY-NC-SA 4.0

序言

我们生活在一个惊人的时代。医学、通信、物理以及所有其他科学领域的进步为我们提供了创建一些在短短一段时间前简直是无法创造的事物的机会。

然而,我们并不容易感到惊奇。我们已经开始期待奇妙的进步,因此曾经令人惊叹的事物变得……嗯……是预期的。真正让人感到惊奇的事物确实不多,确实,要找到让我们感到惊喜的东西。它重新点燃了我们所有人内心深藏的童年惊奇感,因为它曾被夺走。

好吧,准备好重新找回那份惊奇吧。一群决心创造一些奇妙事物的计算机科学家创建了一个名为 Meteor 的新 JavaScript 平台。你可能在想,“一个新的 JavaScript 平台?那没什么特别的。”如果 Meteor 只是那样,你说得对,但幸运的是,故事并没有就此结束。

Meteor 是一个反应式的、简单的、强大的应用平台,能够用几行代码生成复杂、健壮的网页应用。

在网页应用的背景下,它是尖端的。使用经过验证的、被证实的设计模式,Meteor 为构建网页应用的所有困难和单调部分做了所有工作。你可以专注于构建一个具有最新创新的所有最新创新,如反应式编程、模板、插件以及客户端缓存/同步。你不需要陷入写另一个数据库接口或学习一个新的模板引擎等浪费时间的活动。

最好的部分是,它很容易学习。令人惊讶地简单。你将会看到一个应用在你眼前焕然一新,当你回顾它创建所需的代码行数,并与传统开发方法进行比较时,你可能会发现自己说出“哇”或“他们是怎么做到的?”

这本书将带你领略 Meteor 的主要功能,并展示如何从零开始创建一个应用。到了本书末尾,你将创建一个可以工作的、有用的应用,并且你将彻底理解 Meteor 的不同之处。这可能听起来有些夸张,但如果你愿意接受创新和意外可以定义为惊人的观点,那么准备好感到惊奇吧!

本书涵盖内容

第一章,安装和设置,让你在几分钟内开始使用 Meteor,并展示你如何快速、轻松地构建一个完全功能性的、有用的应用。

第二章,响应式编程……它是有生命的!,教你关于响应式编程的一切,以及如何在 Meteor 中利用响应性创建令人惊叹、反应灵敏的应用。

第三章,为什么 Meteor 如此出色!,帮助你了解 Meteor 使用的设计模式,并展示这些强大模式的具体示例。

第四章,模板,深入介绍了 Meteor 模板,并教你如何使用模板为您的借阅图书馆应用程序奠定基础。

第五章,数据,Meteor 风格!,帮助你了解 Meteor 如何处理数据,使得企业级应用变得简单而健壮。它还帮助你快速有效地在应用程序中实现 Meteor 的数据处理。

第六章,应用程序和文件夹结构,展示了您可以对默认配置进行哪些更改,以使您的应用程序更加安全、可扩展和用户友好。

第七章,打包和部署,帮助你成为 Meteor 打包系统的专家,包括如何包含许多流行的第三方框架。学习如何将 Meteor 应用程序部署到您的开发、测试和生产环境。

本书所需材料

要运行书中的示例,需要以下软件:

本书面向人群

本书面向具有 HTML 和 JavaScript 一定了解的应用程序开发者、设计师或分析师,他们希望学习关于 Meteor 的知识,以及 JavaScript 社区内部向完全功能、健壮的网页应用的新运动。

如果您正在寻找了解如何以及在何时使用最新且最具创新性的网络技术的方法,以便将其应用于您的应用程序开发项目,本书适合您。

约定

在本书中,您将发现多种文本样式,以区分不同类型的信息。以下是一些这些样式的示例及其含义:

文本中的代码词汇如下所示:"我们已经通过使用categories模板创建了我们的分类。"

代码块如下所示:

<body>
  <div id="lendlib">
    <div id="categories-container">
      {{> categories}}
    </div>   
    <div id="list">
      {{> list}} 
    </div> 
  </div> 
</body> 

当我们希望将您的注意力吸引到代码块的特定部分时,相关行或项目以粗体显示:

<body>
 <div id="lendlib">
    <div id="categories-container">
      {{> categories}}
    </div>   
 <div id="list">
 {{> list}} 
 </div> 
 </div> 
</body>

任何命令行输入或输出如下所示:

> meteor remove autopublish

新术语重要词汇以粗体显示。例如,在屏幕上、菜单或对话框中看到的单词,在文本中会以这种方式出现:"在我们庆祝之前,请点击服装分类。"

注意

警告或重要说明以框的形式出现。

提示

技巧和窍门就像这样出现。

读者反馈

我们的读者的反馈总是受欢迎的。告诉我们你对这本书的看法——你喜欢什么或者可能不喜欢什么。读者反馈对我们来说非常重要,帮助我们开发出您真正能从中受益的标题。

发送一般性反馈,只需发送电子邮件至<feedback@packtpub.com>,并在消息的主题中提及书名。

如果您在某个话题上有专业知识,并且有兴趣撰写或贡献一本书,请查看我们在 www.packtpub.com/authors 上的作者指南。

客户支持

如今你已成为 Packt 书籍的骄傲拥有者,我们有很多事情可以帮助你充分利用你的购买。

下载示例代码

您可以从您在 www.PacktPub.com 的账户上下载您购买的所有 Packt 书籍的示例代码文件。如果您在其他地方购买了这本书,您可以访问 www.PacktPub.com/support 并注册,以便将文件直接通过电子邮件发送给您。

勘误表

虽然我们已经尽一切努力确保我们内容的准确性,但错误确实会发生。如果您发现我们的一本书中有一个错误——可能是文本或代码中的错误——我们将非常感谢您能向我们报告。通过这样做,您可以节省其他读者的挫折感,并帮助我们改进本书的后续版本。如果您发现任何勘误,请通过访问 www.packtpub.com/support,选择您的书籍,点击 errata submission form 链接,并输入您的勘误详情。一旦您的勘误得到验证,您的提交将被接受,勘误将被上传到我们的网站,或添加到该标题的错误部分现有的勘误列表中。您可以通过选择您的标题从 www.packtpub.com/support 查看任何现有的勘误。

盗版问题

互联网上版权材料的盗版是一个持续存在的问题,涵盖所有媒体。在 Packt,我们对保护我们的版权和许可证非常认真。如果您在互联网上以任何形式发现我们作品的非法副本,请立即提供给我们地址或网站名称,以便我们可以寻求补救措施。

如果您发现可疑的盗版材料,请通过 <copyright@packtpub.com> 与我们联系。

我们感激您在保护我们的作者和我们提供有价值内容的能力方面所提供的帮助。

问题

如果您在阅读书籍时遇到任何问题,可以通过 <questions@packtpub.com> 联系我们,我们会尽力解决。

第一章:设置和安装

在底层,Meteor 其实只是一堆文件和脚本,旨在让构建 Web 应用程序变得更容易。这是描述某样东西如此优雅的一种糟糕方式,但它帮助我们更好地理解我们在使用什么。

毕竟,米拉·库尼斯其实只是一堆组织包裹在骨骼周围,里面有一些重要的器官。我知道你现在可能恨我因为这个描述,但你知道我的意思。她很美。Meteor 也是。但我们不能就停留在那。如果我们想要在我们的 own 上重现这种美丽,我们必须了解到底发生了什么。

所以,文件和脚本……我们将带你了解如何在你的 Linux 或 Mac OS X 系统上正确安装 Meteor 包,然后看看这个文件和脚本的包如何运行。请注意,Windows 支持即将推出,但截至本文写作时,只有 Linux 和 Mac 版本可用。

在本章中,你将学习到:

  • 通过 curl 下载并安装 Meteor

  • 加载一个示例应用程序

  • 进行更改并观察 Meteor 的实际运行

使用 curl 安装

安装文件和脚本有几种方法。你可以手动下载和传输文件,你可以使用一个有很多“下一步”按钮的漂亮安装向导/包,或者你可以像真正的开发者那样做,使用命令行。这会让你变得更有男子气概。然而,我现在想想,这可能并不是一个非常想要的事情。好吧,没有头发;我撒谎了。但仍然,你想使用命令行,相信我。相信刚刚对你撒谎的那个人。

curl(如果你想弄得花哨一点,就是 cURL)是一个命令行工具,用于使用标准 URL 位置传输文件和运行脚本。你可能已经知道了,或者你可能不在乎。无论如何,我们描述了一下,现在我们继续使用它。

打开一个终端窗口或命令行,并输入以下内容:

$ curl https://install.meteor.com | /bin/sh

提示

下载示例代码

你可以从你账户中下载你购买的所有 Packt 书籍的示例代码文件。www.PacktPub.com有你购买这本书以外的所有书籍的示例代码文件。如果你在其他地方购买了这本书,你可以访问www.PacktPub.com/support并注册,以便文件直接发送到你的邮箱。

这将会在你的系统上安装 Meteor。curl是去获取脚本的命令。install.meteor.com是脚本的 URL/位置,/bin/sh当然是脚本解释器“Shell”的位置,它将会运行脚本。

一旦你运行了此脚本,假设你有互联网连接和适当的权限,你就会看到 Meteor 包的下载和安装:

使用 curl 安装

在前面的安装文本中我们要找的关键信息是 Meteor 的位置:

Installing Meteor to /usr/local/meteor

这个位置将根据您是在 Linux 还是 Mac OS X 上运行而有所不同,但它将 Meteor 置于一个可以从其他任何地方访问 Meteor 脚本的位置。这将在一分钟内变得很重要。现在,让我们看看 Meteor 安装完成后会得到什么友好消息:

Meteor installed! To get started fast:

 $ meteor create ~/my_cool_app
 $ cd ~/my_cool_app
 $ meteor

Or see the docs at:

 docs.meteor.com

太好了!您已成功安装 Meteor,并且您正在创建您的第一个 Meteor 网络应用程序的路上!

提示

你应该收藏docs.meteor.com 作为您前进过程中宝贵的参考资料。

加载示例应用程序

Meteor 的了不起的人们包括几个示例应用程序,您可以快速创建并玩耍,帮助您更好地了解 Meteor 能做什么。

对于我们将要构建的应用程序来说,todos示例是最接近的匹配,所以我们将基于那个示例进行构建。我们将再次使用命令行,所以如果你还开着它,那真是太棒了!如果没有,打开一个终端窗口,然后按照以下步骤操作。

选择您的文件位置

为了以后能记住它们的位置,我们将把这本书的所有文件放在~/Documents/Meteor文件夹中。我们需要创建那个文件夹:

$ mkdir ~/Documents/Meteor

现在,我们希望处于那个目录中:

$ cd ~/Documents/Meteor

加载示例应用程序

现在,我们可以使用 Meteor create命令和--example参数来创建todos示例应用程序的本地副本:

$ meteor create –-example todos

与 Meteor 安装本身一样,create命令脚本也有一个友好的成功消息:

todos: created.
To run your new app:
 cd todos
 meteor

多么方便,甚至还有下一步要做什么的说明!让我们按照我们忠实的好命令行朋友的指示去做吧。

启动示例应用程序

要启动一个 Meteor 应用程序,我们需要处于应用程序目录本身。这是因为 Meteor 正在寻找运行应用程序所需的启动文件、HTML 和 JavaScript。所有这些都在应用程序文件夹中,所以让我们去那里:

$ cd todos

这让我们进入了~/Documents/Meteor/todos文件夹,我们准备运行应用程序:

$ meteor

是的,就是它。Meteor 为我们处理所有事情,阅读所有文件和脚本,并设置 HTTP 监听器:

[[[[[ ~/Documents/Meteor/todos ]]]]]

Running on: http://localhost:3000/

现在,我们可以使用我们得到的 URL(http://localhost:3000/),并在网页浏览器中查看示例应用程序。

预览应用程序

打开您最喜欢的网页浏览器(我们将使用 Chrome,但任何现代更新过的浏览器都可以)并导航到http://localhost:3000/

您应该看到以下屏幕,其中已经添加了一些待办事项列表:

预览应用程序

如果你想的话,你可以去探索一下应用程序。给列表添加一个新项目,更改列表,添加一个新标签,或者标记项目为完成。随意操作,朋友!如果我们将来所做的更改与你如果在做很多更改的情况下屏幕上显示的完全一致,那是不可能的,但你会很顺利地跟上的。

救命!我改动太多了!

你是否害怕变化,希望你的屏幕看起来与我们的示例屏幕一模一样?没问题,只需从一个干净的实例开始。

  1. 在命令行:

    Ctrl + C
    
    
  2. 这将停止运行的应用程序。现在向上移动一个目录:

    $ cd ..
    
    
  3. 删除todos应用程序:

    $ rm –R todos
    
    
  4. 再次创建 todos 示例应用程序:

    $ meteor create --example todos
    
    
  5. 切换到新目录,启动 Meteor,一切就绪:

    $ cd todos
    $ meteor
    
    

进行代码更改

好了,我们的应用程序现在在浏览器中运行。现在我们想看看当我们做一些代码更改时会发生什么。

Meteor 最好的特性之一是响应式编程和热代码推送。

以下内容来自docs.meteor.com/#reactivity

注意

Meteor 采用响应式编程的概念。这意味着你可以用简单的命令式风格编写代码,当你的代码依赖的数据发生变化时,结果将自动重新计算。

更简单地说,这意味着你对 HTML、JavaScript 或数据库所做的任何更改都会自动被采纳并传播。

你不必重新启动应用程序,甚至不必刷新你的浏览器。所有更改都实时地被整合,应用程序被动地接受这些更改。

让我们来看一个例子。

从 todos 更改为 items

随着我们对 Meteor 的深入了解,我们希望建立一个可以工作的应用程序:一个有用且复杂到足以让我们体验 Meteor 的所有主要特性的应用程序。我们将构建一个借阅图书馆,我们可以跟踪我们拥有什么物品(例如,广告男人第一季),将这些物品组织成类别(例如,DVD),并跟踪我们借给这些物品的人。

为了看到这方面的开始,让我们将todos列表更改为items列表,并将list一词更改为category,因为这个词听起来更酷。

首先,确保应用程序正在运行。你可以通过打开一个浏览器窗口,指向http://localhost:3000/来进行此操作。如果应用程序正在运行,你将看到你的todos应用程序。如果你的应用程序没有运行,请确保按照启动示例应用程序部分中给出的步骤操作。

现在,我们需要打开并编辑todos.html文件。用你最喜欢的文本/代码编辑器打开~/Documents/Meteor/todos/client/todos.html

  1. head部分更改title

    <head>
      <title>Items</title>
    </head>
    
  2. 接着保存文件,然后在浏览器中查看。页面将自动刷新,你会看到标题从Todos更改为Items从 todos 更改为 items

    现在,标题将显示单词Items

    从 todos 更改为 items

这就是 Meteor 在起作用!它正在监控任何文件的变化,当它看到一个文件发生变化时,它告诉你的浏览器有变化,并应该刷新自己以获取最新版本。

继续前进,我们将从头开始构建一个应用程序,因此我们不想对这个示例应用程序做太多更改。然而,我们仍然希望至少清理掉其他可见的todolist引用。

  1. 回到您的文本编辑器,对大约第 20 行的<h3>标签进行以下更改:

    <template name="lists">
      <h3>Item Categories</h3>
    

    保存此更改,您将在浏览器中看到更改反映。左侧标题栏原本显示以下文本:

    从 todos 更改为 items

    现在它将更改为以下内容:

    从 todos 更改为 items

  2. 我们还需要处理一个区域,我们已经成功将我们的todos应用程序转换为items应用程序。

    如果您注意到,在分类列表的底部,当前打开的盒子写着新列表

    从 todos 更改为 items

    我们需要将其改为新分类。在第 39 行进行以下代码更改:

    <div id="createList">
      <input type="text" id="new-list" placeholder="New category" />
    </div>
    
  3. 保存您的更改,并检查您的工作:从 todos 更改为 items

摘要

太成功了!在本章中,你已经成功安装了 Meteor 框架,加载了一个示例应用程序,并对该应用程序进行了更改,熟悉了文件更改和 Meteor 的反应性质。你现在可以开始构建你自己的 Meteor 应用程序,并了解更多使用 Meteor 开发带来的优雅特性和优势。

第二章.响应式编程…它是有生命的!

正如您在第一章中所学到的,设置和安装,Meteor 采用响应式编程模型。这意味着您的客户端/浏览器不仅关心显示数据,还关心数据的变化,这样它就可以“反应”这些变化。这些寻找数据变化的数据区域称为响应式上下文

我们将开始我们的借贷图书馆应用程序,为未来的章节打下基础,并使用 Meteor 内置的响应式上下文来跟踪和传播我们应用程序的变化,以便所有监听的客户端都能接收到。

在本章中,您将学习到:

  • 创建您的第一个真实应用程序

  • 使用响应式编程来跟踪和自动更新变化

  • 从多个浏览器窗口探索和测试您数据的变化

创建借贷图书馆

这个世界上有两种人。那些记得他们借给谁东西的人,和那些买了很多东西两次的人。如果你和你的 UPS 送货司机很熟,这个应用程序就是为你准备的!

使用 Meteor,我们将建立一个借贷图书馆。我们将跟踪我们所有的东西,以及我们借给了谁,这样下次我们记不起我们把线性压缩扳手放在哪里时,我们只需查找我们最后借给了谁,然后去向他们要回来。

而且当同一个朋友说,“你确定你借给我了吗?”我们可以回答,“是的,史蒂夫,我确定我借给了你!我看到你正在享受我的慷慨借出的线性压缩扳手带来的数字有线电视,为什么不自己去找它,这样我也可以在家享受数字有线电视的好处呢?!”

好吧,好吧,也许史蒂夫也忘记了。也许他是个骗子,他把你的扳手卖了来支付他的炸 Twinkies®习惯。无论如何,你都有自己的自定义 Meteor 应用程序,可以证明你并没有发疯。如果他确实为了油炸嘉年华食品而卖了它,至少你可以让他和他的存货一起分享,然后你可以在他家看比赛。

创建基本应用程序

我们首先要做的就是创建基本应用程序,然后我们可以根据需要进行扩展。

  1. 首先,导航到您的应用程序文件夹。这可以随便放在哪里,但如前所述,我们将使用~/Documents/Meteor作为根文件夹:

    $ cd ~/Documents/Meteor
    
    
  2. 现在我们为我们的借贷图书馆应用程序创建基本文件夹结构:

    $ meteor create LendLib
    
    
  3. 像往常一样,我们将获得关于如何启动应用程序的说明。让我们先试试看,以确保一切都创建得正确:

    $ cd LendLib
    $ meteor
    
    

    这将导航到借贷图书馆文件夹~/Documents/Meteor/LendLib并运行应用程序。

  4. 打开一个浏览器,导航到http://localhost:3000/。你应该看到以下屏幕:创建基本应用程序

  5. 你好世界是远远不够的,所以让我们把它改为借阅图书馆。在你的最喜欢的编辑器中打开~/Documents/Meteor/LendLib/LendLib.html。在顶部(第 9 行左右),你会看到负责我们问候的模板 HTML 代码片段。大胆把Hello World改为Lending Library

    <template name="hello">
      <h1>Lending Library</h1>
      {{greeting}}
      <input type="button" value="Click" />
    </template>
    
  6. 保存那个更改,页面将刷新:创建基本应用

    欢迎信息不在 HTML 文件中,然而。如果你注意到了,它在一个名为 greeting 的模板函数中找到:

    {{greeting}}
    
  7. 我们也来改一下。打开~/Documents/Meteor/LendLib/LendLib.js,将问候模板函数更改如下:

    if (Meteor.isClient) {
      Template.hello.greeting = function () {
      return "my list.";
      };
    
  8. 保存更改,你的页面将更新:创建基本应用

创建一个集合

好,你刚刚静态文件做了一些小改动,但我们真正想看到的是些动态的、响应式的编程,还有实时的 HTML!

我们需要附加一个数据源:一些可以跟踪我们项目的东西。通常,这确实是一个相当的过程,但 Meteor 使之变得简单,支持 Minimongo(MongoDB 的轻量级版本)内置。

提示

要了解更多关于 NoSQL 数据库(特别是 MongoDB,Meteor 内部默认使用的数据库)的信息,你可以访问以下网站:

更多信息请访问en.wikipedia.org/wiki/NoSQL

更多信息请访问www.mongodb.org/

更多信息请访问www.packtpub.com/books/all?keys=mongodb

让我们创建我们的集合。在LendLib.js中,我们想添加以下作为第一行,然后保存更改:

var lists = new Meteor.Collection("Lists");

if (Meteor.isClient) {
…

这将在 MongoDB 中创建一个新的集合。由于它在LendLib.js文件中的任何其他内容之前,所以集合可供客户端和服务器查看。如我们所见,它是持久的,一旦在其中输入值,任何访问页面的客户端都可以检索它们。

要查看这个持久对象,我们需要使用我们网页的控制台。

浏览器控制台的乐趣

浏览器控制台是大多数现代浏览器默认提供的调试工具,或者通过插件作为附加组件。

提示

要深入了解如何在 Chrome 中使用控制台,请查看developer.chrome.com/extensions/tut_debugging.html

  1. 由于我们使用的是 Chrome,控制台默认可用。在一个指向http://localhost:3000/的浏览器窗口中,输入快捷键组合[command] + [option] + i,或者你可以在页面的任何地方右键点击并选择检查元素浏览器控制台的乐趣

    这将打开我们的调试工具。我们现在想要进入控制台。

  2. 点击调试菜单栏最右边的控制台图标:浏览器控制台的乐趣

    你现在将有一个闪烁的光标,你准备检查我们新创建的集合!

  3. 在控制台中输入以下命令并按Enter

    > lists
    
    

    你应该得到一个返回的对象,说 Meteor 集合:

    浏览器控制台的乐趣

添加一些数据

这意味着我们的更改已被接受,我们有一个新的持久集合!它是空的,但让我们做点什么来改变这一点:

  1. 在浏览器控制台中输入以下命令以创建几个示例类别:

    > lists.insert({Category:"DVDs", items: {Name:"Mission Impossible",Owner:"me",LentTo:"Alice"}});
    > lists.insert({Category:"Tools", items: {Name:"Linear Compression Wrench",Owner:"me",LentTo: "STEVE"}});
    

    每个命令执行后,你将获得一个 GUID(类似于f98c3355-18ce-47b0-82cc-142696322a06),这是 Meteor 用来告诉你项目已正确保存的方式。我们作为天生的怀疑论者,将要检查这一过程。

  2. 输入以下命令:

    > lists.findOne({Category: "DVDs"});
    

    你应该得到一个对象,旁边有一个可扩展的图标。

  3. 点击那个图标来展开,你应该有以下内容:添加一些数据

    我们可以通过输入命令lists.findOne({Category:"Tools"})来同样检查我们的工具集合,但我们不需要这样做。这次我们将相信 Meteor 已经正确地输入了。然而,我们确实希望检查对象是否持久存在。

    刷新网页。你的控制台将清空,但我们输入的类别已经保存在持久的 Meteor 集合中,因此我们可以再次检查它们是否还在。

  4. 在控制台中输入以下命令:

    > lists.find({}).count();
    
    

    这个命令查找lists集合中的所有记录,并给我们一个总数。如果一切按计划进行,你应该得到一个2的计数。

我们正在前进!我们已经创建了两个类别,每个类别中有一个项目。我们还验证了lists集合正在从会话到会话中保存。现在,让我们看看如何在我们的页面上显示这个。

在 HTML 中显示集合

我们现在将看到我们在初始化项目时创建的 HTML 页面中的集合栩栩如生。这个页面将使用模板,它们是响应式的,允许我们的集合发生变化时,页面无需刷新即可立即更新。这种类型的响应式编程,页面 DOM 可以无需刷新即可立即更新,称为Live HTML

小贴士

要了解更多关于 Live HTML 的信息,请查阅以下网址的 Meteor 文档:

docs.meteor.com/#livehtml

  1. ~/Documents/Meteor/LendLib/LendLib.html仍然打开的情况下,找到body标签,并添加一个新的模板声明:

    <body>
      {{> hello}}
     <div id="categories-container">
     {{> categories}}
     </div> 
    </body>
    

    这将创建一个新的div,其内容由名为categoriestemplate partial填充。

  2. 现在,在页面的最底部,让我们添加类别template partial的骨架:

    <template name="categories">
    </template>
    
    

    这不会改变页面的外观,但我们现在有一个template partial,我们可以列出我们的类别。

  3. 让我们放入我们的节标题:

    <template name="categories">
     <div class="title">my stuff</div>
    </template>
    
  4. 现在让我们把我们的类别放进去:

    <template name="categories">
      <div class="title">my stuff</div>
     <div id="categories">
    
     </div>
    </template>
    

    这样在div中创建了类别,我们可以遍历并列出所有类别。如果我们只有一个记录要处理,代码将如下所示:

    <div class="category">
     {{Category}}
    </div>
    
    
  5. 但是我们需要将其包装在一个循环中(在这个例子中,一个#each语句),这样我们才能获取所有类别:

    <template name="categories">
      <div class="title">my stuff</div>
      <div id="categories">
     {{#each lists}}
     <div class="category">
     {{Category}}
     </div>
     {{/each}}
      </div>
    </template>
    

    注意我们正在告诉模板“对于lists集合中的每个记录”使用我们的{{#each lists}}命令,然后,“显示类别”使用{{Category}}

  6. 保存这些更改,然后查看网页:在 HTML 中显示集合

    看起来并没有太大不同。是的,我们有我们的头部(我的东西),但是我们刚刚创建的模板的类别在哪里?

    为了让类别显示出来,我们还需要完成一步。目前,我们刚刚创建的模板没有指向任何东西。换句话说,我们有一个 lists 集合,我们有一个模板,但我们没有连接它们的底层 JavaScript 函数。让我们处理一下那部分。

    ~/Documents/Meteor/LendLib/LendLib.js中我们可以看到一些Template函数:

    Template.hello.greeting = function () {...
    
    ...
    
    Template.hello.events = { ...
    

    这些代码块正在将 JavaScript 函数和对象连接到 HTML hellotemplate。Meteor 内置的Template对象使这成为可能,我们将遵循相同的模式来连接我们的 categoriestemplate

  7. 我们想要向任何监听的客户端声明,categories 模板有一个lists集合。我们通过在Template.hello.events = {...}代码块下方输入以下代码来实现:

    Template.hello.events = {
    ...
    };
    
    Template.categories.lists = function () {
    };
    
    

    提示

    Template 声明必须位于if (Meteor.isClient) {...}代码块内,以便客户端可以获取更改,而服务器会忽略它。

  8. 现在我们已经为所有模板声明了lists集合,我们可以让函数返回来自Meteor.Collection查询的结果。我们使用find()命令来实现:

    Template.categories.lists = function () {
     return lists.find({}, {sort: {Category: 1}});
    };
    

    这段代码将找到lists集合中的每个记录,并按Category(名称)对结果进行排序。保存这些更改,你现在将看到一个填充了类别的列表:

    在 HTML 中显示集合

清理

我们正在快速接近一个可以工作的应用程序,我们希望它看起来超级光滑和干净。让我们对我们的代码进行一些清理,并添加一些 CSS 以提高可读性:

  1. 我们不再需要问候语。让我们去掉它。从LendLib.html中删除以下高亮显示的行并保存页面:

    <body>
     {{> hello}}
      <div id="categories">
        {{> categories}}
      </div>
    </body>
    
    <template name="hello">
     <h1>Lending Library</h1>
     {{greeting}}
     <input type="button" value="Click" />
    </template>
    <template name="categories">
    

    我们现在想保留LendLib.js中的 Template.hello 声明,作为参考。我们现在注释掉它们,稍后当它们不再需要时删除它们:

    /*
    
    Template.hello.greeting = function () {
    ...
    };
    
    Template.hello.events = {
    ...
    };
    
    */
    
    
  2. 现在,让我们添加 Twitter Bootstrap 框架,它让我们轻松拥有大量样式:

    1. 打开终端窗口,在/LendLib/中创建一个client文件夹:

      $ mkdir ~/Documents/Meteor/LendLib/client
      
      
    2. 访问twitter.github.com/bootstrap/assets/bootstrap.zip下载最新的 Bootstrap 框架,然后将其解压到~/Documents/Meteor/LendLib/client文件夹中。

      因为 Meteor 会读取并使用应用程序文件夹中的每一个文件,我们希望消除这些冗余的文件。我们不必太担心效率问题,但有些事情确实令人羞愧,留下这么多无用的代码就是其中之一,与享受《暮光之城》系列电影没什么两样。

    3. 导航到 bootstrap 文件夹:

      $ cd ~/Documents/Meteor/LendLib/client/bootstrap
      
      
    4. 删除不需要的文件:

      $ rm js/bootstrap.js
      $ rm css/bootstrap.css
      $ rm css/bootstrap-responsive.css
      
      

      提示

      如果你熟悉 Bootstrap,你可以直接复制imagesmin.jsmin.css文件,而不是按照前面的说明操作。

    经过这些更改后,你的 UI 应该非常干净简洁:

    清理

  3. 让我们快速使其更加醒目和可读。在LendLib.html中,让我们将头部标签从div更改为h2

    <template name="categories">
    <h2 class="title">my stuff</h2>
    
  4. 让我们把分类变成一个漂亮的按钮组:

    <div id="categories" class="btn-group">
    {{#each lists}}
    <div class="category btn btn-inverse">
    {{Category}}
    </div>
       {{/each}}
    

    这给了我们一个独特、干净的页面:

    清理

创建一个反应

在我们创建了基本模板和集合,并且 Meteor 将我们的lists集合放入了反应式上下文之后,我们现在可以继续观察反应式编程模型在实际工作中的表现。

导航到我们的借阅图书馆页面http://localhost:3000/,并打开浏览器控制台窗口。

在控制台中输入以下命令:

> lists.insert({Category:"Fraggles"});

你会立刻看到页面更新。但注意,这次页面没有完全刷新!那是因为在幕后,Meteor 正在跟踪我们的反应式上下文(在这个例子中是lists集合)的变化,并在变化发生后立即更新template

让我们再做些改动。再次输入相同的Fraggles命令:

> lists.insert({Category:"Fraggles"});

与之前一样,一个新的Fraggles按钮立刻出现:

创建一个反应

但我们现在的 Fraggles 分类太多了。确实有很多 Fraggles,但除非你是个奇怪的收藏家,否则你不需要两个分类。所以我们去掉它们:

> lists.remove({Category:"Fraggles"})

这个命令查找任何Category = "Fraggles"的记录并将它们删除。

为了更好地管理我们的收藏品,添加一个收藏品集合条目可能更好,所以我们来这样做:

> lists.insert({Category:"Collectibles"})

正如你所看到的,更改是即时的,无需页面刷新。

多个客户端

好的事物应该分享。Meteor 理解这一点,正如我们即将亲自看到的那样,反应式编程模型允许我们在多个客户端之间实时共享更新。

保持你的 Chrome 网页打开在http://localhost:3000/,然后打开一个新的浏览器标签页,导航到同一页面。

提示

如果你想更高级一点,可以用多个浏览器(Firefox、Opera 或 Safari)进行这个实验——每个会话都是实时的并且具有反应性!

你现在打开了两个客户端,它们模拟了不同的人、在不同的地点、使用不同的计算机打开应用程序。Meteor 的反应式模型允许你对所有客户端一视同仁,其中一个客户端所做的更改将会传播到所有其他客户端。

在关注新的第二个浏览器的同时,在浏览器#1 的控制台中输入以下命令:

> lists.insert({Category:"Vinyl Records"})

你会注意到更改传播到了两个浏览器,再次没有刷新页面:

多个客户端

随意添加任何额外的集合,删除或重命名等。稍作实验,注意这些更改如何能够立即对所有监听的客户端生效。Meteor 遵循一个非常强大的范式,在下一章中,我们将能够确切地看到为什么这是 web 应用程序开发中的一个如此重要和具有颠覆性的变革。

总结

在本章中,你已经成功地为你的新 Meteor 应用创建了框架。你亲自见证了新的项目是如何迅速被创建的,而且你仅仅用几行代码就创建了一些主要的数据库和模板功能。你亲眼看到了实时 HTML 和反应式编程的实际应用,现在你准备更深入地了解 Meteor 引擎。你已经征服了冰山之巅,我的朋友。休息一下,喝杯冰镇饮料,为更深入的 Meteor 精彩做好准备!

第三章:为什么 Meteor 如此出色!

Meteor 是一种具有颠覆性(以一种好的方式!)的技术。它使一种新类型的网络应用程序成为可能,这种应用程序采用了模型-视图-视图模型MVVM)设计模式。

这一章解释了网络应用程序是如何改变的,为什么这很重要,以及 Meteor 是如何通过 MVVM 特别地使现代网络应用程序成为可能的。

到本章末尾,你将学到:

  • 现代网络应用程序是什么样的

  • MVVM 意味着什么,以及它有何不同

  • 如何使用 Meteor 的 MVVM 创建现代网络应用程序

  • 在 Meteor 中使用模板——开始使用 MVVM

现代网络应用程序

我们的世界正在改变。

随着显示、计算和存储能力的不断进步,几年前还不可能实现的事情现在不仅成为可能,而且对于优秀的应用程序的成功至关重要。特别是网络领域经历了显著的变化。

网络应用程序的起源(客户端/服务器)

从一开始,网络服务器和客户端就模仿了傻瓜终端的计算方式,其中服务器具有比客户端多得多的处理能力,对数据执行操作(例如将记录写入数据库、进行数学计算、文本搜索等),将数据转换为可读格式(例如将数据库记录转换为 HTML 等),然后将结果服务于客户端,由用户显示使用。

换句话说,服务器做所有的工作,而客户端更多的是作为一个显示器,或者说是傻瓜终端。这种设计模式的名称是……等一下……叫做客户端/服务器设计模式:

网络应用程序的起源(客户端/服务器)

这种设计模式源自 20 世纪 60 年代和 70 年代的傻瓜终端和大中型计算机,正是它促成了我们所知的网络的诞生,并且一直是我们思考互联网时所想到的设计模式。

机器的崛起(MVC)

在网络出现之前(以及自那以后),桌面能够运行如电子表格或文字处理程序等应用程序,而无需与服务器进行通信。这类应用程序能够在其强大的桌面环境中完成所需的一切。

在 20 世纪 90 年代初,桌面计算机变得更快更好。越来越多地配置了高性能的计算机。同时,网络也开始兴起。人们开始认为将高性能桌面应用程序(也就是胖应用)与网络客户端/服务器应用程序(也就是瘦应用)相结合,可以产生最好的效果。这种类型的应用程序——与傻瓜终端相反——被称为智能应用

创建了许多面向商业的智能应用,但最简单的例子可以在计算机游戏中找到。大型多人在线游戏MMOs)、第一人称射击游戏和实时战略游戏都是智能应用,在这些应用中,信息(数据模型)通过服务器在机器之间传递。在这种情况下,客户端做的不仅仅是显示信息。它执行大部分处理(或控制)并将数据转换为需要显示的内容(视图)。

这种设计模式很简单,但非常有效。它被称为模型-视图-控制器MVC)模式。

机器的崛起(MVC)

模型拥有所有的数据。在智能应用的上下文中,模型是由服务器提供的。客户端从服务器请求模型。一旦客户端获得模型,它就在这些数据上执行操作/逻辑,然后准备将其显示在屏幕上。这个应用程序的部分(与服务器通信、修改数据模型以及准备数据显示的数据)被称为控制器。控制器向视图发送命令,视图显示信息,并在屏幕上发生某些事件(例如按钮点击)时向控制器报告。控制器接收那些反馈,执行逻辑,并更新模型。如此循环。

由于网络浏览器被设计成“愚蠢的客户端”,使用浏览器作为智能应用的想法是不可能的。相反,智能应用是建立在诸如微软.NET、Java 或 Macromedia(现 Adobe)Flash 之类的框架上。只要安装了框架,你就可以访问网页来下载/运行智能应用。

有时你可以在浏览器内运行应用程序,有时你可以在下载之前运行它,但无论如何,你都在运行一种新类型的网络应用程序,在这种应用程序中,应用程序可以与服务器通信并共享处理工作负载。

浏览器成长了(MVVM)

从 2000 年代初开始,MVC 模式出现了一个新的变化。开发者开始意识到,对于连接/企业级的“智能应用”,实际上有一个嵌套的 MVC 模式。

服务器(控制器)通过使用业务对象对数据库信息(模型)执行业务逻辑,然后将该信息传递给客户端应用程序(一个“视图”)。

客户端从服务器接收这些信息,并将其视为自己的个人“模型”。然后客户端将作为一个适当的控制器,执行逻辑,并将信息发送给视图以在屏幕上显示。

所以,对于服务器 MVC 的“视图”是第二个 MVC 的“模型”。

浏览器成长了(MVVM)

然后我想到,“为什么要止步于两个?” 没有理由说一个应用不能有多个嵌套的 MVC,每个视图都成为下一个 MVC 的模型。实际上,在客户端方面,这样做的确有一个很好的理由。

将实际显示逻辑(如“这个提交按钮放在这里”和“文本区域值已更改”)与客户端对象逻辑(如“用户可以提交这个记录”和“电话号码已更改”)分离,使得大部分代码可以被重用。对象逻辑可以移植到另一个应用程序中,您所做的只是更改显示逻辑,以将相同的模型和控制器代码扩展到不同的应用程序或设备。

从 2004-2005 年起,这个想法被马丁·福勒(Martin Fowler)和微软(Microsoft)针对智能应用进行了改进和修改(称为展示模型),称为模型-视图-视图模型(Model View View-Model)。虽然严格来说并不是嵌套 MVC 的同一件事,但 MVVM 设计模式将嵌套 MVC 的概念应用于前端应用程序。

浏览器成长(MVVM)

随着浏览器技术(HTML 和 JavaScript)的成熟,创建直接在 HTML 网页内使用 MVVM 设计模式的智能应用变得可能。这种模式使得直接从浏览器运行完整尺寸的应用程序成为可能。不再需要下载多个框架或单独的应用程序。现在,您可以从访问一个 URL 获得与以前从购买包装产品获得相同的功能。

一个大型的 Meteor 出现了!

Meteor 将 MVVM 模式推向了新的高度。通过应用 handlebars.js(或其他模板库)的模板化,并利用即时更新,它真正使得网页应用程序能够像一个完整的、健壮的智能应用程序一样行动和表现。

让我们通过一些概念来了解 Meteor 是如何做到这一点的,然后我们开始将这个应用到我们的 Lending Library 应用程序中。

缓存和同步数据(模型)

Meteor 支持一种在客户端和服务器上相同的缓存和同步数据模型。

缓存和同步数据(模型)

当客户端注意到数据模型的更改时,它首先在本地缓存这些更改,然后尝试与服务器同步。同时,它正在监听来自服务器的更改。这使得客户端能够拥有数据模型的本地副本,因此它可以快速地将任何更改的结果发送到屏幕,而无需等待服务器响应。

此外,您会注意到这是 MVVM 设计模式的开始,嵌套在一个 nested MVC 中。换句话说,服务器发布数据更改,并将其数据更改视为自身 MVC 模式中的“视图”。客户端订阅这些更改,并将其更改视为 MVVM 模式中的“模型”。

缓存和同步数据(模型)

这个的一个代码示例在 Meteor 中非常简单(尽管如果您愿意,您可以使其更复杂,从而使其更具控制性):

var lists = new Meteor.Collection("lists");

这一行代码的作用是声明存在一个lists数据模型。客户端和服务器都会有它的版本,但它们对待自己的版本方式不同。客户端将订阅服务器宣布的变化,并相应地更新其模型。服务器将发布变化,并监听来自客户端的变化请求,并根据这些请求更新它的模型(它的主副本)。

哇。一行代码就能做到这么多!当然,还有很多我们没有提到,但这超出了本章节的范围,所以我们继续吧。

提示

为了更好地理解 Meteor 的数据同步,请参阅 Meteor 文档中“发布和订阅”部分的发布和订阅docs.meteor.com/#publishandsubscribe

模板化的 HTML(视图)

Meteor 客户端通过使用模板来渲染 HTML。

HTML 中的模板也称为视图数据绑定。简单来说,视图数据绑定是如果数据变化,会以不同方式显示的一块共享数据。

HTML 代码有一个占位符。根据变量的值,将在该占位符中放置不同的 HTML 代码。如果这个变量的值发生变化,占位符中的代码也会随之变化,从而产生不同的视图。

让我们来看一个非常简单的数据绑定,这个你实际上不需要 Meteor 也能做到,来阐明这个观点。

LendLib.html中,你会看到一个 HTML(Handlebar)模板表达式:

<div id="categories-container">
 {{> categories}}
</div>

这个表达式是一个 HTML 模板的占位符,下面就是它:

<template name="categories">
<h2 class="title">my stuff</h2>...

所以,{{> categories}}基本上是在说“在这里放categories模板中的任何东西。”具有相应名称的 HTML 模板正在提供这些内容。

如果你想看看数据变化会如何改变显示效果,将h2标签改为h4标签,并保存更改:

<template name="categories">
<h4 class="title">my stuff</h4>...

你会在浏览器中看到效果(“我的东西”变得微小)。这是一个模板——或者说是视图数据绑定——在起作用!将h4改回h2并保存更改。除非你喜欢这个更改。这里没有判断...好吧,也许有一点判断。它又丑又小,很难阅读。说真的,你应该改回去,否则有人看到会嘲笑你的!

好吧,现在我们知道什么是视图数据绑定,让我们来看看 Meteor 是如何使用它们的。

LendLib.html中的 categories 模板内,你还会找到更多的 Handlebar 模板:

<template name="categories">
  <h4 class="title">my stuff</h4>
  <div id="categories" class="btn-group">
    {{#each lists}}
      <div class="category btn btn-inverse">
        {{Category}}
      </div>
    {{/each}}
  </div>
</template>

第一个 Handlebar 表达式是一对的一部分,是一个for-each语句。{{#each lists}}告诉解释器执行其下方的动作(在这个例子中,创建一个新的div)对于lists集合中的每个项目。lists是数据的一部分。{{#each lists}}是占位符。

现在,在#each lists表达式中,还有一个 Handlebar 表达式。

{{Category}}

由于这位于#each表达式内部,Categorylists的隐含属性。也就是说{{Category}}等同于说this.Category,其中thisfor each循环中的当前项目。因此,占位符表示“在这里添加this.Category的值。”

现在,如果我们查看LendLib.js,我们将看到模板背后的值。

Template.categories.lists = function () {
  return lists.find(...

在这里,Meteor 声明了一个名为lists的模板变量,该变量位于名为categories的模板内。这个变量碰巧是一个函数。这个函数返回lists集合中的所有数据,我们之前定义了这个集合。记得这个命令吗?

var lists = new Meteor.Collection("lists");

那个lists集合是由声明的Template.categories.lists返回的,因此当lists集合发生变化时,变量也会得到更新,模板的占位符也会相应地改变。

让我们实际操作一下。在指向http://localhost:3000的网页上,打开浏览器控制台并输入以下行:

> lists.insert({Category:"Games"});

这将更新lists数据集合(模型)。模板将看到这个变化,并更新 HTML 代码/占位符。for each循环将额外运行一次,为lists中的新条目,然后你会看到以下屏幕:

模板化的 HTML(视图)

关于 MVVM 模式,HTML 模板代码是客户端视图的一部分。任何数据的变化都会自动反映在浏览器中。

Meteor 的客户端代码(视图模型)

如前一部分所述,LendLib.js包含了模板变量,它将客户端模型与 HTML 页面连接起来,这是客户端的视图。在LendLib.js内部,作为对视图或模型变化的反应的任何逻辑都是视图模型的一部分。

视图模型负责跟踪模型的变化并以一种视图可以拾取变化的方式呈现这些变化。它还负责监听来自视图的变化。

在这里,变化并不意味着按钮点击或文本被输入。相反,我们指的是模板值的改变。声明的模板是视图模型,或者说视图的模型

这意味着客户端控制器拥有其模型(来自服务器的数据)并且知道如何处理这个模型,视图拥有其模型(一个模板)并且知道如何显示该模型。

让我们创建一些模板

现在我们将看到 MVVM 设计模式的实际例子,同时对我们的借阅图书馆进行操作。通过控制台添加类别是一个有趣的练习,但它不是长期的解决方案。让我们设法让我们可以在页面上进行此类操作。

打开LendLib.html,在{{#each lists}}表达式之前添加一个新按钮。

<div id="categories" class="btn-group">
<div class="category btn btn-inverse" id="btnNewCat">&plus;</div>
{{#each lists}}

这将向页面添加一个加号按钮。

让我们创建一些模板

现在,如果我们点击它,我们想要将那个按钮换成文本字段。所以让我们使用 MVVM 模式构建这个功能,并使其基于模板中的一个变量的值。

添加以下代码行:

<div id="categories" class="btn-group">
  {{#if new_cat}}
 {{else}}
      <div class="category btn btn-inverse" id="btnNewCat">&plus;</div>
  {{/if}} 
{{#each lists}} 

第一行{{#if new_cat}}检查new_cattrue还是false。如果是false{{else}}部分触发,这意味着我们还没有表示我们想要添加一个新的类别,所以我们应该显示带有加号的按钮。

在这种情况下,由于我们还没有定义它,new_cat将变为false,所以显示不会改变。现在让我们添加 HTML 代码,如果我们想要添加一个新的类别:

<div id="categories" class="btn-group">
  {{#if new_cat}}
    <div class="category">
      <input type="text" id="add-category" value="" />
 </div>
    {{else}}
      <div class="category btn btn-inverse" id="btnNewCat">&plus;</div>
  {{/if}} 
{{#each lists}} 

我们添加了一个输入字段,当new_cattrue时显示。除非它是,否则输入字段不会显示,所以现在它是隐藏的。那么我们如何使new_cat等于true呢?

如果您还没有保存更改,请保存您的更改,并打开LendingLib.js。首先,我们在列表模板声明下方声明一个Session变量。

Template.categories.lists = function () {
  return lists.find({}, {sort: {Category: 1}});
};
// We are declaring the 'adding_category' flag
Session.set('adding_category', false);

现在,我们声明新的模板变量new_cat,它将是一个返回adding_category值的函数:

// We are declaring the 'adding_category' flag
Session.set('adding_category', false);
// This returns true if adding_category has been assigned a value //of true
Template.categories.new_cat = function () {
 return Session.equals('adding_category',true);
};

保存这些更改,你会发现什么都没有变化。Ta-daaa!

实际上,这正是它应该的样子,因为我们还没有做任何改变adding_category值的事情。现在我们来做这件事。

首先,我们将声明我们的点击事件,它将改变我们的Session变量的值。

Template.categories.new_cat = function () {
  return Session.equals('adding_category',true);
};
Template.categories.events({
 'click #btnNewCat': function (e, t) {Session.set('adding_category', true);Meteor.flush();
 focusText(t.find("#add-category"));
 }
});

让我们看看下一行:

Template.categories.events({

这条线声明将在类别模板中找到事件。

现在让我们看看下一行:

'click #btnNewCat': function (e, t) {

这条线告诉我们,我们在寻找 HTML 元素上的点击事件,其id="btnNewCat"(我们在LendingLib.html上已经创建了它)。

Session.set('adding_category', true);
Meteor.flush();
focusText(t.find("#add-category"));

我们设置Session变量adding_category = true,我们刷新 DOM(清除任何不正常的内容),然后使用表达式id="add-category"将焦点设置到输入框。

还有一件事要做,那就是快速添加助手函数focusText()。在if (Meteor.isClient)函数的闭合标签之前,添加以下代码:

/////Generic Helper Functions/////
//this function puts our cursor where it needs to be.
function focusText(i) {
 i.focus();
 i.select();
};

} //------closing bracket for if(Meteor.isClient){}

现在当你保存更改,并点击加号![让我们创建一些模板]按钮时,你会看到以下输入框:

让我们创建一些模板

太棒了!

它仍然没有用,但我们想要暂停一下,反思一下刚才发生了什么。我们在 HTML 页面中创建了一个条件模板,它将根据一个变量的值显示输入框或加号按钮。

这个变量属于视图模型。也就是说,如果我们通过点击事件改变变量的值,那么视图会自动更新。在 Meteor 应用程序中,我们刚刚完成了一个 MVVM 模式!

为了真正说明这一点,我们添加了对lists集合的更改(也是视图模型的一部分,记得吗?),并找出一个在完成后隐藏input字段的方法。

首先,我们需要为keyup事件添加一个监听器。换句话说,我们希望监听用户在框中输入内容并按下回车时。当发生这种情况时,我们希望能够根据用户输入的内容添加一个类别。首先,让我们声明事件处理程序。在#btnNewCatclick事件之后,让我们添加另一个事件处理程序:

focusText(t.find("#add-category"));
},
'keyup #add-category': function (e,t){
 if (e.which === 13)
 {
 var catVal = String(e.target.value || "");
 if (catVal)
 {
 lists.insert({Category:catVal});Session.set('adding_category', false);
 }
 }
}
});

我们在点击函数的末尾添加一个",",然后添加了keyup事件处理程序。

if (e.which === 13)

这一行检查我们是否按下了Enter/回车键。

var catVal = String(e.target.value || "");
if (catVal)

这检查输入字段是否有什么值。

lists.insert({Category:catVal});

如果这样做,我们希望在lists集合中添加一个条目。

Session.set('adding_category', false);

然后我们希望隐藏输入框,这可以通过简单地修改adding_category的值来实现。

还有一件事要做,我们就完成了。如果我们点击input框以外的区域,我们希望隐藏它,并恢复加号按钮。到现在为止,我们已经知道如何在 MVVM 模式内部实现这一点,所以让我们添加一个快速函数来更改adding_category的值。在keyup事件处理程序之后再添加一个逗号,并插入以下事件处理程序:

        Session.set('adding_category', false);
      }
    }
  },
  'focusout #add-category': function(e,t){
 Session.set('adding_category',false); 
 }
});

保存你的更改,让我们来看一下实际效果!在你的网页浏览器中,访问http://localhost:3000,点击加号符号——添加单词衣服并按下回车

你的屏幕现在应该类似于以下内容:

让我们创建一些模板

如果你想添加更多类别,请随意添加。此外,尝试点击加号按钮,输入一些内容,然后点击输入框以外的区域。

总结

在本章中,你学习了网页应用程序的历史,并了解了我们从传统的客户端/服务器模型是如何发展到完整的 MVVM 设计模式的。你已经看到了 Meteor 如何使用模板和同步数据使管理变得非常容易,为我们提供了视图、视图逻辑和数据之间的清晰分离。最后,你向借阅图书馆添加了更多内容,添加了一个用于添加类别的按钮,而且你都是通过修改视图模型而不是直接编辑 HTML 来完成的。在下一章中,我们将真正开始工作,添加各种模板和逻辑,让我们的借阅图书馆焕发生机!

第四章:模板

到目前为止,我们只是对模板有了一个简单的了解,现在我们准备深入其中,使用 MVVM 设计模式创建一个工作应用程序。本章将深入介绍模板系统,并展示如何实现显示逻辑、向模型添加设计考虑(创建视图模型)以及处理数据流。

在本章中,您将完成以下任务:

  • 完成借阅图书馆的核心功能

  • 创建多个模板和模板逻辑

  • 在数据模型中添加、删除和更新条目

  • 观察响应性在实际工作中的应用,并在您的应用程序中使用它

一个新的 HTML 模板

我们已经通过使用categories模板创建了我们的分类。现在,我们想要将其提升到下一个级别,显示我们可能想让(除了 STEVE 之外的)人们借阅的实际项目。所以,当我们点击一个分类时,我们应该得到一个项目列表

让我们使用这个术语。我们需要一个地方来显示一个list。所以,让我们稍微修改一下~/Documents/Meteor/LendLib/LendLib.html代码的开头部分:

<body>
 <div id="lendlib">
    <div id="categories-container">
      {{> categories}}
    </div>   
 <div id="list">
 {{> list}} 
 </div> 
 </div> 
</body>

通过添加这段代码,我们做了两件事:

  1. 我们将id="categories-container"div元素包裹在名为lendlibdiv内。这是出于样式考虑,这样我们的list就能与categories模板大致对齐。

  2. 我们在它下面添加了一个divid="list",并添加了一个对新模板的调用:{{> list}}。这是我们用于itemslist的模板/占位符,我们将在接下来的部分中看到。

现在,我们已经创建了一个非常易于维护的结构,文档中有了明确的界限。我们知道categories(分类)将要放在哪里,我们也知道items(项目)的list(列表)将要放在哪里。

现在让我们来看看列表模板本身。虽然不简单,但仍然不难。在LendLib.html的最后,我们categories模板的闭合</template>标签下方,放置以下代码:

<template name="list"> 
 <ul id="lending_list"> 
 {{#each items}} 
 <li class="lending_item alert"> 
 <button type="button" class="close delete_item" id="{{Name}}">×</button>

 {{Name}}

 {{#if lendee_editing}} 
 <input type="text" id="edit_lendee" class="span2 pull-right" value=""/> 
 {{else}} 
 <div class="lendee pull-right label {{LendClass}}"> {{Lendee}}</div> 
 {{/if}} 
 </li> 
 {{/each}} 
 {{#if list_selected}}
 <li class="alert-success" id="btnAddItem">&plus; 
 {{#if list_adding}}
 <input class="span4" id="item_to_add" size="32" type="text"> 
 {{/if}} 
 </li> 
 {{/if}} 
 </ul> 
</template>

让我们一步一步地来看,这样我们就明白每一行代码的作用:

<template name="list"> 
 <ul id="lending_list"> 
 {{#each items}}
...

在这里,我们声明了一个 HTML<template>,名为"list",以匹配我们在主体部分调用的列表模板。我们创建了一个无序列表<ul>,并给它一个id,这样我们以后如果需要的话就可以引用它。

然后我们开始一个模板化的each语句。这次,我们要遍历items。我们还没有创建 Meteor 的items模板,但我们应该很快就会做到。

 <li class="lending_item alert"> 
 <button type="button" class="close delete_item" id="{{Name}}">×</button> 

 {{Name}}

现在,在each语句下,我们创建了一个<li>元素,并给它赋予了两个类名。lending_item这个类名是为了让我们能在视图模型(Meteor 模板代码)中引用它。alert这个类名是为了 Bootstrap,这样它就能漂亮地显示出来。

接下来,我们创建一个 button,如果我们选择删除项目,我们可以使用它。注意,我们给它一个 ID id="{{Name}}"。这将从 items View-Model 中读取,如果我们想从我们的 items 集合中删除 item,这将使我们的工作在未来变得容易得多。这个 button 上还有两个类名。close 是为 Bootstrap 添加的,delete_item 是添加的,这样我们可以在 View-Model 中引用它。

现在,就在那个下面,我们又有另一个模板占位符 {{Name}}。这样我们可以在显示元素内部使用项目的标题(例如,在 DVD 项目中,标题可能是 "Mission Impossible")。我们很快就会看到它的实际应用。

现在我们开始了一系列条件语句。第一个条件语句与我们需要编辑谁在借阅我们的项目,或者 借阅者 有关:

 {{#if lendee_editing}} 
 <input type="text" id="edit_lendee" class="span2 pull-right" value=""/> 
 {{else}} 
 <div class="lendee pull-right label {{LendClass}}"> {{Lendee}}</div> 
 {{/if}} 
 </li>
 {{/each}}

我们首先使用一个 if 语句来检查这个 item 的当前模式是否为 lendee_editing。也就是说,如果我们想编辑借阅者,我们就会处于 "lendee editing" 模式,因此(在我们的 JavaScript 文件中)Template.list.lendee_editing 会返回 true。如果是这种情况,我们需要一个文本框,因此包含了 <input> 元素,以及它的相关 id

另一种情况——这也是默认情况——我们只想显示借阅者是谁,如果有的话。如果没有,我们可能想改变颜色或其他什么,但仍然希望它显示出来。所以,我们创建一个带有 Bootstrap 样式的 label,其形式为一个 <div> 元素。

在类声明的末尾,我们看到一个模板变量:...{{LendClass}}"。这个类添加是样式上的。它将告诉我们的 CSS 模板是显示为 "free"(有人可以借阅它)还是 "lent out"(借出)。如果是绿色,它是免费的,如果是红色,有人已经借走了。代表颜色的 CSS 类名将在 LendLib.js 中由 item.LendClass 属性确定,我们稍后会创建它。

然后我们来看一下 div 内的值:{{Lendee}}。这同样也是 LendLib.js 中的一个属性,作为 item.Lendee 属性,它会显示借阅者的名字,或者如果没有人借阅的话,显示 "free"。

然后我们有结束的 </li> 标签,以及 each{{/each}} 结束。

现在,我们有了第二个 if 语句,而这个实际上是嵌套的 if。这个 if 语句在 each 语句之外,所以它不是针对特定项目的。这个 if 语句显示一个带 + 符号的浅绿色条,或者一个表单形式的 <input> 元素文本框,以便我们可以向我们的列表中添加项目:

 {{#if list_selected}} 
 <li class="alert-success" id="btnAddItem">&plus; 
 {{#if list_adding}} 
 <input class="span4" id="item_to_add" size="32" type="text"> 
 {{/if}} 
 </li> 
 {{/if}} 
 </ul> 
</template>

所以我们看到第一个 if 语句,它取决于我们是否甚至显示任何列表项。如果我们正在显示,这意味着我们选择了一个列表。或者说,我们处于 list_selected 模式。跟踪这个是 View-Model 的工作的一部分,所以 Template.list.list_selected 可以在 LendLib.js 中找到。

然后我们创建一个<li>元素,用 Bootstrap 的alert-success类将其样式设置为绿色,并添加+号。

接下来是我们的嵌套(第二个)if。这个是检查我们是否在添加项列表。如果是,我们处于list_adding模式,因此我们将以<input>元素的形式显示文本框。如果不是,我们只会保留那个漂亮的浅绿色盒子,里面只有+号。

最后,我们结束嵌套的if,我们的</li>,我们的父级if,我们的</ul>,和我们的</template>

粘合在一起

视图模型(MVVM)或控制器(MVC)或呈现器(MVP)被认为是 MV*应用程序模型的粘合剂。这是因为它将所有视图项,比如按钮或文本框,粘合到了模型上。

这个解释很复杂,是吧?好吧,你尝试着为它做更好的解释。它确实填补了空白,并将模型和视图粘合在一起。这个术语是别人发明的,不是我们,所以让我们抛开批判性观点,继续吧?

在本节中,我们将逐步讲解需要在~/Documents/Meteor/LendLib/LendLib.js中进行的所有更改,以将模板和数据模型粘合在一起。

我们的 items 视图模型

在我们在第二章创建的数据模型中,响应式编程…它是有生命的!,我们在创建几个list时添加了一些示例items。如果你还记得的话,我们是这样通过浏览器控制台操作的:

> lists.insert({Category:"DVDs", items: [{Name:"Mission Impossible",Owner:"me",LentTo:"Alice"}]});

你会注意到那里有一个层级结构。lists集合中的每一个list都有一个items对象,它是一个数组:

Items: [...]

我们需要将这个items数组呈现到我们的 HTML 模板中,但我们还需要一些额外的属性,以便视图知道如何处理它。具体来说,我们需要做的是:

  • 返回借阅者姓名,如果没有借阅者则返回"free"(item.Lendee

  • 根据项目是否已借出(item.LendClass),返回 CSS 类(红色或绿色)。

所以,我们将从当前选定的列表中获取items集合,添加LendeeLendClass属性,并使模板可用。

打开~/Documents/Meteor/LendLib/LendLib.js

function focusText(...的闭合}花括号后立即添加以下代码:

};//<-----This is the end tag for focusText() -----

Template.list.items = function () {
 if (Session.equals('current_list',null)) return null; 
 else 
 { 
 var cats = lists.findOne({_id:Session.get('current_list')}); 
 if (cats&&cats.items) 
 { 
 for(var i = 0; i<cats.items.length;i++) { 
 var d = cats.items[i];  d.Lendee = d.LentTo ? d.LentTo : "free"; d.LendClass = d.LentTo ? 
 "label-important" : "label-success"; 
 }
 return cats.items; 
 }
 } 
};

我们将逐步讲解这个问题。

Template.list.items = function () {
if (Session.equals('current_list',null)) return null; 

在这里,我们声明了Template.list.items函数,并检查是否选择了list。如果选择了listSession变量current_list中将有一个值。如果没有,就没有返回任何东西的必要,所以我们直接返回 null。

提示

这就是视图模型在工作。它正在读取给定类别的内容,并根据用户是否选择了列表,将当前 UI 状态融入其中。这是粘合剂在工作。

如果选中了某个项目,我们首先需要找到类别。我们称之为cats,因为这个名字更短,尽管它不是严格意义上的最佳命名约定。但我们在乎吗?我们这样做是为了好玩,而且cats很棒!

else 
{ 
  var cats = lists.findOne({_id:Session.get('current_list')}); 

我们正在使用 MongoDB 命令findOne(),并将current_list会话参数作为_id在选择器/查询中传递。如果有什么被选中,我们将得到一个单一的类别/列表。让我们确保我们确实这样做,并且我们还能得到items

如果没有返回任何内容,或者该类别中没有items,我们真的需要确定LendeeLendClass吗?所以让我们创建一个if语句,和一个在if内的for语句,只有在我们有值得迭代的元素时才会执行:

    if (cats&&cats.items)
    { 
      for(var i = 0; i<cats.items.length;i++) {
        var d = cats.items[i]; 
  d.Lendee = d.LentTo ? d.LentTo : "free"; 
  d.LendClass = d.LentTo ? "label-important" : "label-success";
      }; 
      return cats.items; 
    }; 
  }; 
};

首先,我们检查catscats.items是否未定义/为空。

接下来,我们遍历items中的所有值(如果你还记得,items是一个数组)。为了更容易,我们声明变量d = cats.item[i]

现在我们添加了Lendee属性,检查项目是否借给了任何人,LentTo属性。如果没有(如果LentTo不存在),我们将分配字符串"free"

同样,如果LentTo存在,我们将红色 Bootstrap 标签类label-important作为LendClass。如果项目没有借出,我们将使用绿色 Bootstrap 类label-success

最后,在我们的新LendeeLendClass属性分配之后,我们将返回cats.items。我们没有将这些属性保存到我们的模型中。那是因为它们不是模型的一部分。它们由视图使用,因此我们只通过 View-Model 模板使它们可用。

附加视图状态

现在我们需要为所有不同的视图状态声明模板。也就是说,我们需要向 View-Model/session 添加属性,这样我们才能知道我们在看什么,我们在编辑什么,以及应该隐藏/显示什么。具体来说,我们需要在四种情况下访问状态值:

  • 我们在看列表吗?(list_selected

  • 我们在看哪个列表?(list_status

  • 我们在向列表中添加项目吗?(list_adding

  • 我们在更新借阅人吗?(lendee_editing

LendLib.js中我们新创建的 items 模板/函数下方添加以下代码:

      return cats.items; 
    }; 
  }; 
}; // <---- ending bracket for Template.list.items function ----

Template.list.list_selected = function() { 
 return ((Session.get('current_list')!=null) && (!Session.equals('current_list',null))); 
}; 

Template.categories.list_status = function(){ 
 if (Session.equals('current_list',this._id)) 
 return ""; 
 else 
 return " btn-inverse"; 
}; 

Template.list.list_adding = function(){ 
 return (Session.equals('list_adding',true)); 
}; 

Template.list.lendee_editing = function(){ 
 return (Session.equals('lendee_input',this.Name)); 
};

让我们逐一分析这些模板函数。

Template.list.list_selected = function() { 
return ((Session.get('current_list')!=null) && (!Session.equals('current_list',null))); 
}

Session变量current_list可以是undefinednull。如果是undefinedSession.equals('current_list'),null)将返回true。所以我们需要检查这两种情况,很不幸。

Template.categories.list_status = function(){ 
if (Session.equals('current_list',this._id)) 
return ""; 
else 
return "btn-inverse"; 
}; 

list_status用于告诉类别按钮是否应显示为选中状态。最容易的方法是通过一个 CSS 类。Bootstrap 使用btn-inverse显示黑白文本,但这是我们默认的按钮外观。因此,因为我们使用了完全相反的颜色方案,我们将使用 Bootstrap 的普通黑白外观来显示选中的类别。

换句话说,对于current_list,我们将返回""(默认按钮的外观和感觉)。对于所有其他列表/类别,我们将返回"btn-inverse",以改变 CSS 样式。

你可能想知道this._id。在这个实例中,this指的是 MongoDB 记录(技术上来说是文档游标),而._id是那个“记录”的唯一 MongoDB 标识符。这称为上下文,在这种情况下使用 HTML 模板时,上下文是从哪里调用模板的列表/类别元素。

Template.list.list_adding = function(){ 
  return (Session.equals('list_adding',true)); 
}

这个真的很直接。如果Session变量list_addingtrue,我们在添加到列表。如果不是,我们就不添加。

Template.list.lendee_editing = function(){ 
  return (Session.equals('lendee_input',this.Name)); 
}

为了检查我们是否应该进入借阅者编辑模式,我们将检查Session变量lendee_input,看它是否有值,以及这个值是否是我们刚刚点击物品的Name。再次说明,这是隐含的上下文。这次,不是列表,而是项目。我们怎么知道这个?因为函数是从哪里调用的。还记得 HTML 吗?

<li class="lending_item alert"> 
  <button type="button" class="close delete_item" id="{{Name}}">×</button> 
{{Name}} 

{{#if lendee_editing}}

注意我们如何在if语句中使用lendee_editing,正好在我们使用{{Name}}之后。这显示了上下文。LendLib.js中的this.NameLendLib.html中的{{Name}}具有相同的上下文。换句话说,this.Name引用了与{{Name}}相同的属性。

既然我们在 HTML 模板中,我们需要对 HTML 类别模板进行一次更改。我们等到现在,这样更改才有意义。当你做出以下代码更改时,你会看到模板{{list_status}}{{_id}}的使用,以及为什么this._id的上下文突然变得有意义。

LendLib.html中找到以下行(应该在第 27 行左右):

{{#each lists}}
  <div class="category btn btn-inverse">
    {{Category}} 
</div> 

{{/each}}

然后将其更改为如下代码片段的样子:

{{#each lists}}
  <div class="category btn {{list_status}}" id="{{_id}}">
    {{Category}}
  </div> 

{{/each}}

添加事件

我们现在将连接所有事件。不是在 HTML(我们的视图)中这样做,而是在模板声明(我们的视图模型)中这样做。

第一个发生在Template.categories.events声明中,因为我们需要添加一个事件,来改变Session变量current_list。如果你记得的话,current_list帮助我们知道是否有选中的列表(list_selected)以及那个列表是什么(list_status)。

LendLib.js中,在Template.categories.events函数的'focusout #add-category'事件声明和最后的});括号之间,添加以下代码:

        Session.set('adding_category', false);
      } 
    } 
  }, 
  'focusout #add-category': function(e,t){
    Session.set('adding_category',false);
  }, 
 'click .category': selectCategory 
});

小贴士

不要忘记在'focusout... function(e,t){...}代码块后面加上逗号(,)。

这为具有 CSS 类"category"的每个按钮添加了点击事件,并调用selectCategory()函数。我们现在就声明那个函数。

focusText()函数之后,在Template.list.items声明之前,添加以下代码:

function selectCategory(e,t){ Session.set('current_list',this._id);
}  

Template.list.items = function () {
...

是的,你本可以把这个放在任何地方。是的,你本可以简单地在一个点击事件声明中嵌入一个通用函数。那么为什么放在这里呢?因为这样能让我们的代码更具可读性,而且我们需要一个部分来处理所有将要需要的添加/删除/更新调用,所以正好放在这里。

是的,很简单。它只是用this._id更新了Session变量current_list。这里this的上下文是类别/列表,因此_id是记录的 MongoDB 生成的 ID。

好的,现在我们已经处理了所有类别的 events,让我们来处理项目的 events。在if (Meteor.is_client) {...代码块的最后一行,在闭合的}括号内,放入以下代码:

Template.list.lendee_editing = function(){ 
  ... 
} 	 

Template.list.events({ 
 'click #btnAddItem': function (e,t){
 Session.set('list_adding',true); 
 Meteor.flush(); 
 focusText(t.find("#item_to_add")); 
 }, 
 'keyup #item_to_add': function (e,t){ 
 if (e.which === 13) 
 { 
 addItem(Session.get('current_list'),e.target.value); 
 Session.set('list_adding',false); 
 } 
 }, 
 'focusout #item_to_add': function(e,t){ 
 Session.set('list_adding',false); 
 }, 
 'click .delete_item': function(e,t){ 
 removeItem(Session.get('current_list'),e.target.id); 
 }, 
 'click .lendee' : function(e,t){
 Session.set('lendee_input',this.Name); 
 Meteor.flush();
 focusText(t.find("#edit_lendee"),this.LentTo); 
 }, 
 'keyup #edit_lendee': function (e,t){ 
 if (e.which === 13) 
 { 
 updateLendee(Session.get('current_list'),this.Name,
 e.target.value); 
 Session.set('lendee_input',null); 
 } 
 if (e.which === 27) 
 {
 Session.set('lendee_input',null); 
 } 
 } 
});

}//<----this is the closing bracket for if(Meteor.is_client) ----

六个事件!看起来比实际更吓人。像往常一样,让我们一步步分解。

Template.list.events({ 
  'click #btnAddItem': function (e,t){
    Session.set('list_adding',true); 
    Meteor.flush(); 
    focusText(t.find("#item_to_add")); 
  },

我们声明Template.lists.events,并枚举我们的事件。第一个是为添加一个项目。添加项目的按钮有趣的是,命名为btnAddItem,所以我们只需要添加声明,然后写我们的函数。

我们将list_adding设置为true。由于我们使用Session.set(),这种变化会通过我们的模板级联。这是一个反应,或者说反应式编程在起作用。我们还调用Meteor.flush()以确保清理 UI,然后,作为对用户的礼貌,我们将文本框(名为item_to_add)聚焦,这样我们的亲爱的用户就可以开始打字了。

  'keyup #item_to_add': function (e,t){
    if (e.which === 13) 	   
    {
      addItem(Session.get('current_list'),e.target.value);
      Session.set('list_adding',false);
    }
  },

提示

你可以在 Meteor 文档中了解更多关于Meteor.flush()做的事情,地址是docs.meteor.com/#meteor_flush

下一个事件是基于我们的item_to_add文本框的keyup事件。如果我们按下Enter或 Return 键(e.which === 13),我们将调用addItem()函数来更新数据模型,然后我们将隐藏文本框。我们怎么做呢?当然设置list_adding = false。再次强调,通过Session.set()来做会使这种变化通过我们的模板级联。

你可能还漏掉了一件别的事情:记得我们曾在第二章, 响应式编程…它是有生命的!, 使用控制台手动添加一个类别/列表吗?变化立刻反映在 HTML DOM 中。这里也是同样的道理。当addItem()更新数据模型时,这个变化会触发Template.list.items的模板刷新。

  'focusout #item_to_add': function(e,t){
    Session.set('list_adding',false);
  }, 

触发器创造了一个情境,这样如果我们改变主意不想添加一个item,我们只需要点击离开它。如果文本框item_to_add触发了focusout事件,我们将设置Session变量list_addingfalse,模板将会级联。

  'click .delete_item': function(e,t){ 
    removeItem(Session.get('current_list'),e.target.id); 
  },

记得我们曾在 HTML列表模板中创建了那个小小的![Adding events]按钮吗?这个按钮属于 CSS 类delete_item,当用户点击它时,我们将调用removeItem()函数,传递current_list和被点击的 HTML 元素中的id,碰巧是Name项(我们在LendingLib.html的第 39 行添加了id="{{Name}}")。

  'click .lendee' : function(e,t){
    Session.set('lendee_input',this.Name); 
    Meteor.flush();
    focusText(t.find("#edit_lendee"),this.LentTo); 	
  }, 

现在,我们关注lendee部分/按钮。如果你还记得,我们在LendingLib.html中设置了一个带有 CSS 类lendee<div>元素。我们现在声明,无论何时点击其中一个<div>元素,我们都将执行与想要向list添加item非常相似的操作:

  1. 将控制文本框可见性的Session变量设置为truelendee_input)。

  2. 刷新 UI(Meteor.flush())。

  3. 设置文本框的焦点(focusText())。

还有一个事件处理程序:

  'keyup #edit_lendee': function (e,t){
    if (e.which === 13)
    {
      updateLendee(Session.get('current_list'),this.Name,
      e.target.value); 
      Session.set('lendee_input',null); 
    }
    if (e.which === 27) 
    { 		   
      Session.set('lendee_input',null); 	  
    }
  }
});

具有id="edit_lendee"的文本框有两个keyup条件。

如果我们按下Enter或 Return(e.which === 13),我们将使用updateLendee()更新数据模型上的LentTo属性。然后通过将lendee_input设置为null来隐藏文本框。记住,更新数据模型,设置一个Session变量将导致模板刷新(又是反应式编程)。

如果我们决定不喜欢这些更改,我们将按下Esc键(e.which === 27),在这种情况下,我们将lendee_input设置为null,让反应式编程隐藏文本框。

模型更新

我们还剩下两件事要做。我们需要照顾好让我们的应用变得漂亮(不过还不是现在),我们需要创建刚刚在events部分提到的addItem()removeItem()updateLendee()函数。

所以让我们开始工作!在LendingLib.js中的帮助器部分(正好在Template.lists.items上方,第 68 行左右),让我们添加我们的addItem()函数:

function addItem(list_id,item_name){
 if (!item_name&&!list_id)
 return; 
 lists.update({_id:list_id}, 
 {$addToSet:{items:{Name:item_name}}}); 
}

Template.list.items = function () {
...

addItem()函数有两个参数:list_iditem_name

list_id用于update语句的选择器(查询)部分,而item_name包含要添加到新itemitem.Name属性的值。

但首先,我们需要检查是否有item_namelist_id的值。如果没有,我们就会return

现在,我们将对lists集合调用 MongoDB 的update()函数。{_id:list_id}是选择器。我们告诉 MongoDB 找到具有_id = list_id的记录。然后我们告诉 MongoDB 我们将要执行哪种更新。在这种情况下,我们将使用$addToSet,它将追加到一个数组中。我们要追加什么?

{items:...表示我们正在更新items[]数组。{Name:item_name}是我们要添加的,它是Name属性。这相当于说item.Name = item_name

一旦我们添加了这个功能,正如你现在可能已经猜到的那样,模板将自动更新,因为数据模型发生了变化。停下来思考一下。在六行代码中,我们执行了一个更新查询将更改传播到我们的 UI。六行!这难道不是很神奇吗?

接下来我们处理removeItem()

function removeItem(list_id,item_name){ 
if (!item_name&&!list_id) 
return; 
lists.update({_id:list_id}, 
{$pull:{items:{Name:item_name}}}); 
}

Template.list.items = function () {
...

哇,这看起来与addItem()函数非常相似。事实上,我们确实使用了相同的update()函数,只是这次使用了不同的操作。这次我们将使用$pull,从items[]数组中删除一个元素,其中Name == item_name。再一次,我们只需要六行代码。数据模型和 UI 都将自动更新。

现在我们来处理updateLendee(),这有点复杂,但只是因为 Meteor 实际上使用的是 minimongo,这是 MongoDB 的一个简化版,且不支持游标变量。这意味着,我们不是使用类似于items.$.Name的东西,其中$是游标位置,而是需要遍历items[]数组,更新值,然后调用一个更新操作,用我们的更新后的数组替换整个items[]数组。下面是我们在LendLib.js中是这样做的:

function updateLendee(list_id,item_name,lendee_name){
 var l = lists.findOne({"_id":list_id , 
 "items.Name":item_name}); 
 if (l&&l.items) 
 { 
 for (var i = 0; i<l.items.length; i++)
 { 
 if (l.items[i].Name === item_name)
 {
 l.items[i].LentTo = lendee_name;
 } 
 }
 lists.update({"_id":list_id},{$set:{"items":l.items}}); 
 } 
};

Template.list.items = function () {
...

我们获取了list_iditem_namelendee_name,这样我们就可以找到正确的记录(我们用list_iditem_name来做到这一点),然后用lendee_name中的值更新LentTo属性。

为了节省一些打字,我们声明了一个变量l,使用了 MongoDB 的findOne()函数。这个函数需要一个由两部分组成的选择器语句:_id:list_id"items.Name":item_name。这个选择器基本上是在说:“找到一个记录,其中_id == list_iditems[]数组中有一个记录,其中Name == item_name。”

如果l有一个值,并且l也有items,我们将进入我们的for循环。在这里,我们特别检查哪个数组元素具有Name == item_name。如果我们找到一个,我们将LentTo属性设置为lendee_name

一旦我们完成了for循环,我们将调用 MongoDB 的update()函数,并使用$set操作用新的{"items":l.items}替换旧的items[]数组。自动更新再次发生,我们的 UI(视图)和数据文档(模型)再次保持同步。

样式更新

现在,你可以运行这个应用程序。它将是一场视觉灾难,因为我们还没有设置任何 CSS 样式,但让我们快速搞定这件事。我们都会盯着一场车祸看的,承认吧!确保你的应用程序正在运行(在控制台输入> meteor)并导航到http://localhost:3000

样式更新

点击DVDs,你应该会看到Mission Impossible的其中一个条目:

样式更新

这并没有那么糟糕,除了有些宽度和高度问题,但那是因为我们使用了 Bootstrap。让我们继续解决剩下的 UI 问题。

首先,我们在LendLib.js中有一个最后的更改。更改focusText()函数(位于大约第 55 行):

//this function puts our cursor where it needs to be. 
function focusText(i) {
  i.focus();
  i.select(); 
};

现在应该是这样的:

//this function puts our cursor where it needs to be. 
function focusText(i,val) {
  i.focus(); 
 i.value = val ? val : ""; 
  i.select(); 
};

这个更改只是使得当我们去编辑一个已经有值的东西(比如借阅人)时,这个值会传输到文本框中。这使得用户更容易看到当前借阅人是谁。val ? val : ""这个条件语句是必要的,因为如果val没有被传递或者是 null,"undefined"会被放进文本框中。

我们现在想要更新所有其他视觉特性的 CSS。我们在这里不会详细介绍 CSS,因为有更好的方法来处理它,而且我们不是 CSS 专家。所以只需将以下内容添加到~/Documents/Meteor/LendLib/LendLib.css中,并保存更改:

/* CSS declarations go here */ 
#lendlib{ 
 width:535px; 
 margin:0 auto; 
} 
#categorybuttons{ 
 width:100%; 
} 
#lending_list{
 list-style:none; 
 margin:0; 
 padding:0; 
} 
#lending_list li{ 
 list-style:none; 
 margin:5px; 
} 
#lending_list li.lending_item:hover{ 
 background-color:#fc6; 
 color:#630; 
} 
#lending_item{
 vertical-align:middle; 
} 
#add_item{
 padding-left:5px; 
} 
#btnAddItem{
 padding:10px;
 border-radius:5px; 
} 
#btnAddItem:hover{
 background-color:#B7F099; 
} 
#edit_lendee{
 padding-top:0; 
 margin-top:-2px; 
} 

如果你保持了http://localhost:3000打开,你的浏览器将自动刷新。如果你没有,重新打开它(确保 Meteor 正在运行)并观察结果:

样式更新

不要忘记测试你所有的新的功能!添加新的类别,将项目添加到列表中,更改借阅人,删除借阅人,删除项目等等,只是感受一下模型更新有多快和多干净。

现在,打开两个浏览器,都指向http://localhost:3000。你会注意到,在一个浏览器中做的更改在另一个浏览器中也会反映出来!就像之前一样,Meteor 正在处理客户端和服务器之间的数据模型同步,任何在客户端上的更改都会通过服务器传播到其他客户端。

样式更新

一旦你创建了足够多的项目和列表,就可以继续下一章。

总结

在本章中,你已经完成了你借贷图书馆应用的模板、事件和数据模型部分。你已经创建了语句来添加、删除和更新你的记录,并实现了 UI 状态的改变。你亲自看到了响应式编程是如何工作的,并对你所使用的上下文有了坚实的基础。你现在能够从零开始创建一个应用,使用 Meteor 的核心功能快速开发并拥有健壮的功能。在下一章中,你将更深入地了解 Meteor 的数据缓存和同步方法,并加强你的应用。

第五章:数据,Meteor 风格!

我们已经接近完成借阅图书馆应用程序的开发,而无需过多担心我们的数据是如何被存储的。Meteor 的数据缓存和同步方法故意构建,以使应用程序这部分尽可能简单,这样你就可以集中精力编写伟大的程序,而不是花很多时间处理数据库连接、查询和缓存。

然而,我们确实想回顾一下方法,并确保我们有一个坚实的基础,了解 Meteor 如何处理数据,这样我们就可以进行一些常见的优化,并且更快地构建我们的应用程序。

在本章中,你将学习以下主题:

  • MongoDB 和文档导向存储

  • 广播变化——Meteor 如何使你的网络应用具有反应性

  • 配置发布者——如何优化和保护你的数据

文档导向存储

Meteor 使用 MongoDB 的一个版本(minimongo)来存储来自你模型的所有数据。它能够使用任何其他 NoSQL/文档导向数据库,但 MongoDB 随 Meteor 安装包默认提供。这个特点使得你的程序更简单、更容易编写,并且非常适合快速、轻量级的数据存储。

那么,为什么不使用关系型数据库呢?

传统上,数据是使用关系模型进行存储的。关系模型,以及它所有的相关规则、关系、逻辑和语法,是现代计算的一个不可或缺且极其有价值的部分。关系型数据库那种严格结构,对每个记录、关系和关联的精确要求,为我们提供了快速搜索、可扩展性以及深入分析的可能性。

然而,那种精确性并不总是必要的。例如,在我们的借阅图书馆的情况下,一个完整的关系型数据库可能是杀鸡用牛刀。实际上,在某些情况下,拥有一个灵活的数据存储系统更为有效,这个系统你可以快速扩展,而不需要大量的重编码。

例如,如果你想要向你的list对象添加一个新属性,只是简单地添加新属性,让数据库去操心,而不是必须重构你的数据库代码、添加新列、更新你所有的 SQL 语句和触发器,并确保所有之前的记录都有这个新属性,要简单得多。

文档导向存储就此发挥作用。在文档导向存储系统中,你的数据存储由一系列键值对文档组成。那个文档的结构是怎样的?数据存储其实并不关心。它可能包含几乎任何东西,只要每个记录有一个唯一的键,以便它可以被检索到。

所以,在一个文档条目中,你可能会有一个非常简单的文档。也许是一个键值对。

{name:phone_number}

然后在另一个文档条目(在同一个数据存储中),你可能会有一个复杂的对象,有嵌套数组、嵌套对象等等。

{ people: [
  {firstname:"STEVE", lastname:"Scuba", phones :[
    {type:cell, number:8888675309},
    {type:home, number:8005322002}]
  },
  {firstname:...
    ...
  }]
}

毕竟,它可能是威廉·莎士比亚的全集。真的不重要。只要数据存储能够为那个文档分配一个唯一的键,它就可以被存储。

正如您可能已经猜到的那样,缺乏结构可以使查询、排序和操作那些文档的效率降低。但没关系,因为我们的主要关注点是编码的便利性和开发速度,而不是效率。

此外,由于我们的应用程序只有几个核心功能,我们可以快速确定我们将最经常使用的查询,并将文档架构围绕那个进行优化。这使得在某些情况下,面向文档的数据库实际上比传统的关系数据库表现得更好

提示

市场上有一些相当复杂的面向文档的存储解决方案,有些人认为它们与标准的关系数据库一样有效,甚至更有效,但这个讨论超出了本书的范围。

鉴于面向文档的存储系统的灵活性,它非常适合快速更改,Meteor 提供的基础库使我们不必担心连接或结构。我们只需要对如何检索、添加和修改这些文档有高层次的理解,其余的都可以留给 Meteor。

MongoDB

MongoDB——这个词是对“humongous”(巨大)的玩弄——是一个开源的 NoSQL(不仅仅是 SQL)数据库。它提供了如索引、链接和原子操作等复杂功能,但它的核心仍然是一个面向文档的存储解决方案。

提示

想要了解更多关于 MongoDB 的信息,请访问官方网站 www.mongodb.org

使用简单命令,我们可以查看哪些记录(哪些文档)可用,将那些记录转换成 JavaScript 对象,然后保存那些更改后的对象。把 MongoDB 记录想象成实际文本文档:

  1. 查找并打开文档进行编辑(Meteor 等效:lists.find (...))。

  2. 修改文档(Meteor 等效:lists.update({...}))。

  3. 保存文档(自动通过.update()函数完成)。

没那么简单,如果你想要成为 MongoDB 领域的专家,还有很多语法你需要学习,但你可以清晰地看到这种简单、干净的面向文档的方法:查找/创建一个记录,进行更改,并将记录保存/更新到数据存储中。

我们需要讨论一个最后一个概念,以帮助您更好地理解 MongoDB 是如何工作的。它被称为数据库,但把它想象成文档的集合更容易。集合是索引化的,可以快速访问,但它仍然是一个集合,而不是关系表/实体的组。就像你会在硬盘上想象一个文件夹,你把所有的文本文档都放在里面,把 MongoDB 想象成一个文档的集合,所有这些文档都可以访问并且可以“打开”,更改和保存。

使用直接命令

为了更好地了解 MongoDB 如何工作,让我们在命令行中玩得开心一些。

  1. 首先,确保您的应用程序正在运行(打开一个终端窗口,cd~/Documents/Meteor/LendLib目录,并执行meteor命令)。接下来,打开浏览器访问http://localhost:3000

  2. 现在,您可能想打开一个额外的终端窗口,cd~/Documents/Meteor/LendLib目录,并运行以下命令:

    meteor mongo
    
    

    您应该看到以下类似屏幕截图的消息:

    使用直接命令

您现在已连接到您借阅图书馆应用程序的运行 MongoDB 数据库。让我们用几个命令四处看看。

  1. 首先,让我们打开帮助屏幕。输入以下命令并按Enter

    > help
    
    
  2. 您将获得一个命令列表,以及每个命令的简要说明。其中一个特别能让我们看到更多命令:db.help()。这将为我们提供数据库相关命令的列表。在您的终端窗口中输入以下内容,然后按Enter

    > db.help()
    
    

    不要被可能的命令数量吓到。除非您想成为 MongoDB 专家,否则您不需要了解所有这些。您只需要知道几个,但四处看看永远不会伤害任何人,所以让我们继续。

  3. 如前所述,文档存储在 MongoDB 中的一个逻辑分组中,称为集合。我们可以亲眼看到这一点,并直接在终端窗口中查看我们的 lists 集合。要查看所有可用集合的列表,请输入以下内容:

    > db.getCollectionNames()
    
    
  4. 在您的响应中,您将找到您借阅图书馆集合的名称:lists。让我们继续查看lists集合。输入以下内容:

    >  db.getCollection('lists')
    
    
  5. 嗯,这并不是很有趣。我们得到的只是meteor.lists。我们希望能够对该集合执行一些查询。所以这次,让我们将集合分配给一个变量。

    > myLists = db.getCollection('lists')
    
    

    看来我们得到了与上次相同的结果,但我们得到的远不止这些。现在,我们将lists集合分配给变量myLists。因此,我们可以在终端窗口中运行与我们在 Meteor 代码中相同的命令。

  6. 让我们获取Clothes列表,该列表目前没有任何项目,但仍然存在。输入以下命令:

    >  Clothes = myLists.findOne({Category:"Clothes"})
    
    

    这将返回一些非常基本的 JSON。如果您仔细看,将能够看到空的项目数组,表示为"items" : [ ]。您还会注意到一个_id键值,旁边有一个长数字,类似于以下内容:

    "_id" : "520e4f45-8469-47b9-8621-b41e60723de0",
    

    我们没有添加那个_id。MongoDB 为我们创建了它。这是一个唯一键,因此如果我们知道它,我们就可以更改该文档,而不会干扰其他文档。我们实际上在我们借阅图书馆应用程序的多个位置使用这个。

如果您在~/Documents/Meteor/LendLib/LendLib.js中查看,您将看到以下用于向列表添加项目的函数:

function addItem(list_id,item_name){
  if (!item_name&&!list_id)
    return;
  lists.update({_id:list_id},
  {$addToSet:{items:{Name:item_name}}}); 
}

注意,当我们调用lists.update()函数时,我们通过_id来识别我们要更新的文档。这确保了我们不会意外地更新多个文档。例如,如果你给两个列表赋予相同的类别名称(例如,"DVDs"),并使用类别作为选择器({Category:"DVDs"}),你将对这两个类别列表采取行动。如果你 instead 使用_id,它将只更新具有匹配_id的唯一文档。

回到终端,现在我们的lists集合有了变量myLists,我们将Clothes变量赋予了代表"Clothes"列表的lists集合中的文档。

注意一下目前Clothes列表在浏览器中的样子。

使用直接命令

让我们继续将我们最喜欢的衬衫添加到Clothes列表中。我们将在终端窗口直接执行此操作。输入以下命令:

>myLists.update({_id:Clothes._id},{$addToSet:{items:{Name:"Favorite Shirt"}}})

这个命令使用Clothes._id作为选择器更新myLists,并调用$addToSet,添加一个名为Name:"Favorite Shirt"的项目。Meteor 更新需要几秒钟,但很快您就会看到您最喜欢的衬衫已添加到列表中。

使用直接命令

如果您重新运行Clothes赋值命令Clothes = myLists.findOne({Category:"Clothes"}),您现在会看到items数组有一个您最喜欢的衬衫的条目。

我们可以同样轻松地更新或删除一个项目,使用不同的参数调用.update()函数($pull用于删除,$set用于更新)。

提示

对于代码示例,请参阅LendLib.js中的removeItem()updateLendee()函数。

要深入了解 MongoDB 命令,请访问mongodb.org并点击TRY IT OUT

既然我们已经浏览了一些可以直接实施的命令,让我们重新审视一下我们的LendLib.js代码,讨论一下追踪我们集合变化的响应式代码。

广播变化

使用发布/订阅模型,Meteor 不断寻找集合和Session变量的变化。发生变化时,会触发一个变化事件(或发布)。回调函数监听(或订阅)正在广播的事件,当它订阅的特定事件发布时,函数中的代码将被激活。或者,数据模型可以与 HTML/Handlebars 模板的某些部分直接绑定,这样当发生变化时,HTML 将被重新渲染。

发布的事件

那么,何时发布事件呢?如前所述,当模型发生更改时会广播事件。换句话说,当集合或变量被修改时,Meteor 发布适当的变化事件。如果将文档添加到集合中,将触发一个事件。如果已存在于集合中的文档被修改并保存回集合中,将触发一个事件。最后,如果Session变量被更改,将触发一个事件。函数和模板正在监听(订阅)特定事件,并将适当地处理数据的更改。

如果您回想起来自第三章,为什么 Meteor 如此棒!,这是模型-视图-视图模型模式在起作用。在一个反应式上下文中,函数和模板会响应模型的更改。反过来,视图中的动作将通过视图模型逻辑创建对模型的更改:

已发布的事件

Meteor 的 MVVM 是一种干净、简洁的开发模式:

  1. 设置订阅以监控模型更改(模型=集合、文档和Session变量)。

  2. 创建逻辑来处理视图事件(视图事件=按钮点击、文本输入等)。

  3. 更改模型,当逻辑需要时(更改=已发布的事件)。

一圈又一圈地转,按钮点击导致模型更改,然后触发一个事件,模板监听这个事件。根据模型更改更新视图。洗发水,冲洗。重复。

配置发布者

到目前为止,我们一直在使用autopublish。这意味着,我们没有为任何事件或集合编写特定的发布事件。这对于测试来说很好,但我们希望对发布的事件和文档有更多的控制,以便我们可以提高性能和安全性。

如果我们有一个大数据集,我们可能不希望每次都返回整个集合。如果使用autopublish,将返回整个集合,这可能会减慢速度,或者可能会暴露我们不想暴露的数据。

关闭 autopublish

是时候关闭autopublish了。如果您正在运行 Meteor 应用程序,请通过打开您运行meteor命令的终端窗口来暂时停止它。您可以按Ctrl + C键停止它。一旦它停止,请输入以下命令:

> meteor remove autopublish

这移除了autopublish库,这个库负责 Meteor 内部所有事件的自动发布。

提示

通常建议从您的项目中移除autopublishautopublish用于开发和调试,当您准备认真使用应用程序时应关闭。

通过关闭此功能,您实际上使您的应用程序什么也不做!恭喜您!您可以通过重新启动 Meteor 服务(输入meteor命令并按Enter键),然后打开/导航到http://localhost:3000来查看您的惊人进步。您将看到以下屏幕截图:

关闭 autopublish

分类/列表消失了!你甚至可以在控制台进行检查。输入以下命令:

> lists.find().count()

你应该看到一个6的计数,但你会发现计数是0

关闭 autopublish

这是怎么回事呢?实际上,原因很简单。因为移除了autopublish库,服务器不再广播我们模型的任何更改。

我们为什么要这么做呢?破坏我们应用的目的是什么?啊!因为我们想要让我们的应用更高效。我们不是自动获取每个记录,而是只获取我们需要的记录,以及这些记录的最小数据字段集。

列出分类

LendLib.js中,在if(Meteor.isServer)块内,创建以下Meteor.publish函数:

Meteor.publish("Categories", function() {
 return lists.find({},{fields:{Category:1}}); 
});

这告诉服务器发布一个"Categories"事件。每当函数内部变量发生变化时,它都会发布这个事件。在这个例子中,是lists.find()。每当对lists.find()的结果产生影响的变化发生时,Meteor 将触发/发布一个事件。

如果你注意到了,lists.find()调用并不是空的。有一个选择器:{fields:{Category:1}}。这个选择器告诉lists.find()调用只返回fields:指定的内容。并且只指定了一个字段——{Category:1}

这段 JSON 代码告诉选择器我们想要获取Category字段(1=真,0=假)。因为提到的唯一字段是1(真),Meteor 假定你希望排除所有其他属性。如果你有任何字段设置为0(假),Meteor 会假定你希望包括所有你没有提到的其他字段。

提示

有关find()函数的更多信息,请查阅 MongoDB 文档:www.mongodb.org/display/DOCS/Advanced+Queries

所以,如果你保存这个更改,你的浏览器会刷新,然后...显示没有任何变化!

为什么这么做呢?正如你可能会猜到的,移除autopublish库不仅仅是去除了publish事件。它还去除了监听器/订阅者。我们没有为Categories事件通道设置任何订阅者。因此我们需要添加一个订阅者事件,以便能够接收到Categories通道的信息。

if (Meteor.isClient)函数的最顶部,在开括号内输入以下代码行:

Meteor.subscribe("Categories");

保存这个更改,你现在将看到Categories回到了它们应该在的位置。

列出分类

在我们庆祝之前,先点击服装分类。

列出分类

我们最喜欢的衬衫不见了!正如你现在可能已经猜到的,这是因为我们设置的发布事件非常具体。在Categories通道中发布的唯一字段是Category字段。包括我们的items(以及因此我们的最喜欢的衬衫)在内的所有其他字段都没有被广播。

让我们再检查一下。在浏览器中点击+按钮,在Clothes类别中输入Red Hooded Sweatshirt,然后按Enter。新条目会出现一瞬间,然后就会消失。这是因为本地缓存和服务器同步。

当你输入一个新的item时,本地缓存包含一个副本。那个item暂时对您的客户端可见。然而,当与服务器同步时,服务器更新只发布Category字段,所以当服务器模型更新本地模型时,item就不再包括在内。

再试一次,只是为了好玩。在你的终端窗口中,停止 Meteor 服务(Ctrl + C)。现在,在浏览器中,在Clothes类别中输入另一个item(我们用Pumped Up Kicks)。因为服务已经停止,所以没有与服务器的同步,所以您使用本地缓存,您的item就在那里。

列出类别

现在重新启动你的服务器。您的客户端将与服务器同步,然后你的item又消失了。

列出类别

列出项目

这不行,因为我们想要看到我们的items。所以,让我们把items加回来,并在选择一个Category时获取适当的items列表。在LenLib.cs中,在我们第一个Meteor.publish()函数下面的if(Meteor.isServer)块中,添加以下函数:

Meteor.publish("listdetails", function(category_id){
 return lists.find({_id:category_id}); 
});

这个publish函数将在"listdetails"通道上发布。任何监听器/订阅者都将提供变量category_id,以便我们的find()函数返回一个更轻的记录集。

请注意,到目前为止,我们的客户端还没有发生变化(您的items仍然不可见)。那是因为我们需要创建一个subscribe函数。

在我们的第一个Meteor.subscribe()函数下面,添加以下函数:

Meteor.subscribe("Categories");

Meteor.autosubscribe(function() {
 Meteor.subscribe("listdetails", Session.get('current_list')); 
});

保存您的更改,然后查看您的Clothes收藏!

列出项目

让我们花一分钟揭开这里面的奥秘,弄清楚刚才发生了什么。请注意,订阅使用了Session.get('current_list')。这个变量是在发布函数中传递的。换句话说,Session变量current_list中的值将作为find()函数选择器中的category_id使用。

如果你记得第四章,模板,我们已经设置了一个点击事件处理程序,用来监听Category的变化。例如,当你点击Clothes时,一个事件会被触发,LendLib.js中的selectCategory()函数处理该事件,并改变我们的Session变量。

function selectCategory(e,t){
  Session.set('current_list',this._id); 
}

那个 Session.set() 触发了一个发布事件。我们将 Meteor.subscribe() 函数包裹在 Meteor.autosubscribe() 函数中,以便为 "listdetails" 通道提供 Meteor.subscribe() 函数。我们这样做是因为 Session.set() 事件将触发 Meteor.autosubscribe(),而那里正好有一个 Meteor.subscribe() 函数,专门为 "listdetails" 通道服务。

换句话说:

  1. Session.set() 触发一个事件。

  2. Meteor.subscribe() 监听这个事件,因为它使用了 Session 变量。

  3. 流星在 "listdetails" 通道上重置了订阅监听器(因为它被包裹在 Meteor.autosubscribe() 中)。

  4. 流星看到新的订阅监听器并触发了一个初始事件。

  5. Meteor.subscribe() 函数接收到这个事件,传入 category_id 变量,由于模型变化,UI 进行了刷新。

检查你的简化数据

现在显示与我们开始本章时没有区别。但在显示之下,模型要精简得多。选择 服装 类别,在浏览器控制台中运行以下命令:

> lists.findOne({Category:"DVDs"})

展开对象,你会看到没有列出任何项目。

检查你的简化数据

之所以没有项目,是因为我们的 Session 变量 current_list 被设置为 Clothes,而不是 DVDsfind() 函数只获取 current_list 的完整记录。

现在在浏览器控制台中输入以下命令并按 Enter

> lists.findOne({Category:"Clothes"})

展开对象,你会看到你的三个项目在一个数组中。

检查你的简化数据

点击各个地方,为类别添加项目,添加新类别,并检查客户端可见的底层数据模型。你会发现你的列表现在明显可见性降低,因此更加安全和私密。这可能对你个人的借贷图书馆应用来说不会有问题,但随着我们在下一章中扩展这个应用,让多个人可以使用,简化和私密的数据将真正提高性能。

总结

在本章中,你已经了解了 MongoDB 是什么,面向文档的数据库是如何工作的,以及在命令行中进行了直接查询,熟悉了 Meteor 的默认数据存储系统。你还通过移除 autopublish 简化了你的应用,并且对内置于 Meteor 中的发布/订阅设计模式有了扎实的理解。

在下一章中,你将真正加强应用的安全性,允许多用户跟踪和控制他们自己的项目列表,并且你将了解如何通过使用文件夹进一步简化客户端和服务器代码。

第六章:应用程序和文件夹结构

为了让你能够直接开始,Meteor 创建了一组默认库、默认文件夹结构和默认权限。这个默认配置非常适合快速开发、测试和学习。然而,它并不适合生产环境。

在这一章,我们将讨论你可能想要对默认配置进行的更改,以便你的应用性能更优、更安全、更容易管理。具体来说,你将学习到:

  • 将你的应用程序的客户端、服务器和公共文件分离

  • 启用数据库安全和用户登录

  • 定制显示结果以保护隐私

客户端和服务器文件夹

到目前为止,我们把所有的 JavaScript 代码都放在了一个文件中:LendLib.js

LendLib.js 中,我们有两个部分,由 if 语句分隔。面向客户端的代码位于 if (Meteor.isClient) {...} 块中,而服务器端代码位于 if (Meteor.isServer) {...} 块中。

这种结构对于一个非常简单的应用程序来说是可以的,但当我们编写一个更复杂的应用程序,或者我们有 multiple 人在同一个应用程序上工作时,尝试用条件语句共享一个文件很快就会变成一场噩梦。

另外,Meteor 会读取我们应用程序文件夹中的任何和所有文件,并尝试将 JavaScript 应用到客户端和服务器。如果我们想使用面向客户端的 JavaScript 库(例如,Twitter Bootstrap 或 jQuery),这就会造成一种奇怪的情况。如果我们把库放到根目录,Meteor 就会尝试在客户端和服务器上都实现这个文件。这要么因为我们在服务器上加载了它不需要的文件而造成性能问题,要么因为服务器不知道如何处理显示对象(服务器不显示任何东西)而产生错误。

相反,如果文件中包含面向客户端和服务器的服务器端代码,客户端可能会尝试实现该代码,这可能会造成各种问题,或者至少会让代码对客户端可见,这可能很快就会成为一个安全问题。有些文件和代码我们就是不想让客户端看到或访问。

让我们看看客户端代码被服务器处理的一个例子,然后把那部分代码移到一个只有客户端会尝试执行它的地方。在 ~/Documents/Meteor/ 中创建一个名为 LendLibClient.js 的新文件。打开 LendLib.js 并剪下以下高亮显示的代码块中的整个客户端代码块:

var lists = new Meteor.Collection("lists");

if (Meteor.isClient) { 
...
}

if (Meteor.isServer){...

提示

你应该剪掉了大约 186 行代码。确保你找到了闭合的 } 括号!

现在把刚刚剪切的代码粘贴到 LendLibClient.js 中,然后保存对两个文件的更改。你会注意到这并没有对你的正在运行的应用程序产生任何视觉上的变化。那是因为 Meteor 正在处理这两个文件,而 if 条件阻止了服务器执行代码。

但让我们看看当我们移除if条件时会发生什么。在LendLibClient.js中,删除包含if (Meteor.isClient) {条件的第一行。同时,确保你也删除if条件的闭合括号(})的最后一行。保存LendLibClient.js,然后去看看 Meteor 正在运行的控制台。

你会看到以下错误信息,或类似的内容:

app/LendLibClient.js:21
   Meteor.subscribe("Categories");
          ^
TypeError: Object #<Object> has no method 'subscribe'
    at app/LendLibClient.js:21:11
...
Exited with code: 1
Your application is crashing. Waiting for file change.

移除if条件创造了一种情况,Meteor 的服务器部分试图运行面向客户端的代码。它不知道如何处理它,所以应用程序崩溃了。我们将通过使用文件夹结构来解决这个问题。

如果你还记得,当我们实现 Twitter Bootstrap 时,我们创建了client文件夹。Meteor 识别出client文件夹,并且将独占地运行在此文件夹中找到的任何 JavaScript 文件,作为面向客户端的代码,而不会在服务器端运行。

LendLibClient.js文件从~/Documents/Meteor/LendLib/移动(剪切+粘贴,拖放,或使用mv命令)到~/Documents/Meteor/LendLib/client/。这将立即修复我们的崩溃应用程序,Meteor 再次快乐!你会在控制台看到以下内容:

=> Modified -- restarting.

因为我们把LendLibClient.js移动到了client文件夹,所以不再需要if条件。由于文件位置,Meteor 知道该代码只打算在客户端运行,所以它不会尝试在服务器上运行它。

提示

你可能想刷新你的浏览器,指向http://localhost:3000

这是因为你的应用程序崩溃了。悔改你的邪恶行为,刷新你的页面。

现在让我们对服务器端代码也做同样的事情。创建一个名为server的新文件夹。你可以通过 Finder 窗口,或者直接在命令行中如下操作:

$ mkdir ~/Documents/Meteor/LendLib/server

我们知道我们应该直接在新生成的server文件夹中创建我们的 JavaScript 文件,但我们也是病态的好奇,喜欢破坏事物,所以我们打算创建一个可能引起问题的地方。

~/Documents/Meteor/LendLib文件夹中创建一个名为LendLibServer.js的新文件。从LendLib.js中剪切if (Meteor_is.server) { … }块,粘贴到LendLibServer.js中,然后保存两个文件。

提示

此时,LendLib.js中应该只剩下一行代码了:

var lists = new Meteor.Collection("lists");

与客户端代码的移动一样,在此阶段不会发生任何不良反应,因为我们仍然有if条件。让我们移除它,让应用程序崩溃继续!

LendLibServer.js中,删除包含if (Meteor.isServer) {的第一行和包含闭合括号(})的最后一行。

保存你的更改,让我们看看混乱场面!

客户端和服务器文件夹

嗯。没有崩溃。应用程序仍然运行良好。真是让人失望...

让我们检查一下浏览器控制台:

客户端和服务器文件夹

是的!我们确实做了坏事!这个行为(不幸的是)没有干扰或影响应用程序的其他部分,原因有两个:

  • 是客户端(浏览器)抛出了错误。这不会影响服务器应用程序。

  • LendLibServer.js中的唯一代码是服务器代码。如果这段代码在客户端出错了,那也没关系,因为本来就不应该在客户端运行。

最终用户永远不知道错误的存在,但我们知道,所以让我们来修复它。将LendLibServer.js移动到~/Documents/Meteor/LendLib/server/目录下。错误将消失,我们小小的 Meteor 王国将再次一切安好。

公共文件夹

客户端文件夹(client)只能被客户端处理,服务器文件夹(server)只能被服务器处理,这是很逻辑的。但我们还需要考虑一个额外的因素,那就是资源文件(如图片、文本/内容文件等)。

资源文件仅在运行时需要。我们在任何逻辑或处理上都不依赖于它们,所以如果我们能将它们移开,Meteor 编译器就可以忽略它们,这能加快我们应用程序的处理和交付速度。

这就是public文件夹发挥作用的地方。当 Meteor 在为客户端和服务器编译 CSS 或 JavaScript 时,它会忽略public文件夹内的任何内容。然后,在所有的编译工作完成后,它会使用public文件夹来访问可能需要传递给客户端的任何内容。

让我们为我们的应用程序添加一个背景图片。慷慨和英俊的先生在subtlepatterns.com有很多选择,而且都是免费的,所以我们从中选择一个。我们将使用 Texturetastic Gray,因为它似乎符合我们的主题。访问subtlepatterns.com/texturetastic-gray/并下载图片。

小贴士

您可以使用任何背景图片。只需按照以下步骤操作您自定义的背景图片,并在我们声明background-image时将图片名称替换到 CSS 中。

在我们可以使用下载的背景之前,我们需要对LendLib.css做一次快速更改,并创建一个公共文件夹。

打开LendLib.css(除非您已将其移动到client文件夹中,这完全可以),并添加以下 CSS 声明:

body {
 background-image: url(/texturetastic_gray.png); 
}

保存这个更改。现在还不会发生任何事情(译者注:暂时不变),我们稍后再处理。在~/Documents/Meteor/LendLib目录下创建一个名为public的文件夹。现在,打开下载的压缩包texturetastic_gray.zip,并将texturetastic_gray.png从压缩包中复制到我们刚创建的public文件夹中:

公共文件夹

背景已经更换成了您的背景图片,现在我们拥有了一个更炫酷的界面!

这个文件被安全地保存在public文件夹中,所以 Meteor 编译器不需要处理它,但当需要服务于客户端以供显示时,它仍然可用并准备就绪。

小贴士

还存在其他文件夹,它们有不同的效果和目的。有关完整解释,请参阅 Meteor 文档中的docs.meteor.com/#structuringyourapp

安全和账户

此时,我们的借阅图书馆应用程序完全是我们想要它做的。它记录了所有我们的物品以及我们借出物品给谁。如果我们把这个应用程序投入使用,然而,应用程序本身存在一些我们需要解决的安全问题。

首先,阻止某人访问我们的应用程序并从他们借阅的物品中删除他们的名字有什么阻止他们呢?那个恶棍 STEVE 可能会永远保留我们的线性压缩扳手,如果他有意的话,而我们将无法证明他是否还拥有它。

我们不能让这种盗窃和不诚实的行为不受惩罚!STEVE 必须为此负责!所以,我们需要实现安全措施。具体来说,我们需要执行两个操作:

  • 只允许物品所有者在 UI 中进行编辑

  • 确保数据库安全,防止通过网络控制台进行更改。

删除 insecure

实现这两个目标的第一步是删除 Meteor 中的insecure库。默认情况下,insecure库是包含在内的,这样我们就可以在制定安全策略并编写大部分代码之前构建我们的应用程序,而不用担心安全问题。

是时候了,我们知道我们希望实现的安全方面,所以让我们继续摆脱那个库。停止 Meteor 应用程序(在终端窗口中按Ctrl + C),然后输入以下命令(您需要位于LendLib目录中):

>meteor remove insecure

这将生成以下消息:

insecure: removed

我们的应用程序现在很安全。实际上安全了。重新启动 Meteor(在终端中输入meteor并按Enter键),然后在浏览器窗口中使用http://localhost:3000导航到我们的应用程序。一旦你到了那里,试着添加一个新项目;添加一个借阅者,或者甚至删除一个项目。我们将尝试把我们的最爱衬衫借给我们的性感美国女友,但是什么也不会发生;没有删除,没有添加,没有更改。现在什么都不起作用了!如果你打开浏览器控制台,你会发现每次尝试更新数据库都会显示更新失败:访问被拒绝的消息:

Removing insecure

这个消息发生是因为我们禁用了 insecure 包。换句话说,不再允许匿名更改。因为我们还没有登录账户,我们所有的请求都是匿名的,因此将会失败。

添加管理员账户

为了重新启用更新功能,我们需要能够创建一个管理员账户,给管理员账户权限进行更改,并给用户提供一个找回丢失密码的方法。

我们首先需要添加三个内置的 Meteor 包。停止 Meteor 应用程序,在终端窗口中输入以下三个命令:

$ meteor add accounts-base
$ meteor add accounts-password
$ meteor add email

这些命令将为我们的 Meteor 应用程序添加管理账户所需的包。

Meteor 还提供一个 UI 包,可以自动为我们的账户创建登录逻辑,这样我们就不用编写任何自定义的账户 UI 代码。既然如此,我们顺便添加这个包:

$ meteor add accounts-ui

既然我们已经添加了accounts-ui包,我们只需要快速配置要显示的字段,并更新我们的 HTML 模板。打开LendLibClient.js,在文件的底部添加以下代码:

Accounts.ui.config({
 passwordSignupFields: 'USERNAME_AND_OPTIONAL_EMAIL' 
});

这告诉accounts-ui包我们希望在注册表单中显示usernameemail字段,其中email字段是可选的(我们需要它来恢复丢失的密码)。

现在打开LendLib.html,在<body>标签的直接下方输入以下代码:

<body>
  <div style="float: right; margin-right:20px;">
 {{loginButtons align="right"}}
 </div>
  <div id="lendlib">

这段 HTML 代码将在我们屏幕的右上角添加一个登录链接和上下文菜单框。让我们看看它的实际效果。保存所有更改,启动您的 Meteor 应用,在浏览器中导航到http://localhost:3000。注意以下屏幕截图的右上角:

添加管理员账户

点击登录,然后点击弹出窗口右下角的创建账户

添加管理员账户

填写创建账户表单,确保为管理员输入一个用户名和一个有效的电子邮件地址,以便在需要时可以恢复您的密码。输入并确认您的新密码,然后点击创建账户

添加管理员账户

你现在将作为管理员登录,我们可以继续配置权限:

添加管理员账户

授予管理员权限

现在我们已经有了管理员账户,让我们允许该账户在 UI 中进行任何必要的更改,同时取消管理员账户未登录时在浏览器控制台进行更改的能力。

我们原始的LendLib.js文件目前里面只有一行代码。我们将向其中添加一些账户检查代码,确保只有管理员账户可以进行更改。

将以下代码添加到LendLib.js并保存您的更改:

/*checks to see if the current user making the request to update is the admin user */

function adminUser(userId) {
 var adminUser = Meteor.users.findOne({username:"admin"});
 return (userId && adminUser && userId === adminUser._id);
}

lists.allow({
 insert: function(userId, doc){
 return adminUser(userId);
 },
 update: function(userId, docs, fields, modifier){
 return adminUser(userId);
 },
 remove: function (userId, docs){
 return adminUser(userId);
 }
});

adminUser函数在多个地方使用,因此创建一个公共函数是有意义的,该函数仅检查发出请求的userId是否与管理员账户的_id相同。

lists.allow设置了允许操作的条件,每个操作都有一个返回true以允许和false以拒绝的函数。例如,如果我们永远不想让任何人(包括管理员账户)删除类别,我们可以将remove函数检查设置为总是返回false

目前,我们只是想让操作根据管理员账户是否登录并发起请求而有条件地执行,因此我们将每个函数设置为return adminUser(userId);

在我们的浏览器中,我们现在可以测试我们的权限。添加一个新的类别(任何您喜欢的,但我们将会添加玻璃器皿),添加一个新项目,更改一个所有者,等等——只要您以管理员身份登录,所有操作都应该被允许。

让我们确保访问确实与我们的管理员账户相关联。通过点击右上角管理员旁边的登出按钮,然后点击登出按钮,退出应用程序:

授予管理员权限

现在,在浏览器控制台中,输入以下命令(或等效于您添加的类别):

> lists.remove({Category:"glassware"})

您将收到一个访问被拒绝的消息:

授予管理员权限

以管理员身份重新登录,再次运行该命令。这次分类将被删除。通过在lists级别设置权限和允许的操作,使用lists.allow(),我们使某人无法在不以管理员身份登录的情况下进行更改。现在,用户界面和浏览器控制台都受到了 STEVE,那个扳手小偷的邪恶阴谋的保障!

自定义结果

当我们考虑到应用程序的安全性和可用性时,还有一个我们需要考虑的问题。如果我们能够使多个用户可以使用借阅图书馆,而每个用户只能看到属于他们的物品,那会怎样呢?如果我们这样做,我们就可以阻止人们看到别人拥有什么样的东西,同时我们也可以让每个人跟踪自己的物品。我们最初的目标是为自己创建一个应用程序,但是稍作修改,我们就可以让任何人使用它,他们会觉得我们很棒,也许会请我们吃午饭!

修改 Meteor.publish()

为了准备让多个人使用我们的应用程序,我们需要确保没有人能看到别人的东西。这是在Meteor.publish()声明中为Categories完成的。逻辑上,如果我们限制用户可以看到的类别,这种限制将会传递到可见的项目,因为项目是在类别中找到的。

打开LendLibServer.js,修改大约在第 6 行附近找到的find({})块:

Meteor.publish("Categories", function() {
  return lists.find({owner:this.userId},{fields:{Category:1}});
});

添加选择器owner:this.userId将检查我们lists存储库中的每个列表,并返回每个当前登录用户是列表所有者的每个实例的类别。保存此更改,您会发现所有当前的类别都消失了!

修改 Meteor.publish()

这是因为我们已经在应用程序中创建的列表没有任何所有者,而且我们以管理员身份登录。当我们尝试修改现有项目时,我们将遇到类似的问题,因为没有列表有任何所有者。

我们有几种方法可以解决这个问题,包括手动将管理员账户作为所有者添加,让管理员账户看到所有未认领的列表,或者直接重新开始。由于我们只借出了一件物品(可恶,STEVE!我们还我们的扳手!),现在是一个很好的时间清空我们的数据库,并在我们忘记谁有它之前重新添加我们的线性压缩扳手。

作为管理员登录到浏览器控制台,输入以下命令:

>lists.remove({})

这将删除我们所有的列表,一旦我们给新创建的列表添加了所有者,我们就可以重新开始了。

提示

如果你也想清除所有用户,你可以通过停止 Meteor 应用程序,然后在终端窗口中运行meteor reset,然后再重新启动 Meteor 应用程序来完成。要小心!没有警告,也没有后悔药!

添加所有者权限

给任何新类别添加所有者是相当简单的。我们只需要更新我们的lists.insert()函数,并添加所有者字段。打开LendLibClient.js,找到Templates.categories.events声明。在'keyup #add-category'事件的事件代理中,你会看到lists.insert()函数的调用。按照以下方式修改该调用:

if (catVal)
{
  lists.insert({Category:catVal,owner:this.userId});
  Session.set('adding_category', false);
}

每当添加一个新的列表时,我们不仅在添加一个类别字段,而是在添加一个所有者字段。这使得我们的Meteor.publish()代码能够正确地为我们创建的任何新列表工作。

让我们先恢复“工具”类别,输入项目“线性压缩扳手”,并将借阅者设为STEVE

添加所有者权限

这样,我们就可以重新运行,并且在每个列表中都隐藏了一个所有者属性。当允许其他人创建和维护他们自己的列表时,这个属性就变得重要了。

启用多个用户

好的,现在一切都准备就绪,我们可以拥有一个定制化的、私有的个人物品视图,但目前只有管理员账户可以添加列表或项目,并将借阅者分配给一个项目。

我们将通过回到LendLib.js,并添加一些逻辑来检查当前登录的用户是否拥有列表,或者是否是管理员来解决这个问题。在LendLib.js中的lists.allow()代码块里,加入以下内容:

lists.allow({
  insert: function(userId, doc){
    return (adminUser(userId) || (userId && doc.owner === userId)); 
  },
  update: function(userId, docs, fields, modifier){
    return adminUser(userId) ||
 _.all(docs, function(doc) {
 return doc.owner === userId;
 });
  },
  remove: function (userId, docs){
    return adminUser(userId) ||
 _.all(docs, function(doc) {
 return doc.owner === userId;
 });
  }
});

insert中,我们检查当前doc.owner是否是登录的用户。在updateremove中,我们遍历所有要更新的记录(使用_.all()),并检查doc.owner是否是登录的用户。

现在你可能想要保存你的更改,并在http://localhost:3000上创建一个新的账户。尽情添加类别和项目吧。你可以随意在用户之间切换,并且可以添加尽可能多的用户和列表。

你会注意到,一个人的列表对另一个人是不可见的,因此也没有人能够操纵或删除另一个人的列表和记录。当 STEVE 最终得到你的应用程序时,他只能看到自己的东西(顺便说一下,这些都是不值得借的东西!):

启用多个用户

总结

在这一章节中,你已经学习了 Meteor 是如何编译和搜索 JavaScript 和 CSS 代码的,以及如何优化搜索。你已经学会了如何保护你的服务器代码,并保持运行流畅和高效。你已经学会了如何通过使用 Meteor 内置的 Accounts 包来保护你的数据库,并且你已经关闭了你的应用程序中的所有主要安全漏洞。最后,你启用了多个账户,这样任何人都可以使用你的借阅图书馆来跟踪他们的物品,而且你这样做并没有损害最终用户的隐私。

在下一章节中,你将学习如何将 Meteor 应用程序部署到一个生产环境中,并学习开始编写快速、健壮且适用于生产的 Meteor 应用程序的技术。

第七章:打包和部署

我们的应用程序看起来很棒。我们已经使它变得安全,易于使用,并且通过添加多重登录,现在任何人都可以使用借阅图书馆跟踪他们的物品。

在最后一章中,我们将介绍 Meteor 出色的包系统,这将加快未来的代码项目,并且我们会讨论部署应用程序的选项。你会学会如何:

  • 添加和配置第三方包,如 jQuery,Backbone 和 Bootstrap

  • 捆绑您的整个应用程序,以便可以部署

  • 使用 Meteor 的公共服务器部署您的应用程序

  • 将您的应用程序部署到自定义服务器

第三方包

Meteor正在为主要的 JavaScript 和预处理库添加包。这些包很智能,不仅包含基础的 JavaScript 或预处理库,而且它们还配置为直接与 Meteor 代码库交互。

这意味着对于您来说,添加您最喜欢的库几乎不需要任何努力,并且您可以确信它将与您的 Meteor 应用程序协同工作。

列出可用包

要查看带有简要说明的所有可用包列表,只需在终端中输入以下命令,然后按Enter

$ meteor list

这将为您提供安装的 Meteor 版本的的所有包的列表。

正如您所看到的,有很多最受欢迎的框架,包括 jQuery,Backbone,underscore 和 Twitter 的 Bootstrap!我们花了很多时间手动下载 Bootstrap,创建客户端文件夹,并提取 Bootstrap 文件。这是一个手动安装框架的好练习,但现在我们将学习如何作为包来安装它。

首先,让我们移除现有的 Bootstrap 安装。导航到~/Documents/Meteor/LendLib/client/并删除bootstrap目录。你的 Meteor 应用是否正在运行无关紧要(记住,Meteor 是动态更新的)。要么启动它,然后导航到http://localhost:3000,或者如果它已经在运行,直接导航那里。你会发现我们所有的漂亮格式都消失了!

列出可用包

现在我们将添加官方的 Meteor Bootstrap 包。再次强调,因为 Meteor 是动态更新的,除非我们想停止,否则我们不需要停止 Meteor 应用。要么打开一个新的终端窗口,或者暂时停止您的 Meteor 应用,并确保您在~/Documents/Meteor/LendLib文件夹中。一旦在那里,输入以下命令:

$ meteor add bootstrap

您将收到一个非常简短的消息:

bootstrap: UX/UI framework from Twitter

如果您使用了第二个终端窗口,只需打开浏览器(您甚至不必刷新页面)。如果您停止了您的 Meteor 应用程序,再次启动它,并导航到http://localhost:3000。您将能够看到 Bootstrap 格式现在已返回,一切都很正常:

列出可用包

真的就是这么简单。在你的终端中使用一个命令,你就可以向你的项目中添加一个库或框架,而无需担心链接、下载,并确保文件在正确的位置。只需运行meteor add...,然后就可以出发了!

小贴士

你可以通过在终端中输入以下命令来获取你已经在使用的包的列表:meteor list --using

由于 Meteor 正在如此快速地添加包,保持你的 Meteor 安装最新是个好主意。时不时地在终端中运行以下命令:

$ meteor update

如果你是最新的版本,它会告诉你,你正在运行哪个版本。如果有新版本,它会下载并为你安装它。

捆绑你的应用程序

遵循 Meteor 一贯的风格,将你的应用程序捆绑以便可以部署是极其简单的。如果应用程序正在运行,请停止它,确保你处于你的应用程序文件夹中(对于借贷图书馆来说,它是~/Documents/Meteor/LendLib),然后在终端中输入以下命令:

$ meteor bundle lendlib.tgz

这需要一点时间来运行,但完成后你将在LendLib文件夹中得到一个lendlib.tgz的 tar 压缩包,然后你可以将其部署到你希望的任何地方。这是一个完整的包/捆绑。你部署这个捆绑的机器只需要安装了 Node.js 和 MongoDB 即可。你需要的其他所有东西都包含在捆绑包中。

部署到 Meteor 的服务器

Meteor 团队把部署工作又推进了一步,超出了甚至是一个付费产品,更不用说是一个免费产品的预期。Meteor 允许你直接在他们的部署服务器上部署你的应用程序。为你的应用选择一个名字(我们将使用packt.lendlib,但你需要想出自己的名字)并在终端中输入以下命令:

$ meteor deploy [your app name].meteor.com

所以,在我们的案例中,我们输入了meteor deploy packt.lendlib.meteor.com。控制台会在捆绑、上传和部署应用程序时给出状态更新。完成后,它会给你一个类似于以下的消息:

Now serving at [your app name].meteor.com

如果你在浏览器中导航到那个 URL(例如,packt.lendlib.meteor.com),你就会看到你的应用程序已经部署并运行起来了!

部署到 Meteor 的服务器

小贴士

在开始使用应用程序或向他人介绍之前,你可能想要创建一个admin登录账户。你不想让那个狡猾的STEVE控制你的应用!

更新 Meteor 的服务器

如果你做了改动,或者你发现了一个错误,并且你想更新在 Meteor 服务器上的应用程序代码,会怎样呢?正如你大概猜到的,这非常简单。只需重新运行你的deploy命令:

$ meteor deploy [your app name].meteor.com

这不仅更新了您的应用程序,而且还保留了您的数据,因此如果您已经输入了大量信息,您不需要从头开始。很酷,对吧?Meteor 团队真正知道是什么让开发变得有趣,他们不遗余力地提供了一个您可以编码、玩耍并立即获得应用程序反馈的环境。

使用您自己的主机名

但是等等,还有更多!您甚至可以使用自己的主机名与部署在 Meteor 服务器上的应用程序。使用您的主机提供商设置一个指向origin.meteor.com的 CNAME,然后您可以部署到该主机名。例如,如果我们有一个子域meteor.too11.com作为 CNAME 指向origin.meteor.com,我们将在终端中运行以下命令:

$ meteor deploy meteor.too11.com

如果你设置了正确的 CNAME,这将就像你直接部署到[你的应用名称].meteor.com一样进行部署,并且将使用你自定义的域名可用!

提示

咨询您的主机提供商关于设置 CNAME 路由。这因提供商而异,但做起来相当简单。

部署到自定义服务器

在撰写本文时,将 Meteor 应用程序部署到托管服务或个人计算机是一项相当繁重的任务。部署过程中存在版本问题,而且大多数托管服务仍处于支持 Meteor 捆绑包的早期阶段。

话说回来,我们将通过部署到自定义服务器的一个 Meteor 应用程序,并留给您探索托管服务(如 Heroku 或 AppFog)。

服务器设置

你从哪个服务器托管你的应用程序需要两样东西:

  • Node.js,版本 0.8 或更高版本

  • MongoDB(最新版本)

要安装 Node.js,请访问nodejs.org/并按照 Linux 或 Mac OS X 安装说明操作。

要安装 MongoDB,请访问docs.mongodb.org/manual/installation/并按照您相应操作系统的说明操作。安装完成后,请确保设置一个名为lendlib的数据库。

一旦安装和配置了这两个产品,您将有一个 MongoDB 的默认位置。这将是类似于mongodb://localhost:27017/lendlib的东西。您需要在未来的一个步骤中使用该 URI,所以请确保记下或准备好供参考。

另外,你可以在远程服务器上设置 MongoDB,或者使用像 MongoHQ 这样的托管服务。(www.mongohq.com)。如果您使用远程服务,设置一个新数据库,并记下您需要的 URI。以下是来自 MongoHQ 的一个示例:

服务器设置

部署您的捆绑包

如果你还记得,我们在这个章节开始时创建了一个 tarball。现在我们需要解压这个 tarball,然后进行一些修改,之后我们的应用就可以启动了。将lendlib.tgz复制到你的服务器上,在用于部署的目录中(例如~/Sites/LendLib)。一旦lendlib.tgz在正确的位置,你可以使用以下命令解压 tarball:

$ tar –zxvf lendlib.tgz && rm lendlib.tgz

这将解压 tarball,你将得到一个名为bundle的新文件夹。

可选 – 不同平台

如果你开发应用的机器和你要部署到的机器不一样,你可能需要重新构建原生包。为此,进入node_modules目录:

$ cd bundle/server/node_modules

一旦在那里,删除fibers目录:

$ rm –r fibers

现在使用npm重新构建fibers

$ npm install fibers

这将安装特定于你部署平台的最新fibers版本。如果你开发的机器和部署的机器运行的是同一个平台,你不需要这样做。

运行你的应用

现在你的 bundle 已经被正确解压,你准备启动你的应用。启动应用你需要以下信息:

  • 根 URL(例如,http://lendlib.mydomain.comhttp://localhost

  • 你希望应用运行的端口(例如,80

  • 你的 MongoDB URI(例如,mongodb://<user>:<password>@alex.mongohq.com:10022/lendlib

一旦你做出了决定并收集了这些信息,为你的应用启动 Node.js。导航到你的root文件夹(我们的文件夹是~/Sites/LendLib)并输入以下内容:

$ PORT=80 ROOT_URL=http://localhost MONGO_URL=mongodb://<user>:<password>@alex.mongohq.com:10022/lendlib node bundle/main.js

让我们分解一下:

  • PORT设置了端口变量,这样 NodeJS 就知道你希望服务应用程序的哪个端口

  • ROOT_URL设置了rootUrl变量,这样 NodeJS 就知道请求是为你的应用程序指定的主机名

  • MONGO_URL告诉 NodeJS 在哪里可以找到我们的 MongoDB

  • node是调用的命令

  • bundle/main.js是 NodeJS 调用的起始 JavaScript 文件

如果你的所有信息都正确,应用将会运行,你可以使用浏览器来测试它:

运行你的应用

你还可以更深入地了解部署,比如设置环境变量,以守护进程/服务的形式运行你的应用,甚至使用远程服务器公开托管你的应用。我们到目前为止所做的一切应该足以让你开始,并让你在使用 Meteor 的生产环境的道路上越走越远。

总结

你现在已经成为 Meteor 专家了!认真的。你知道如何从零开始构建一个 Meteor 应用。你理解 Meteor 背后的设计模式和数据库原理,你可以为你的应用进行定制、优化和保障,让它做任何你想要的事情。你还可以将 Meteor 部署到多个环境。你已经迈出了编写高效、稳定且可靠的 web 应用的正确道路。

因为 Meteor 非常新,所以拥有与你现在一样多的关于 Meteor 的实际操作知识的人非常少。按照定义,这使你成为了专家。现在,如何运用你的专业知识取决于你自己。建议在你的下一个开发项目中使用 Meteor,通过代码贡献、在论坛上回答问题以及制作你自己的教程来为 Meteor 社区做贡献。

Meteor 是一项突破性技术,越来越受到关注,而你现在拥有使用这项突破性技术来推进你的个人和职业开发项目的知识与经验,这使你能够保持领先,让你的开发者生活更加充实。

posted @ 2024-05-23 14:40  绝不原创的飞龙  阅读(61)  评论(0编辑  收藏  举报