二、构建 HTML 模板

在我们成功安装 Meteor 和设置我们的文件夹结构后,我们现在可以开始为我们的博客构建基本模板了。

在本章中,我们将学习如何构建模板。 我们将看到如何显示数据,以及如何使用辅助函数更改某些部分。 我们将了解如何在模板中添加事件、使用条件和理解数据上下文。

以下是本章的概述:

  • 基本模板结构
  • 显示数据
  • 编写模板辅助函数
  • 在模板中使用条件
  • 数据上下文以及如何设置它们
  • 嵌套模板和数据上下文继承
  • 添加事件
  • Building block helpers

    注释

    如果你在第 1 章Getting Started with Meteor中没有设置文件夹结构就直接进入本章, 下载前一章的代码示例从这本书的 web 页面在 https://www.packtpub.com/books/content/support/17713从 GitHub 库 https://github.com/frozeman/book-building-single-page-web-apps-with-meteor/tree/chapter1

    这些代码示例还将包含所有样式文件,因此我们不必担心在过程中添加 CSS 代码。

在 Meteor 中编写模板

通常当我们构建网站时,我们在服务器端构建完整的 HTML。 这很简单; 每个页面都建立在服务器上,然后它被发送到客户端,最后 JavaScript 添加了一些额外的动画或动态行为。

但在单页面应用中就不是这样了,在单页面应用中,每个页面都需要已经在客户端浏览器中,这样才能随意显示。 Meteor 通过提供 JavaScript 中存在的模板来解决这个问题,这些模板可以在某个点上放置在 DOM 中。 这些模板可以有嵌套模板,这样就可以方便地重用和构造应用的 HTML 布局。

由于 Meteor 在文件夹和文件结构方面是如此灵活,任何*.html页面都可以包含一个模板,并将在 Meteor 的构建过程中进行解析。 这允许我们把所有的模板放在my-meteor-blog/client/templates文件夹,这是我们在Getting Started with Meteor中创建的。 选择这个文件夹结构是因为它可以帮助我们在应用增长时组织模板。

Meteor 的模板引擎叫空格,是车把模板引擎的衍生。 空格键建立在Blaze之上,这是 Meteor 的反应式 DOM 更新引擎。

注释

虽然使用 Meteor 的空格键或构建在 Blaze 之上的第三方模板语言(如 Jade for Meteor)更方便,但 Blaze 可以直接使用 API 生成响应式 HTML。

详情请访问https://docs.meteor.com/#/full/blazehttps://github.com/mquandalle/meteor-jade

空格键之所以如此令人兴奋,是因为它的简单性和反应式。 响应式模板意味着,当底层数据改变时,模板的某些部分可以自动改变。 不需要手动 DOM 操作,不一致的接口已经成为过去。 为了更好地理解 Meteor,我们将从应用的基本 HTML 文件开始:

  1. Let's create an index.html file in our my-meteor-blog/client folder with the following lines of code:

    <head> <title>My Meteor Blog</title> </head> <body> Hello World </body>

    注释

    注意,我们的index.html文件不包含<html>...</html>标签,因为 Meteor 收集了所有的<head><body>标签在任何文件,并建立自己的index.html文件,将交付给用户。 实际上,我们也可以将这个文件命名为myapp.html

  2. Next, we run our Meteor app from the command line by typing the following command:

    ``` $ cd my-meteor-blog $ meteor

    ```

    这将启动 Meteor 服务器与我们的应用运行。

  3. 就是这样! 我们可以打开浏览器,导航到http://localhost:3000,我们应该看到Hello World

所发生的是,Meteor 将查看我们的应用的文件夹中的所有 HTML 文件,连接所有的<head><body>标签的内容,它找到并将它们作为索引文件提供给客户端。

如果我们看一下应用的源代码,我们会看到<body>标签是空的。 这是因为 Meteor 将<body>标签的内容视为自己的模板,当 DOM 加载时,将注入相应的 JavaScript 模板。

注释

要查看源代码,不要使用 Developer Tools 的元素面板,因为这会在 JavaScript 执行后显示源代码。 在 Chrome 浏览器中右键点击查看页面来源

我们还将看到 Meteor 已经在我们的<head>标签中链接了各种 JavaScript 文件。 这些是 Meteor 的核心包和我们添加的第三方包。 在生产中,这些文件将被连接成一个文件。 要查看这个操作,进入终端,使用Ctrl+C退出正在运行的 Meteor 服务器,并运行以下命令:

$ meteor --production

如果我们现在看一下源代码,我们将看到只有一个看起来神秘的 JavaScript 文件链接。

对于接下来的步骤,最好回到我们的开发模式,简单地退出 Meteor 和运行meteor命令再次,因为这将重新加载应用时,文件发生变化更快。

构建基本模板

现在,让我们通过在my-meteor-blog/client/templates文件夹中创建一个名为layout.html的文件,将基本模板添加到我们的博客中。 这个模板将作为我们博客布局的包装模板。 构建基本模板,执行以下步骤:

  1. 将以下代码添加到我们刚刚创建的layout.html中:
  2. 接下来,我们将创建主页模板,该模板稍后将列出我们所有的博客文章。 在与layout.html相同的模板文件夹中,我们将创建一个名为home.html的文件,代码如下:
  3. The next file will be a simple About page and we save it as about.html with the following code snippet:

    ``` {{#markdown}}

    About me

    Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

    Link to my facebook: facebook.com

    {{/markdown}} ```

    正如你所看到的,我们使用了{{#markdown}}块助手来包装我们的文本。 花括号是句柄语法,Blaze 使用它为 HTML 引入逻辑。 当模板被渲染时,{{#markdown}}...{{/markdown}}块将把里面的所有 markdown 语法转换成 HTML。

    注释

    markdown 文本不能像 HTML 标记那样缩进,因为 markdown 语法将缩进解释为代码。

  4. 能够使用{{#markdown}}块助手,我们需要首先添加markdown核心包应用。为此,我们退出运行应用的终端使用Ctrl + C,输入以下命令:

    ``` $ meteor add markdown

    ``** ** 现在我们可以再次运行meteor`命令来启动服务器。*

然而,当我们现在进入我们的浏览器,我们仍然会看到Hello World*。 那么我们现在如何使模板可见呢?

添加模板和局部

要在应用中显示主模板,我们需要打开我们之前创建的index.html,并执行以下步骤:

  1. 我们用以下模板包含助手替换Hello World:
  2. 如果我们现在回到浏览器,我们看到文本已经消失了,而我们之前创建的layout模板已经出现了它的标题和菜单。
  3. 为了完成页面,我们需要在layout模板中显示home模板。 我们只需在layout.html文件中的layout模板的main部分添加另一个模板包含助手,如下所示:
  4. If we go back to the browser, we should see the following screenshot:

    Adding templates and partials

如果我们现在将{{> home}}切换为{{> about}},我们将看到我们的about模板。

使用模板助手显示数据

每个模板都可以有函数,这些函数被称为templatehelper,它们可以在模板和子模板中使用。

除了我们的自定义助手函数,还有三个回调函数,它们在模板创建、渲染和销毁时被调用。 使用模板帮助器显示数据,执行以下步骤:

  1. To see the three callback functions in action, let's create a file called home.js and save it to our my-meteor-blog/client/templates/ folder with the following code snippet:

    ``` Template.home.created = function(){ console.log('Created the home template'); }; Template.home.rendered = function(){ console.log('Rendered the home template'); };

    Template.home.destroyed = function(){ console.log('Destroyed the home template'); }; ```

    如果我们现在打开浏览器的控制台,我们将看到前两个回调正在被触发。 只有在动态删除模板时,最后一个才会触发。

  2. 为了在home模板中显示数据,我们将创建一个 helper 函数,它将返回一个简单的字符串,如下所示:

  3. 现在,如果我们转到我们的home.html文件,在{{markdown}}块助手之后添加{{exampleHelper}}助手,并保存文件,我们会看到字符串出现在浏览器中,但我们会注意到 HTML 被转义了。
  4. To make Meteor render the HTML correctly, we can simply replace the double curly braces with triple curly braces, as shown in the following line of code, and Blaze won't let the HTML escape:

    {{{exampleHelper}}}

    注释

    注意,在我们的大多数模板助手中,我们不应该使用三段胡子{{{...}}},因为这会为 XSS 和其他攻击打开大门。 只有在返回的 HTML 可以安全呈现时才使用它。

  5. 此外,我们可以使用双花括号返回未转义的 HTML,但我们需要返回通过SpaceBars.SafeString函数传递的字符串,如下例所示:

设置模板的数据上下文

现在我们已经看到了如何使用 helper 显示数据,让我们看看如何设置模板的整个数据上下文:

  1. 对于接下来的例子,我们将在my-meteor-blog/client/templates文件夹中创建一个名为examples.html的文件,并添加以下代码片段:
  2. Now that we have our contextExample template, we can add it to our home template by passing some data as follows:

    {{> contextExample someText="I was set in the parent template's helper, as an argument."}}

    这将显示文本在contextExample模板中,因为我们正在使用{{someText}}显示它。

    提示

    记住,文件名并不重要,因为 Meteor 正在收集和连接它们; 然而,模板名称很重要,因为我们使用它来引用模板。

    在 HTML 中设置上下文不是动态的,因为它是硬编码的。 为了能够动态更改上下文,最好使用templatehelper 函数来设置它。

  3. 为了做到这一点,我们必须首先将帮助器添加到我们的home模板帮助器中,它返回数据上下文,如下所示:

  4. 现在我们可以将这个助手作为数据上下文添加到我们的contextExample模板包含助手,如下所示:
  5. 此外,为了显示我们返回的嵌套数据对象,我们可以在contextExample模板中使用 Blaze 点语法,方法是向模板中添加以下代码:

现在,将显示的someTextsomeNested.text,这是我们的助手函数返回的。

使用{{#with}}块帮助器

另一种设置数据上下文的方法是使用{{#with}}块助手。 下面的代码片段与使用 helper 函数的前一个包含 helper 具有相同的结果:

{{#with dataContextHelper}}
  {{> contextExample}}
{{/with}}

当我们不使用子模板,而只是在{{#with}}块助手中添加contextExample模板的内容时,我们甚至会在浏览器中得到相同的结果,如下所示:

{{#with dataContextHelper}}
  <p>{{someText}}</p>
  <p>{{someNested.text}}</p>
{{/with}}

"this"在模板助手和模板回调中

在 Meteor 中,模板助手中的this在模板回调中使用不同,如created()rendered()destroyed()

如前所述,模板有三个回调函数,它们在模板的不同状态下被触发:

  • created:这个在模板被初始化时触发,但还没有进入 DOM
  • rendered:当模板及其所有子模板被附加到 DOM 时触发
  • destroyed:当模板从 DOM 中移除时,在模板实例被销毁之前触发

在这些回调函数中,this指的是当前的模板实例。 实例对象可以访问模板 DOM,并提供以下方法:

  • this.$(selectorString):这个方法查找所有匹配selectorString的元素,并从这些元素返回一个 jQuery 对象。
  • this.findAll(selectorString):这个方法查找所有匹配selectorString的元素,但是返回普通的 DOM 元素。
  • this.find(selectorString):这个方法查找匹配selectorString的第一个元素,并返回一个普通的 DOM 元素。
  • this.firstNode:该对象包含模板中的第一个元素。
  • this.lastNode:该对象包含模板中的最后一个元素。
  • this.data:该对象包含模板数据上下文
  • this.autorun(runFunc):一个反应式Tracker.autorun()函数,当模板实例被销毁时该函数停止。
  • this.view:该对象包含该模板的Blaze.View实例。 Blaze.View是响应式模板的构建块。

在 helper 函数内部,this仅指当前的数据上下文。

为了使这些不同的行为可见,我们将看一些例子:

  • When we want to access the DOM of a template, we must do it in the rendered callback because only at this point, the template elements will be in the DOM. To see it in action, we edit our home.js file as follows:

    ``` Template.home.rendered = function(){ console.log('Rendered the home template');

    this.$('p').html('We just replaced that text!'); }; ```

    这将用我们设置的字符串替换由{{#markdown}}块助手创建的第一个p标签。 现在,当我们检查浏览器时,我们将看到包含博客介绍文本的第一个<p>标签已经被替换。

  • For the next example, we need to create an additional template JavaScript file for our contextExample template. To do this, we create a new file called examples.js in our templates folder and save it using the following code snippet:

    ``` Template.contextExample.rendered = function(){ console.log('Rendered Context Example', this.data); };

    Template.contextExample.helpers({ logContext: function(){ console.log('Context Log Helper', this); } }); ```

    这将把渲染回调以及一个名为logContext的助手添加到我们的contextExample模板助手中。 为了让这个助手运行,我们还需要将这个助手添加到我们的contextExample模板中,如下所示:

    <p>{{logContext}}</p>

现在,当我们返回浏览器控制台时,我们看到数据上下文对象已经从渲染的contextTemplates模板中返回了所有rendered回调和 helper。 我们还可以看到 helper 将在呈现回调之前运行。

注释

如果您需要从模板帮助程序内部访问模板实例,您可以使用Template.instance()来获取它。

现在让我们使用事件使我们的模板交互。

添加事件

为了使我们的模板更具的动态性,我们将添加一个简单的事件,该事件将响应性地重新运行我们之前创建的logContexthelper。

首先,我们需要添加一个按钮到我们的contextExample模板:

<button>Get some random number</button>

要捕获点击事件,请打开examples.js并添加以下event函数:

Template.contextExample.events({
  'click button': function(e, template){
    Session.set('randomNumber', Math.random(0,99));
  }
});

这将设置一个名为randomNumber的会话变量为一个随机数。

注释

我们将在下一章深入讨论会话。 现在,我们只需要知道当会话变量发生变化时,所有使用Session.get('myVariable')获取该会话变量的函数将再次运行。

为了看到这个的作用,我们将向logContexthelper 添加一个Session.get()调用,并返回前一组的随机数,如下所示:

Template.contextExample.helpers({
  logContext: function(){
    console.log('Context Log Helper',this);

    return Session.get('randomNumber');
  }
});

如果我们进入浏览器,我们会看到Get some random number按钮。 当我们点击它时,我们会看到一个随机数字出现在按钮上方。

注释

当我们在home模板中多次使用contextTemplates模板时,我们将看到该模板 helper 的每个实例都将显示相同的随机数。 这是因为会话对象将重新运行它的所有依赖项,所有这些依赖项都是logHelperhelper 的实例。

现在我们已经介绍了模板帮助器,让我们创建一个自定义块帮助器。

Block helpers

块助手是包装块内容的模板。 它们可以根据条件以不同的方式显示内容,也可以用于向块内容添加额外的功能,例如,对其 DOM 元素进行一些 JavaScript 计算。

在下面的示例中,我们将构建一个简单的块助手,它将基于布尔条件显示内容。

为了做到这一点,我们将在我们的example.html文件末尾添加以下代码片段:

<template name="blockHelperExample">
  <div>
    <h1>My Block Helper</h1>
    {{#if this}}
      <p>Content goes here: {{> Template.contentBlock}}</p>
    {{else}}
      <p>Else content here: {{> Template.elseBlock}}</p>
    {{/if}}
  </div>
</template>

{{> Template.contentBlock}}是一个预定义的块内容占位符。 这同样适用于{{> Template.elseBlock}}

this(在这个例子中,我们使用一个简单的布尔值作为模板的上下文)是true时,它将显示给定的Template.contentBlock。 否则,将显示Template.elseBlock内容。

要了解如何使用最近创建的模板作为块助手,请看以下示例,我们可以将其添加到home模板中:

{{#blockHelperExample true}}
  <span>Some Content</span>
{{else}}
  <span>Some Warning</span>
{{/blockHelperExample}}

现在我们应该看到下面的截图:

Block helpers

当我们现在将true(传递给{{#blockHelperExample}})改为false时,我们应该看到{{else}}后面的内容。

我们还可以使用 helper 函数来替换布尔值,这样我们就可以动态地切换块 helper。 此外,我们可以传递 key-value 参数,并在 block helper 模板中通过它们的键访问它们,如下面的代码示例所示:

{{#blockHelperExample myValue=true}}
...
{{/blockHelperExample}}

我们也可以通过 block 模板中的名称来访问给定的参数,如下所示:

<template name="blockHelperExample">
  <div>
    <h1>My Block Helper</h1>
    {{#if myValue}}
    ...
    {{/if}}
  </div>
</template>

注释

请注意,块内容的数据上下文将来自块出现的模板,而不是块 helper 模板本身。

块帮助程序是一个强大的工具,因为它们允许我们编写自包含的组件,当打包到包中时,其他人可以将其作为一种即时功能使用。 这个特性有可能带来一个充满活力的市场,就像我们在 jQuery 插件中看到的那样。

列出帖子

既然我们已经浏览了使用帮助器和数据的所有方式,我想引入一个名为{{#each}}的块帮助器,我们可能会发现它是最有用的。

如果我们经历的所有示例完成到目前为止,我们可以看到,最好删除所有数据上下文的例子从我们的home模板,examples.html文件,其examples.jsJavaScript 文件,以便我们可以清晰地继续建设我们的博客。

下一步是将博客条目列表添加到我们的主页。 为此,我们需要为后期预览创建一个模板。 这可以通过执行以下步骤来实现:

  1. We create a file called postInList.html in our my-meteor-blog/client/templates folder and save it with the following code snippet:

    <template name="postInList"> <div class="postListItem"> <h2><a href="#">{{title}}</a></h2> <p>{{description}}</p> <div class="footer"> Posted by {{author}} </div> </div> </template>

    这个模板将用于我们在主页中显示的每个帖子。

  2. To make it appear, we need to add a {{#each}} helper to the home template, as follows:

    {{#each postsList}} {{> postInList}} {{/each}}

    当我们传递给 block helper 的postsList返回一个数组时,数组中的每一项都会重复{{#each}}的内容,将数组项设置为数据上下文。

  3. 为了看到这一点,我们将home.js文件中的postsList助手添加到模板助手中,如下所示:

  4. As we can see, we return an array where each item is an object containing our post's data context. For timeCreated, we use the moment function of our previously added third-party package. This will generate dummy timestamps of a few days in the past. If we now go to our browser, we will see the two posts listed, as shown in the following screenshot:

    Listing posts

  5. 要以正确的格式显示 post 项中的timeCreated,我们需要创建一个 helper 函数来格式化时间戳。 但是,因为我们想在以后的其他模板中使用这个帮助器,所以我们需要使它成为一个可以被任何模板访问的全局帮助器。 为此,我们创建一个名为template-helpers.js的文件,并将其保存到my-meteor-blog/client文件夹中,因为它不属于任何特定的模板。

  6. 注册一个全局助手,我们可以使用 Meteor 的Template.registerHelper函数:
  7. 现在,我们只需要将 helper 添加到我们的postInList模板中,方法是用以下代码片段替换页脚的内容:

现在,如果我们保存这两个文件并返回浏览器,我们将看到一个相对日期添加到博客文章的页脚。 这是因为我们将时间和类型字符串传递给 helper,如下所示:

{{formatTime timeCreated "fromNow"}}

助手然后使用moment函数返回格式化的日期。

有了这个全局帮助器,我们现在可以格式化任何 Unix 时间戳、任何模板中的相对时间、ISO 时间字符串和标准日期格式(使用 LLLL 格式,它可以转换为 1986 年 9 月 4 日星期四晚上 8:30)。

既然我们已经使用了{{#with}}{{#each}}块帮助程序,现在让我们看看 Blaze 使用的其他默认帮助程序和语法。

空格键语法

让我们总结一下空格键的语法:

|

助手

|

描述

| | --- | --- | | {{myProperty}} | 模板助手可以是模板数据上下文的属性,也可以是模板助手函数。 如果 helper 函数和同名的属性存在,则模板 helper 将使用 helper 函数。 | | {{> myTemplate}} | 包含助手是用于模板的,并且总是期望一个模板对象或 null。 | | {{> Template.dynamic template=templateName [data=dataContext]}} | 使用{{> Template.dynamic ...}}帮助器,您可以通过提供一个返回模板参数的模板名的模板帮助器来动态呈现模板。 当帮助程序重新运行并返回一个不同的模板名称时,它将用新的模板替换这个位置上的模板。 | | {{#myBlockHelper}}...{{/myBlockHelper}} | 一个包含 HTML 和空格键语法的块助手。 |

默认情况下,空格键有以下四个默认块助手:

  • {{#if}}..{{/if}}
  • {{#unless}}..{{/unless}}
  • {{#with}}..{{/with}}
  • {{#each}}..{{/each}}

{{#if}}块助手允许我们创建简单的条件,如下所示:

{{#if myHelperWhichReturnsABoolean}}
  <h1>Show me this</h1>
{{else}}
  <strong>If not<strong> show this.
{{/if}}

{{#unless}}块助手的工作原理与{{#if}}相同,但逻辑交换。

如前所述,{{#with}}块将为其内容和包含的模板设置一个新的数据上下文,{{#each}}块助手将呈现多次,为每次迭代设置不同的数据上下文。

访问父数据上下文

为了完成空格键语法的之旅,让我们仔细看看用于显示数据的模板帮助器语法。 正如我们已经看到的,我们可以使用双花括号语法显示数据,如下所示:

{{myData}}

在这个帮助器中,我们可以使用点语法访问对象的属性:

{{myObject.myString}}

我们也可以使用类似路径的语法访问父数据上下文:

{{../myParentsTemplateProperty}}

此外,我们可以向上移动不止一个上下文:

{{../../someParentProperty}}

这个特性使我们能够非常灵活地处理数据上下文。

注释

如果我们想在模板帮助器内部做同样的事情,我们可以使用模板 APITemplate.parentData(n),其中n是访问父模板的数据上下文的步骤数。

Template.parentData(0)Template.currentData()this相同,如果我们是在模板助手中。

传递数据给助手

将数据传递给助手可以通过两种不同的方式。 我们可以像下面这样向 helper 传递参数:

{{myHelper "A String" aContextProperty}}

然后,我们可以在 helper 中访问它,如下所示:

Template.myTemplate.helpers({
   myHelper: function(myString, myObject){
     // And we get:
     // myString = 'aString'
     // myObject = aContextProperty
   }
});

除此之外,我们还可以以键值的形式传递数据:

{{myHelper myString="A String" myObject=aDataProperty}}

然而,这一次,我们需要如下访问它们:

Template.myTemplate.helpers({
   myHelper: function(Parameters){
     // And we can access them:
     // Parameters.hash.myString = 'aString'
     // Parameters.hash.myObject = aDataProperty
   }
});

注意块和包含助手的行为是不同的,因为它们总是期望对象或键值作为参数:

{{> myTemplate someString="I will be available inside the template"}}

// Or

{{> myTemplate objectWithData}}

如果我们想只传递一个变量或值给包含或块帮助器,Meteor 会对参数进行对象化,如下面的代码片段所示:

{{#myBlock "someString"}}
...
{{/myBlock}}

如果想在 helper 函数中使用传入的参数,则需要进行类型转换,如下所示:

Template.myBlock.helpers({
   doSomethingWithTheString: function(){
     // Use String(this), to get the string
     return this;
   }
});

此外,我们还可以简单地使用{{Template.contentBlock}}在块帮助器模板中显示字符串,如下所示:

<template name="myBlock">
  <h1>{{this}}</h1>
  {{Template.contentBlock}}
</template>

我们也可以将另一个模板帮助器作为参数传递给包含或块帮助器,如下例所示:

{{> myTemplate myHelperWhichReturnsAnObject "we pass a string and a number" 300}}

尽管将数据传递给模板帮助程序和包含/块帮助程序略有不同,但在使用帮助程序生成参数时,参数可以非常灵活。

小结

响应式模板是 Meteor 最令人印象深刻的特性之一,一旦我们习惯了它们,我们可能就不会再回头看手动 DOM 操作了。

读完这一章后,我们应该知道如何编写和使用 Meteor 模板。 我们还应该了解它的基本语法和如何添加模板。

我们看到了如何访问和设置模板中的数据,以及如何使用帮助器。 我们了解了不同类型的帮助器,如包容帮助器和阻断帮助器。 我们还创建了自己的自定义方块助手,并使用了 Meteor 的默认助手。

我们知道模板有三个不同的回调函数,用于模板的创建、呈现和销毁。

我们学习了如何将数据传递给帮助程序,以及这在普通帮助程序和区块帮助程序中的区别。

为了更深入地挖掘,请查看以下文档:

你可以在https://www.packtpub.com/books/content/support/17713或在 GitHubhttps://github.com/frozeman/book-building-single-page-web-apps-with-meteor/tree/chapter2上找到本章的代码示例。

有了这些关于模板的新知识,我们就可以向数据库中添加数据,并看看如何在主页中显示这些数据。*