express-9 Handlebars模板引擎(2)

视图和布局

视图一般表现为网站上的各个页面(它也能够表现为页面中AJAX局部加载的内容,或一封电子邮件,或页面上的任何东西)。默认状况下,Express会在views子目录中查找视图。布局是一种特殊的视图,事实上,它是一个用于模板的模板。布局是必不可少的,由于站点的大部分页面都有几乎相同的布局。例如,页面中必须有一个<html>元素和一个<title>元素,它们一般都会加载相同的CSS文件,诸如此类。你不想为每一个网页复制代码,因而这就须要用到布局。让咱们看看基本的布局文件:css

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

请注意标记内的文本:{{{body}}}。这样视图引擎就知道在哪里渲染的内容了。必定要用三重大括号而不是两个,由于视图极可能包含HTML,咱们并不想让Handlebars试图去转义它。注意,在哪里放置{{{body}}}并无限制。此外,常见的网页元素,如页眉和页脚,一般也在布局中,而不在视图中。举例以下:html

<!-- ... -->
<body>
       <div class="container">
               <header><h1>Meadowlark Travel</h1></header>
               {{{body}}}
               <footer>&copy;{{copyrightYear}} Meadowlark Travel</footer>
       </div>
</body>

因为执行的顺序,你能够向视图中传递一个叫做body的属性,并且它会在视图中正确渲染。然而,当布局被渲染时,body的值会被已渲染的视图覆盖。前端

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

  • 当咱们建立视图引擎时,会指定一个默认的布局:
var handlebars = require('express3-handlebars').create({ defaultLayout: 'main' });
  • 默认状况下,Express会在views子目录中查找视图,在views/layouts下查找布局。因此若是有一个叫做views/foo.handlebars的视图,能够这样渲染它:
app.get('/foo', function(req, res){
        res.render('foo');
});

它会使用views/layouts/main.handlebars做为布局。jquery

  • 若是你根本不想使用布局(这意味着在视图中你不得不拥有全部的样板文件),能够在上下文中指定layout: null
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来渲染视图了。ajax

局部文件

不少时候,有些组成部分(在前端界一般称为“组件”)须要在不一样的页面重复使用。使用模板来实现这一目标的惟一方法是使用局部文件(partial,如此命名是由于它们并不渲染整个视图或整个网页)。express

  • 首先,建立一个局部文件,views/partials/weather.handlebars:
<div class="weatherWidget">
       {{#each partials.weather.locations}}
               <div class="location">
                      <h3>{{name}}</h3>
                      <a href="{{forecastUrl}}">
                               <img src="{{iconUrl}}" alt="{{weather}}">
                               {{weather}}, {{temp}}
                      </a>
               </div>
        {{/each}}
        <small>Source: <a href="http://www.wunderground.com">Weather
               Underground</a></small>
</div>

请注意,咱们使用partials.weather为开头来命名上下文。咱们想在任何页面上使用局部文件,但上述作法实际上并不会将上下文传递给每个视图,所以可使用res.locals(对于任何视图可用)。可是咱们并不想让个别的视图干扰指定的上下文,因而将全部的局部文件上下文都放在partials对象中。
json

  • 建立一个方法来获取当前天气数据:
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)',
           },
      ],
   };
}
  • 建立一个中间件给res.locals.partials对象添加这些数据:
app.use(function(req, res, next){
        if(!res.locals.partials) res.locals.partials = {};
        res.locals.partials.weather = getWeatherData();
        next();
});
  • 在视图中使用这个局部文件。例如,为将咱们的组件放在主页上,编辑views/home.handlebars:
<h2>Welcome to Meadowlark Travel!</h2>
{{> weather}}

语法{{> partial_name}}可让视图中包含一个局部文件。express3-handlebars会在views/partials中寻找一个叫做partial_name.handle‐bars的视图(或是weather.handlebars)服务器

express3-handlebars支持子目录,因此若是你有大量的局部文件,能够将它们组织在一块儿。例如,你有一些社交媒体局部文件,能够将它们放在views/partials/social目录下面,而后使用{{> social/facebook}}、{{>social/twitter}}等来引入它们。app

段落

我从微软的优秀模板引擎Razor中借鉴了段落(section)的概念。若是全部的视图在你的布局中都正好放在一个单独的元素里,布局会正常运转,可是当你的视图自己须要添加到布局的不一样部分时会发生什么?一个常见的例子是,视图须要向<head>元素中添加一些东西,或是插入一段使用jQuery的<script>脚本(这意味着必须引入jQuery,因为性能缘由,有时在布局中这是最后才作的事)。函数

Handlebars和express3-handlebars都没有针对于此的内置方法。幸运的是,Handlebars的辅助方法让整件事情变得简单起来。当咱们实例化Handlebars对象时,会添加一个叫做section的辅助方法:

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

如今咱们能够在视图中使用section辅助方法了。让咱们建立一个视图(views/jquerytest. handlebars),在<head>中添加一些东西,并添加一段使用jQuery的脚本:

{{#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>

{{#section 'jquery'}}
         <script>
                $('document').ready(function(){
                      $('h1').html('jQuery Works');
                 });
         </script>
{{/section}}

如今在这个布局里,咱们能够像放置{{{body}}}同样放置一个段落:

<!doctype html>
<html>
<head>
        <title>Meadowlark Travel</title>
        {{{_sections.head}}}
</head>
<body>
        {{{body}}}
        <script src="http://code.jquery.com/jquery-2.0.2.min.js"></script>
        {{{_sections.jquery}}}
</body>
</html>

客户端Handlebars

AJAX调用能够返回HTML片断,并将其原样插入DOM中,可是客户端Handlebars容许咱们使用JSON数据接收AJAX调用结果,并将其格式化以适应咱们的网站。所以,在与第三方API(返回JSON数据,而不是适应你网站的格式化HTML文本)通讯时尤为有用。

在客户端使用Handlebars以前,咱们须要加载Handlebars。咱们既能够将Handlebars放在静态资源中引入,也可使用一个CDN

{{#section 'head'}}
        <script src="//cdnjs.cloudflare.com/ajax/libs/handlebars.js/1.3.0/
  handlebars.min.js"></script>
{{/section}}

如今须要找个地方放模板,一种方法是使用在HTML中已存在的元素,最好是一个隐藏的元素。你能够将它放在<head>中的<script>元素里。这看起来有点奇怪,可是运行良好:

{{#section 'head'}}
          <script src="//cdnjs.cloudflare.com/ajax/libs/handlebars.js/1.3.0/handlebars.min.js"></script>

          <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>.
          </script>
{{/section}}

请注意,咱们必须转义至少一个大括号,不然,服务器端视图会尝试对其进行替换。

在使用模板以前,咱们须要编译它:

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

咱们须要一个放置已渲染模板的地方。出于测试的目的,咱们添加两个按钮,一个经过JavaScript来直接渲染,另外一个经过AJAX调用来渲染:

<div id="nurseryRhyme">Click a button....</div>
<hr>
<button id="btnNurseryRhyme">Generate nursery rhyme</button>
<button id="btnNurseryRhymeAjax">Generate nursery rhyme from AJAX</button>

最后是渲染模板的代码:

{{#section 'jquery'}}
    <script>
            $(document).ready(function(){

                  var nurseryRhymeTemplate = Handlebars.compile(
                        $('#nurseryRhymeTemplate').html());

                  var $nurseryRhyme = $('#nurseryRhyme');

                  $('#btnNurseryRhyme').on('click', function(evt){
                        evt.preventDefault();
                        $nurseryRhyme.html(nurseryRhymeTemplate({
                                 animal: 'basilisk',
                                 bodyPart: 'tail',
                                 adjective: 'sharp',
                                 noun: 'a needle'
                        }));
                  });

                  $('#btnNurseryRhymeAjax').on('click', function(evt){
                        evt.preventDefault();
                        $.ajax('/data/nursery-rhyme', {
                                success: function(data){
                                     $nurseryRhyme.html(
                                             nurseryRhymeTemplate(data))
                                }
                        });
                  });
             });
     </script>
{{/section}}

针对nursery rhyme页和AJAX调用的路由处理程序:

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',
        });
});

**从本质上讲,Handlebars.compile接收一个模板,返回一个方法。这个方法接收一个上下文对象,返回一个已渲染字符串。因此一旦咱们编译了模板,就能够像调用方法函数同样重用模板渲染。

**

相关文章
相关标签/搜索