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接收一個模板,返回一個方法。這個方法接收一個上下文對象,返回一個已渲染字符串。因此一旦咱們編譯了模板,就能夠像調用方法函數同樣重用模板渲染。

**

相關文章
相關標籤/搜索