Jinja2比传统HTML拥有更加强大的功能,其中一个就表现在代码模块化上。HTML除了通过iframe标签在浏览器端动态加载其他网页,几乎不具备任何代码模块化的能力。而Jinja2则可以通过宏、模板继承、引入模板等方式实现代码模块化,下面分别进行讲解。
模板中的宏与Python中的函数类似,可以传递参数,但是不能有返回值。可以将一些常用的代码片段放到宏中,然后把一些不固定的值抽取出来当成一个参数,下面用一个例子来阐述宏的用法。
{% macro input(name,value=", type='text') %} <input type="{{ type }}" value="{{ value|escape }}" name="{{ name }}"> {% endmacro %}
以上代码通过macro标签创建了一个叫作input的宏,这个宏接收两个参数,分别是name和type。以后在创建input标签时,可以通过以下代码快速创建。
<div>{{ input('username') }}</div> <div>{{ input('password', type='password') }}</div>
在实际的开发工作中,经常会将一些常用的宏单独放到一个文件中,在需要使用时再从这个文件进行导入。导入使用的是import语句,import语句的用法与Python中的import类似,形式可以直接是import…as…,也可以是from…import…,或者是from…import…as…。下面先创建一个forms.html,然后添加以下代码。
{% macro input(name, value='', type='text') %} <input type="{{ type }}" value="{{ value|escape }}" name="{{ name }}"> {% endmacro %} {% macro textarea(name, value='', rows=10, cols=40) %} <textarea name="{{ name }}" rows="{{ rows }}" cols="{{ cols }}">{{ value|escape }}</textarea> {% endmacro %}
在forms.html中添加了两个宏,分别是input和textarea。下面再在另外一个文件中通过import语句进行导入。
(1)通过import…as…形式导入。
{% import 'forms.html' as forms %} <dl> <dt>Username</dt> <dd>{{ forms.input('username') }}</dd> <dt>Password</dt> <dd>{{ forms.input('password', type='password') }}</dd> </dl> <p>{{ forms.textarea('comment') }}</p>
(2)通过from…import…as…或者from…import…形式导入。
{% from 'forms.html' import input as input_field, textarea %} <dl> <dt>Username</dt> <dd>{{ input_field('username') }}</dd> <dt>Password</dt> <dd>{{ input_field('password', type='password') }}</dd> </dl> <p>{{ textarea('comment') }}</p>
需要注意的是,通过import导入模板并不会把当前模板的变量添加到被导入的模板中,如果要在被导入的模板中使用当前模板的变量,可以通过以下两种方式实现。
(1)显示地通过参数形式传递变量。
(2)使用with context的方式,示例代码如下。
{% from 'forms.html' import input with context %}
通过以上两种方式,在forms.html中的代码也可以使用当前模板的所有变量了。
一个网站中的大部分网页的模块是重复的,如顶部的导航栏、底部的备案信息。如果在每个页面中都重复地去写这些代码,会让项目变得臃肿,增加后期的维护成本。比较好的做法是,通过模板继承,把一些重复性的代码写在父模板中,子模板继承父模板后,再分别实现自己页面的代码。首先来看一个父模板base.html的例子。
<!DOCTYPE html> <html lang="en"> <head> <link rel="stylesheet" href="base.css" /> <title>{% block title %}{% endblock %}</title> {% block head %}{% endblock %} </head> <body> <div id="body">{% block body %}{% endblock %}</div> <div id="footer"> {% block footer %} © Copyright 2008 by <a href="http://domain.invalid/">you</a> {% endblock %} </div> </body> </html>
在以上父模板中编写了网页的整体结构,并且把所有子模板都需要用到的样式文件base.css也提前做了引用。针对子模板需要重写的地方,则定义成了block,如title、head、body、footer,子模板在继承了父模板后,重写对应block的代码,即可完成子模板的渲染。下面以继承base.html的方式实现一个index.html文件,代码如下。
以上代码首先通过extends语法加载父模板,因为base.html和index.html是在同一级目录,所以直接写base.html。这里需要注意的是,extends必须出现在子模板所有代码的最前面。接下来分别实现了title、head、content这3个block,实现的block中的代码将会被自动填充到父模板指定的位置,并且最终会生成一个完整HTML结构的index.html文件。
模板中不能出现重名的block,如果一个地方需要用到另外一个block中的内容,可以使用self.blockname的方式进行引用,如以下代码所示。
<title> {% block title %} 这是标题 {% endblock %} </title> <h1>{{ self.title() }}</h1>
在以上示例代码中,h1标签中通过{{ self.title() }}把title这个block中的内容引用到了h1标签中。
如果子模板要继承父模板中某个block的内容,如以上案例中,如果要继承父模板footer这个block中已有的内容,则可以通过super()来实现,示例代码如下。
{% block footer %} {{ super() }} // 子模板自己的代码 {% endblock %}
以上代码中,如果没有使用{{ super() }},那么子模板将不能继承父模板footer这个block中的内容。
有些HTML模块需要在几个页面中使用,如果用模板继承会在所有子模板中都加载,不太合适;如果在每个需要使用这个HTML结构的页面中都重复相同的代码,会增加后期项目的维护成本。这时就可以通过include语法引入模板。如在网站中推广客服二维码联系方式,在有些页面中需要使用,但是并不是所有页面都需要,因此可以把相关代码写成_contact.html文件,然后在需要的位置进行引用即可,示例代码如下。
{% include "_contact.xhtml" %}
因为_contact.html是作为被引用的模板而存在的,所以一般在命名前加一个下画线,这样可以和普通的页面模板区分开来。