# 模板引擎

框架内置了模板引擎,模板文件存放于根目录下的 views 文件夹中

# 变量

变量会从模板上下文获取,如果你想显示一个变量可以

{{ username }}

会从上下文查找 username 然后显示,可以像 javascript 一样获取变量的属性 (可使用点操作符或者中括号操作符):

{{ foo.bar }}
{{ foo["bar"] }}

如果变量的值为 undefined 或 null 将不显示,引用到 undefined 或 null 对象也是如此 (如 foo 为 undefined,{{ foo }}, {{ foo.bar }}, {{ foo.bar.baz }} 也不显示)。

# 过滤器

过滤器是一些可以执行变量的函数,通过管道操作符 (|) 调用,并可接受参数。

{{ foo | title }}
{{ foo | join(",") }}
{{ foo | replace("foo", "bar") | capitalize }}

第三个例子展示了链式过滤器,最终会显示 "Bar",第一个过滤器将 "foo" 替换成 "bar",第二个过滤器将首字母大写

# 模板继承

模板继承可以达到模板复用的效果,当写一个模板的时候可以定义 "blocks",子模板可以覆盖他,同时支持多层继承。

如果有一个叫做 parent.html 的模板,如下所示:

{% block header %}
This is the default content
{% endblock %}

<section class="left">
  {% block left %}{% endblock %}
</section>

<section class="right">
  {% block right %}
  This is more content
  {% endblock %}
</section>

然后再写一个模板继承他

{% extends "parent.html" %}

{% block left %}
This is the left side!
{% endblock %}

{% block right %}
This is the right side!
{% endblock %}

以下为渲染结果

This is the default content

<section class="left">
  This is the left side!
</section>

<section class="right">
  This is the right side!
</section>

你可以将继承的模板设为一个变量,这样就可以动态指定继承的模板。这个变量既可以是个指向模板文件的字符串,也可以是个模板编译后所生成的对象(需要添加上下文环境)。因此你可以通过设置上下文变量,从而在渲染时动态地改变所要继承的模板。

{% extends parentTemplate %}

# super

你可以通过调用super从而将父级区块中的内容渲染到子区块中。如果在前面的例子中你的子模板是这样的:

{% block right %}
{{ super() }}
Right side!
{% endblock %}

这个区块的渲染结果将是:

This is more content
Right side!

# 标签

标签是一些特殊的区块,它们可以对模板执行一些操作

# if

if 为分支语句,与 javascript 中的 if 类似。

{% if variable %}
  It is true
{% endif %}

如果 variable 定义了并且为 true (译者注:这里并非布尔值,和 javascript 的处理是一样的) 则会显示 "It is true",否则什么也不显示。

{% if hungry %}
  I am hungry
{% elif tired %}
  I am tired
{% else %}
  I am good!
{% endif %}

# for

for 可以遍历数组 (arrays) 和对象 (dictionaries)。

var items = [{ title: "foo", id: 1 }, { title: "bar", id: 2}];
<h1>Posts</h1>
<ul>
{% for item in items %}
  <li>{{ item.title }}</li>
{% else %}
  <li>This would display if the 'item' collection were empty</li>
{% endfor %}
</ul>

上面的示例通过使用items数组中的每一项的title属性显示了所有文章的标题。如果items数组是空数组的话则会渲染else语句中的内容。

你还可以遍历对象:

var food = {
  'ketchup': '5 tbsp',
  'mustard': '1 tbsp',
  'pickle': '0 tbsp'
};
{% for ingredient, amount in food %}
  Use {{ amount }} of {{ ingredient }}
{% endfor %}

除此之外,会将数组解开,数组内的值对应到变量

var points = [[0, 1, 2], [5, 6, 7], [12, 13, 14]];
{% for x, y, z in points %}
  Point: {{ x }}, {{ y }}, {{ z }}
{% endfor %}

在循环中可获取一些特殊的变量

  • loop.index: 当前循环数 (1 indexed)
  • loop.index0: 当前循环数 (0 indexed)
  • loop.revindex: 当前循环数,从后往前 (1 indexed)
  • loop.revindex0: 当前循环数,从后往前 (0 based)
  • loop.first: 是否第一个
  • loop.last: 是否最后一个
  • loop.length: 总数

# set

set 可以设置和修改变量。

{{ username }}
{% set username = "joe" %}
{{ username }}

如果 username 初始化的时候为 "james', 最终将显示 "james joe"。

可以设置新的变量,并一起赋值。

{% set x, y, z = 5 %}

如果在顶级作用域使用 set,将会改变全局的上下文中的值。如果只在某个作用域中使用,则只会影响该作用域。

# extends

extends 用来指定模板继承,被指定的模板为父级模板

{% extends "base.html" %}

你可以将继承的模板设为一个变量,这样就可以动态指定继承的模板。这个变量既可以是个指向模板文件的字符串,也可以是个模板编译后所生成的对象(需要添加上下文环境)。因此你可以通过设置上下文变量,从而在渲染时动态地改变所要继承的模板。

{% extends parentTemplate %}

extends也可以接受任意表达式,只要它最终返回一个字符串或是模板所编译成的对象:

{% extends name + ".html" %}`.

# block

区块(block) 定义了模板片段并标识一个名字,在模板继承中使用。父级模板可指定一个区块,子模板覆盖这个区块

{% block css %}
<link rel="stylesheet" href="app.css" />
{% endblock %}

可以在循环中定义区块

{% for item in items %}
{% block item %}{{ item }}{% endblock %}
{% endfor %}

子模板可以覆盖 item 区块并改变里面的内容。

{% extends "item.html" %}

{% block item %}
The name of the item is: {{ item.name }}
{% endblock %}

在区块中,你可以调用特殊的super函数。它会渲染父级区块中的内容。

# include

include 可引入其他的模板,可以在多模板之间共享一些小模板,如果某个模板已使用了继承那么 include 将会非常有用。

{% include "item.html" %}

可在循环中引入模板

{% for item in items %}
{% include "item.html" %}
{% endfor %}

这一点可以帮助我们把模板切分成更小的部分,从而使得在浏览器上,当我们需要改变页面时,我们可以渲染这些小部分的模板,而非一整个的大的模板。

include 可以接受任意表达式,只要它最终返回一个字符串或是模板所编译成的对象: {% include name + ".html" as obj %}.

在某些情况下,我们可能希望在模板文件不存在时不要抛出异常。对于这类情况,我们可以使用ignore missing来略过这些异常:

{% include "missing.html" ignore missing %}

被包含的模版自身可以扩展(extends)另一个模版(因此你可以让一系列相关联的模版继承同一种结构)。 一个被包含的模版并不会改变包含它的模版的区块结构,它有一个分离的继承树和块级命名空间。换言之, 在渲染时,include并不 不是 将被包含模版代码拉取到包含它的模版中的预处理器。相对的,它对被 包含的模版触发了一次的分离渲染,然后再将渲染的结果引入。

# raw

如果你想输出一些特殊的标签 (如 {{),可以使用 {{),可以使用 {% raw %} 将所有的内容输出为纯文本。

# filter

filter区块允许我们使用区块中的内容来调用过滤器。不同于使用|语法,它会将区块渲染出的内容传递给过滤器。

{% filter title %}
may the force be with you
{% endfilter %}

{% filter replace("force", "forth") %}
may the force be with you
{% endfilter %}

# 注释

你可以使用 {# and #} 来写注释,渲染时将会去除所有的注释。

{# Loop through all the users #}
{% for user in users %}...{% endfor %}

# 空白字符控制

模板在正常情况会将变量 (variable) 和标签区块 (tag blocks) 周围的空白字符完全输出。有时,你不想输出一些额外的空白字符,但代码又需要一些空白字符来显得整洁。

你可以在开始和结束区块 (start or end block tag) 添加 (-) 来去除前面和后面的空白字符。

{% for i in [1,2,3,4,5] -%}
  {{ i }}
{%- endfor %}

上面准确的输出为 "12345",-%} 会去除标签右侧的空白字符,{%- 会去除标签之前的空白字符。

# 表达式

你可以使用和 javascript 一样的字面量。

  • Strings: "How are you?", 'How are you?'
  • Numbers: 40, 30.123
  • Arrays: [1, 2, "array"]
  • Dicts: { one: 1, two: 2 }
  • Boolean: true, false

# 运算

支持运算 (但尽量少用,把逻辑放在代码中),可使用以下操作符:

  • Addition: +
  • Subtraction: -
  • Division: /
  • Division and integer truncation: //
  • Division remainder: %
  • Multiplication: *
  • Power: **

可以如下使用:

{{ 2 + 3 }}       (outputs 5)
{{ 10/5 }}        (outputs 2)
{{ numItems*2 }}

# 比较

  • ==
  • ===
  • !=
  • !==
  • >
  • >=
  • <
  • <=
{% if numUsers < 5 %}...{% endif %}
{% if i == 0 %}...{% endif %}

# 逻辑符号

  • and
  • or
  • not
  • 可使用大括号来分组
{% if users and showUsers %}...{% endif %}
{% if i == 0 and not hideFirst %}...{% endif %}
{% if (x < 5 or y < 5) and foo %}...{% endif %}

# If 表达式

和 javascript 的三元运算符 (ternary operator) 一样,可使用 if 的内联表达式:

{{ "true" if foo else "false" }}

当 foo 为 true 的时候最终输出 "true" 否则为 "false",对于获取默认值的时候非常有用:

{{ baz(foo if foo else "default") }}

# 函数调用

如果你传入一个函数,则可以直接调用

{{ foo(1, 2, 3) }}

# 正则表达式

你可以像在JavaScript中一样创建一个正则表达式:

{{ /^foo.*/ }}
{{ /bar$/g }}

正则表达式所支持的标志如下

  • g: 应用到全局
  • i: 不区分大小写
  • m: 多行模式
  • y: 粘性支持(sticky)

# 自动转义

所有的输出都会自动转义,但可以使用 safe 过滤器来保持不转义

{{ foo }}           // &lt;span%gt;
{{ foo | safe }}    // <span>

也可以使用 escape 过滤器来强制转义。

{{ foo }}           // <span>
{{ foo | escape }}  // &lt;span&gt;

# 全局函数

以下为一些内置的全局函数

# range([start], stop, [step])

如果你需要遍历固定范围的数字可以使用 range,start (默认为 0) 为起始数字,stop 为结束数字,step 为间隔 (默认为 1)。

{% for i in range(0, 5) -%}
  {{ i }},
{%- endfor %}

上面输出 0,1,2,3,4.

# cycler(item1, item2, ...itemN)

cycler 可以循环调用你指定的一系列的值。

{% set cls = cycler("odd", "even") %}
{% for row in rows %}
  <div class="{{ cls.next() }}">{{ row.name }}</div>
{% endfor %}

上面的例子中奇数行的 class 为 "odd",偶数行的 class 为 "even"。你可以使用current属性来获取当前项(在上面的例子中对应cls.current)。

# joiner([separator])

当合并多项的时候,希望在他们之间又分隔符 (像逗号),但又不希望第一项也输出。joiner 将输出分割符 (默认为 ",") 除了第一次调用。

{% set comma = joiner() %}
{% for tag in tags -%}
  {{ comma() }} {{ tag }}
{%- endfor %}

如果 tags 为 ["food", "beer", "dessert"], 上面将输出 food, beer, dessert。

# 内置的过滤器

# default(value, default, [boolean])

(简写为 d)

如果value全等于undefined则返回default,否则返回value。 如果boolean为true,则会在value为JavaScript中的假值时(比如:false, ""等)返回default。

# sort(arr, reverse, caseSens, attr)

用JavaScript中的arr.sort函数排序arr。如果reverse为true,则会返回相反的 排序结果。默认状态下排序不会区分大小写,但你可以将caseSens设置为true来让排序 区分大小写。我们可以用attr来指定要比较的属性。

# striptags (value, [preserve_linebreaks])

如果preserve_linebreaks为false(同时也是默认值),则会移去SGML/XML标签并用一个空格符 替换临近的、连续的空白符号。如果preserve_linebreaks为true,则会尝试保留临近的空白符号。 如果你希望使用管道操作符进行类似于{{ text | striptags | nl2br }}这样的操作时,你就会 需要用到后一种。否则你还是应该使用默认的用法。

# dump (object)

在一个对象上调用JSON.stringify,并将结果输出到模板上。这在调试时很有用:{{ foo | dump }}。

# 其他过滤器