express-8 Handlebars模板引擎(1)

简介###

  • 使用JavaScript生成一些HTML
document.write('<h1>Please Don\'t Do This</h1>');
document.write('<p><span class="code">document.write</span> is naughty,\n');
document.write('and should be avoided at all costs.</p>');
document.write('<p>Today\'s date is ' + new Date() + '.</p>');

问题出现在这里:切换上下文环境是困难的。如果你写了大量的JavaSctipt,混合在HTML中会引起麻烦和混乱; 由JavaScript生成的HTML充满了问题:

  • 必须不断地考虑哪些字符需要转义以及如何转义。
  • 使用JavaScript来生成那些自身包含JavaScript代码的HTML会很快让你抓狂。
  • 通常会失去编辑器的语法高亮显示和其他方便的语言特性。
  • 很难发现格式不正确的HTML。
  • 很难直观地分析。
  • 很难让别人读懂你的代码。

模板解决了在目标语言中编写代码的问题,同时也让插入动态数据成为了可能; 只有在某些最简单的情况下才会使用JavaScript生成HTML。

选择模板引擎###

一些可供参考的准则: 参考推荐文章

  • 性能: 希望模板引擎尽可能地快
  • 客户端、服务端或兼而有之: 大多数(但不是所有)模板引擎都可用于客户端和服务器端。如果需要在这两端都使用模板,推荐选择那些在两端都表现优秀的模板引擎。
  • 抽象: 让代码更可读(例如,在普通HTML文本中使用大括号)

Template-Engine-Chooser

Jade###

doctype html                              <!DOCTYPE html>
html(lang="en") <html lang="en">
head <head>
title= pageTitle <title>Jade Demo</title>
script. <script>
if (foo) { if (foo) {
bar(1 + 5) bar(1 + 5)
} }
body </script>
<body>
h1 Jade <h1>Jade</h1>
#container <div id="container">
if youAreUsingJade
p You are amazing <p>You are amazing</p>
else
p Get on it!
p. <p>
Jade is a terse and Jade is a terse and
simple templating simple templating
language with a language with a
strong focus on strong focus on
performance and performance and
powerful features. powerful features.
</p>
</body>
</html>

Jade无疑是少打了很多字,因为不再有尖括号和结束标记。取而代之,它依赖缩进和一些常识性规则,从而更容易表达出自己想要的。Jade具有一个额外的优势:理论上讲,当HTML自身发生改变时,你可以轻松地将Jade定位于HTML版本的最新版本,从而让你的内容更具“前瞻性”。

Handlebars基础###

理解模板引擎的关键在于context(上下文环境):当渲染一个模板时,便会传递给模板引擎一个对象,叫作上下文对象,它能让替换标识运行。

例如,如果上下文对象是{ name: 'Buttercup' },模板是<p>Hello, {{name}}!</p> ,则 {{name}}会被Buttercup替换。如果向模板中传递HTML文本会发生什么呢?例如,上下文换成{ name: '<b>Buttercup</b>' },使用之前的模板得到的结果将是<p>Hello,&lt;b&gt;Buttercup&lt;b&gt;</p>,这或许并不是你想要的。要想解决这个问题,用三个大括号代替两个就可以了:{{{name}}}。

express-8 Handlebars模板引擎(1)

  • 注释: 区分Handlebars注释和HTML注释很重要。示例如下:
{{! super-secret comment }}
<!-- not-so-secret comment -->

假设这是一个服务器端模板,上面的super-secret comment将不会被传递到浏览器,然而如果用户查看HTML源文件,下面的not-so-secret comment就会被看到。

  • 块级表达式

上下文对象:###

{
currency: {
name: 'United States dollars',
abbrev: 'USD',
},
tours: [
{ name: 'Hood River', price: '$99.95' },
{ name: 'Oregon Coast', price: '$159.95' },
],
specialsUrl: '/january-specials',
currencies: [ 'USD', 'GBP', 'BTC' ],
}

传递到如下模板:

<ul>
{{#each tours}}
{{! I'm in a new block...and the context has changed }}
<li>
{{name}} - {{price}}
{{#if ../currencies}}
({{../../currency.abbrev}})
{{/if}}
</li>
{{/each}}
</ul>
{{#unless currencies}}
<p>All prices in {{currency.name}}.</p>
{{/unless}}
{{#if specialsUrl}}
{{! I'm in a new block...but the context hasn't changed (sortof) }}
<p>Check out our <a href="{{specialsUrl}}">specials!</p>
{{else}}
<p>Please check back often for specials.</p>
{{/if}}
<p>
{{#each currencies}}
<a href="#" class="currency">{{.}}</a>
{{else}}
Unfortunately, we currently only accept {{currency.name}}.
{{/each}}
</p>
  • each辅助方法,这使我们能够遍历一个数组; 在下级块中,如果想访问currency对象,就得使用../来访问上一级上下文。

  • if辅助方法:

    在Handlebars中,所有的块都会改变上下文,所以在if块中,会产生一个新的上下文......而这刚好是上一级上下文的副本。换句话说,在if或else块中,上下文与上一级上下文是相同的。但是当在一个each循环中使用if块时就有必要细究一下了。在{{#each tours}}循环体中,可以使用../.访问上级上下文。不过,在{{#if ../currencies}}块中,又进入了一个新的上下文......所以要获得currency对象,就得使用../../.。第一个../获得产品的上下文,第二个获得最外层的上下文。这就会产生很多混乱,最简单的权宜之计就是在each块中避免使用if块。

  • 在if和each块中都有一个可选的else块(对于each,如果数组中没有任何元素,else块就会执行)。我们也用到了unless辅助方法,它基本上和if辅助方法是相反的:只有在参数为false时,它才会执行。

  • 最后要注意的一点是在{{#each currencies}}块中使用{{.}}。{{.}}指向当前上下文,在这个例子中,当前上下文只是我们想打印出来的数组中的一个字符串。

访问当前上下文还有另外一种独特的用法:它可以从当前上下文的属性中区分出辅助方法。例如,如果有一个辅助方法叫作foo,在当前上下文中有一个属性也叫作foo,则{{foo}}指向辅助方法,{{./foo}}指向属性。

服务器端模板###

服务器端模板与客户端模板不同,客户端模板能够通过查看HTML源文看到,而不会看到服务器端模板,或是用于最终生成HTML的上下文对象。

服务器端模板除了隐藏实现细节,还支持模板缓存,这对性能很重要。模板引擎会缓存已编译的模板(只有在模板发生改变的时候才会重新编译和重新缓存),这会改进模板视图的性能。默认情况下,视图缓存会在开发模式下禁用,在生产模式下启用。如果想显式地启用视图缓存,可以这样做:app.set('view cache', true)

Express支持Jade、EJS和JSHTML。所以需要添加一个node包,让Express提供Handlebars支持。

npm install --save express3-handlebars

然后就可以在Express中引入:

var handlebars = require('express3-handlebars').create({ defaultLayout: 'main' });
app.engine('handlebars', handlebars.engine);
app.set('view engine', 'handlebars');

express3-handlebars让Handlebars模板拥有了.handlebars扩展名。可以在创建express3-handlebats实例将扩展名改成同样常见的.hbs;默认模版还是main.hbs

var exphbs = require('express3-handlebars');
app.engine('hbs', exphbs({extname: '.hbs'}));
app.set('view engine', 'hbs');
上一篇:C(++)基于websocket实时通信的实现—GoEasy


下一篇:Java Web(八) MVC和三层架构