视图一般表现为网站上的各个页面(它也能够表现为页面中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>©{{copyrightYear}} Meadowlark Travel</footer> </div> </body>
因为执行的顺序,你能够向视图中传递一个叫做body的属性,并且它会在视图中正确渲染。然而,当布局被渲染时,body的值会被已渲染的视图覆盖。前端
var handlebars = require('express3-handlebars').create({ defaultLayout: 'main' });
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
<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)', }, ], }; }
app.use(function(req, res, next){ if(!res.locals.partials) res.locals.partials = {}; res.locals.partials.weather = getWeatherData(); next(); });
<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>
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
接收一个模板,返回一个方法。这个方法接收一个上下文对象,返回一个已渲染字符串。因此一旦咱们编译了模板,就能够像调用方法函数同样重用模板渲染。
**