为了账号安全,请及时绑定邮箱和手机立即绑定

《Node与Express开发》学习笔记-第七章-Handlebars模板引擎

标签:
Node.js
7.4 Handlebars基础
上下文环境

当你渲染一个模板时,便会传递给模板引擎一个对象,叫作上下文对象,它能让替换标识运行。

标识

在模板中使用{{ }}或{{{ }}}的地方就是表示
{{ }} 会转译标签
{{{ }}} 则直接替换

图片描述
7.4.1 注释

{{! super-secret comment }}

7.4.2 块级表达式

上下文环境

{
    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' ],
}

each 辅助方法

<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会遍历整个tours数字,更具tours.length为他遍历的次数,然后根据tours每条数据还去替换标示符。
这时each中的上下文环境指向tours,如果想访问currency 对象,就必须先回退到最上层上下文环境然后在选中currencies。
使用../ 来访问上一级上下文。

if 辅助方法
if 块中,会产生一个新的上下文。而这刚好是上一级上下文的副本。

也就是说,each语块中的上下文是tours each中的if 的上下文环境是tours的副本。tours的副本的上一级是tours。所以这里要用../../才能回退到顶层。

unless 辅助方法
它基本上和if 辅助方法是相反的:只有在参数为false 时,它才会执行。

{{.}}指向当前上下文。

一般数据组织方法都是放置在一个数组中。
如果数组就是一个普通数组,each会{{.}}表示遍历出这个数组的每一个元素。
如果数组是由多个对象组成,each会每一条对象作为each的上下文去遍历然后根据对象的属性名去替换标示。

注意,如果属性名与handlebars辅助方法同名foo,则{{foo}} 指向辅助方法,{{./
foo}} 指向属性。

7.4.3 服务器端模板

模板缓存
模板引擎会缓存已
编译的模板(只有在模板发生改变的时候才会重新编译和重新缓存),这会改进模板视图的性能。默认情况下,视图缓存会在开发模式下禁用,在生产模式下启用。如果想显式地启用视图缓存,可以这样做:

app.set('view cache', true);

安装Handlebars

npm install --save express3-handlebars

Express 中引入

var handlebars = require('express3-handlebars') // 引入handlebars
.create({ 
    defaultLayout: 'main', // 设置默认布局为main
    extname: '.hbs', //  设置模板引擎文件后缀为.hbs
}); 
app.engine('handlebars', handlebars.engine); // 将express模板引擎配置成handlebars
app.set('view engine', 'handlebars'); 
7.4.4 视图和布局

视图
视图通常表现为网站上的各个页面。默认情况下,Express 会在views 子目录中查找视图。
布局
布局是一种特殊的视图,事实上,它是一个用于模板的模板。布局是必不可少的,因为站点的大部分(即使不是全部)页面都有几乎相同的布局。
布局模板:

<!doctype>
<html>
    <head>
        <title>Meadowlark Travel</title>
        <link rel="stylesheet" href="/css/main.css">
    </head>
<body>
    {{{body}}}
</body>
</html>

请注意<body>
标记内的文本:{{{body}}}。这样视图引擎就知道在哪里渲染你的内容了。一定要用三重大括号而不是两个,因为视图很可能包含HTML,我们并不想让Handlebars试图去转义它。注意,在哪里放置{{{body}}}> 并没有限制。例如,你想用Bootstrap 3 构建一个响应式布局,你或许想要把视图放进一个<div>> 容器里。此外,常见的网页元素,如页眉和页脚,通常也在布局中,而不在视图中。

模板引擎是怎样结合视图、布局和上下文来完成渲染的。重要的是,此图解
释了运行的顺序。视图首先被渲染,之后是布局。起初这看似是反常的:视图是在布局中渲染的,所以不应该是布局首先被渲染吗?虽然从技术上讲可以这么做,但是逆向运行是有优势的。特别是,它允许视图本身进一步自定义布局,这在我们讨论段落时会派上用场。

图片描述

首先服务端会通过res.render()将所需要的上下文与视图合并,合并后的视图又变成了布局模板中{{{body}}}的内容然后合并。

7.4.5 在Express中使用(或不使用)布局

handlebars默认会在 views/layouts/下查找布局
配置视图不需要布局

app.get('/foo', function(req, res){
    res.render('foo', { layout: null });
});

使用其他布局

app.get('/foo', function(req, res){
    res.render('foo', { layout: 'microsite' });
});

这样就会使用布局views/layouts/microsite.handlebars 来渲染视图了。

7.4.6 局部文件

很多时候,有些组成部分(在前端界通常称为“组件”)需要在不同的页面重复使用。使用模板来实现这一目标的唯一方法是使用局部文件(partial,如此命名是因为它们并不渲染整个视图或整个网页)

创建一个组件
创建一个局部文件,views/partials/weather.handlebars

<div class="weatherWidget">
    {{#each partials.weather.locations}}
        <div class="location">
            <h3>{{name}}</h3>
            <a href="{{forecastUrl}}">
                <img class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="{{iconUrl}}" alt="{{weather}}">
                {{weather}}, {{temp}}
            </a>
        </div>
    {{/each}}
    <small>Source: <a href="http://www.wunderground.com">WeatherUnderground</a></small>
</div>
function getWeatherData(){
    return {
        locations: [
    {
        name: 'Portland',
        forecastUrl: 'http://www.wunderground.com/US/OR/Portland.html',
        iconUrl: 'http://icons-ak.wxug.com/i/c/k/cloudy.gif',
        weather: 'Overcast',
        temp: '54.1 F (12.3 C)',
    },
    {
        name: 'Bend',
        forecastUrl: 'http://www.wunderground.com/US/OR/Bend.html',
        iconUrl: 'http://icons-ak.wxug.com/i/c/k/partlycloudy.gif',
        weather: 'Partly Cloudy',
        temp: '55.0 F (12.8 C)',
    },
{
name: 'Manzanita',
forecastUrl: 'http://www.wunderground.com/US/OR/Manzanita.html',
iconUrl: 'http://icons-ak.wxug.com/i/c/k/rain.gif',
weather: 'Light Rain',
temp: '55.0 F (12.8 C)',
},
],
};
}
app.use(function(req, res, next){
    if(!res.locals.partials) res.locals.partials = {};
    res.locals.partials.weather = getWeatherData();
    next();
});

考虑到这个组件可能在任何页面出现,通过给每一个具有这个组件的路由配置上下文的方式显然有些麻烦。所以可以把数据直接挂载到res.locals(对于任何视图可见)。
也就是res.locals可以用于挂在通用的上下文环境。当handlebars进行组合的时候不单通过res.render()获取上下文,同时也会访问res.locals。

在所需要这个组件的视图中添加
{{> weather}}
handlebars就加载views/partials/weather.handlebars局部文件

语法{{> partial_name}} 可以让你在视图中包含一个局部文件。express3-handlebars 会在views/partials 中寻找一个叫作partial_name.handle-bars 的视图(或是weather.handlebars,如上例)。

express3-handlebars 支持子目录,所以如果你有大量的局部文件,可以将它 们组织在一起。例如,你有一些社交媒体局部文件,可以将它们放在views/ partials/social 目录下面, 然后使用{{> social/facebook}}、{{> social/twitter}} 等来引入它们。

7.4.7 段落

如果视图只有一个结构组成的话,那么直接用布局套用就可以了,但是往往有些时候视图可能会出现独有的配置时显然光用布局的方法是不够用的。

例如,如果一个页面需要引入浏览器端的handlebars。其他页面没必要引入时就可以用到段落这个概率。

Handlebars和express3-handlebars都没有针对于此的内置方法。
Handlebars是通过辅助方法来添加对段落的支持。

var handlebars = requrie('express3-handlebars').create({
    defaultLayout : 'main',
    helpers : {
        section : funciton(name, options){
            if(!this._sections) this._sections = {};
            this._sections[name] = options.fn(this);
            return null;
        }
    }
});

这样就配置好了段落(说实话,我自己也没看太懂。这里作者讲的比较初略没有说明,name和options代指的是什么。)

创建一个带有段落的模板
jquerytest.handlebars

<!-- 段落部分-head -->
{{#section 'head'}}
    <!-- we want Google to ignore this page -->
    <meta name="robots" content="noindex">
{{/section}}
<!-- 视图部分 -->
<h1>Test Page</h1>
<p>We're testing some jQuery stuff.</p>

<!-- 段落部分-jquery -->
{{#section 'jquery'}}

        $('document').ready(function(){
            $('h1').html('jQjery Works');               
        });

{{/section}}

这个视图就包含了3个部分
一个视图部分、两个段落。模板引擎会分成三个部分插入到布局里。

修改布局文件

<!doctype html>
<html>
<head>
    <title>Meadowlark Travel</title>
    {{{_sections.head}}}
</head>
<body>
    {{{body}}}
    <script class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="http://code.jquery.com/jquery-2.0.2.min.js">
    {{{_sections.jquery}}}
</body>
</html>

通过在视图文件中定义段落

{{#section 'name'}}
{{/section}}

布局中使用

{{{_sections.name}}}
7.4.9 客户端Handlebars

1.引入handlebars浏览器框架并且编辑handlebars模板

{{#section 'head'}}
<script class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="//cdnjs.cloudflare.com/ajax/libs/handlebars.js/1.3.0/handlebars.min.js">
<script id="nurseryRhymeTemplate" type="text/x-handlebars-template">
    Marry had a little <b>\{{animal}}</b>, its <b>\{{bodyPart}}</b>    was <b>\{{adjective}}</b> as <b>\{{noun}}</b>.

{{/section}}

2.将模板放入handlebars进行编译
这里主要使用Handlebars.compile()方法传入模板字符串

{{#section 'jquery'}}
    $(document).ready(function(){
        var nurseryRhymeTemplate = Handlebars.compile(
        $('#nurseryRhymeTemplate').html());
    });
{{/section}}

4.想编译完成的结果传入数据
然后直接在生成的nurseryRhymeTemplate(传入模板数据)会返回拼接好的模板。在用jquery插入即可。

{{#section 'jquery'}}

    $(document).ready(function(){
        var nurseryRhymeTemplate = Handlebars.compile(
            $('#nurseryRhymeTemplate').html());

        var $nurseryRhyme = $('#nurseryRhyme');
        //js的方式组合数据与模板
        $('#btnNurseryRhyme').on('click', function(evt){
            evt.preventDefault();
            $nurseryRhyme.html(nurseryRhymeTemplate({
                animal: 'basilisk',
                bodyPart: 'tail',
                adjective: 'sharp',
                noun: 'a needle'
            }));
        });
        //ajax的方式组合数据与模板
        $('#btnNurseryRhymeAjax').on('click', function(evt){
            evt.preventDefault();
            $.ajax('/data/nursery-rhyme', {
                success: function(data){
                $nurseryRhyme.html(
                nurseryRhymeTemplate(data))
                }
            });
        });
    });

{{/section}}

服务器端

app.get('/nursery-rhyme', function(req, res){
    res.render('nursery-rhyme');
});
app.get('/data/nursery-rhyme', function(req, res){
    res.json({
        animal: 'squirrel',
        bodyPart: 'tail',
        adjective: 'bushy',
        noun: 'heck',
    });
});
点击查看更多内容
19人点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
Web前端工程师
手记
粉丝
90
获赞与收藏
327

关注作者,订阅最新文章

阅读免费教程

感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消