高级页面模板3
第九章 高级页面模板
翻译: 杜文山 / 邮件: zope@dohao.org
如有不妥,欢迎批评指正
+9.9 Macros(宏)
到此为止,你已经看到了页面模板如何给独立的web页面加入动态的行为。页面模板的另外一个特性是许多页面可以重复使用外观和风格元素。
例如,使用页面模板,网站就可以有一致的外观和风格。不管页面的内容是什么,都将有一致的页眉,按钮条,页脚,以及其它的页面元素。对于web站点来说,这是一种非常普遍的要求。
你可以通过使用macros,你可以在多个页面里重复使用表现元素。Macros定义了多个页面之间共性的部分。一个macro可以为一个整个页面,或者仅为页面的一部分,比如页眉或页脚。当你在一个页面模板里边定义一个或多个macro以后,就可以在其他页面模板里边使用它们。
+9.9.1使用macro
你可以通过类似于TAL语句的标记符属性来定义macro。Macro标记符属性被称为macro扩展标记符属性语句(Macro Expansion Tag Attribute Language (METAL))。以下是一个定义macro的例子:
<p metal:define-macro="copyright">
Copyright 2001, <em>Foo, Bar, and Associates</em> Inc.
</p>
其中的metal:define-macro语句定义了一个名为"copyright"的macro。这个macro由p 和内容(包括所有被包括的标记符)组成。
在页面模板里定义的macro存储在模板的macro属性里边。你可以通过指向在其他模板里定义的macro属性来使用macro。例如,假设copyright这个macro位于一个名为"master_page"的页面模板里边,以下显示了如何在另外一个页面模板里调用这个macro:
<hr>
<b metal:use-macro="container/master_page/macros/copyright">
Macro goes here
</b>
在这个页面模板里,b标记符在Zope执行这个页面时将完全用macro替换:
<hr>
<p>
Copyright 2001, <em>Foo, Bar, and Associates</em> Inc.
</p>
如果你更改了macro(例如,名称变了),那么使用了这个macro的页面模板都会自动反映出这种变化。 需要注意的是macro是如何在path表达式里通过使用metal:use-macro语句来识别的。metal:use-macro语句用macr替换语句的元素。o
+9.9.2 Macro细节
metal:define-macro 和 metal:use-macro语句还是相当易用的,但是有一些注意事项::
Macro的名称在其被定义的页面模板里边必须是唯一的。你可以在一个模板里定义多个macro,但他们都需要是不同的名字。
一般你通过一个path表达式里用metal:use-macro语句来调用一个macro。然而,只要表达式返回一个macro, 就可以使用任何类型的表达式:
<p metal:use-macro="python:here.getMacro()">
Replaced with a dynamically determined macro,
which is located by the getMacro script.
</p>
使用表达式来定位macro,可以让你动态的确定模板使用那一个macro。
你可以通过metal:use-macro 语句使用default变量:
<p metal:use-macro="default">
This content remains - no macro is used
</p>
这个结果与使用tal:content 和 tal:replace 语句是一样的,语句元素不变。
如果你试图通过metal:use-macro使用nothing变量,会得到一个错误,这是由于nothing不是一个macro。 如果你想使用nothing来有条件的包含一个macro,你应该用一个tal:condition语句合拢metal:use-macro语句。
Zope执行模板时会先处理macros。那么Zope对TAL表达式求值。例如,看以下的这个macro:
<p metal:define-macro="title"
tal:content="template/title">
template's title
</p>
当你使用这个macro,它将插入使用macro的那个模板的标题,而不是定义macro的模板的标题。换句话说, 当你使用一个macro,就像是把macro的文字复制到模板里,然后执行你的模板。
如果你选中了页面模板在Edit视图里的Expand macros when editing选项,那么你使用的任何macro都将在模板源文件里展开。这是Zope的默认行为,并且通常这是需要的,这是由于它允许你编辑完整而有效的页面。但某些时候,特别是当你正在ZMI里编辑时,而不是使用WYSIWYG编辑工具,这时不展开则会更方便。此时,只要不选中这个选项就可以了。
+9.9.3 使用slots
当你使用macro时如果能够覆盖其中的某一部分,macro就更显得有用了。实现这个功能,可以通过在macro里定义slots的方式实现,这样当你使用模板时就可以填充它。例如,考虑一个栏目条macro:
<p metal:define-macro="sidebar">
Links
<ul>
<li><a href="/">Home</a></li>
<li><a href="/products">Products</a></li>
<li><a href="/support">Support</a></li>
<li><a href="/contact">Contact Us</a></li>
</ul>
</p>
这个macro不错,但假设你希望在某些页面里的栏目条里加入一些附加信息。使用slots实现这个功能的一种方式是:
<p metal:define-macro="sidebar">
Links
<ul>
<li><a href="/">Home</a></li>
<li><a href="/products">Products</a></li>
<li><a href="/support">Support</a></li>
<li><a href="/contact">Contact Us</a></li>
</ul>
<span metal:define-slot="additional_info"></span>
</p>
当你使用这个macro,你可以这样来填充slot:
<p metal:fill-slot="container/master.html/macros/sidebar">
<b metal:fill-slot="additional_info">
Make sure to check out our <a href="/specials">specials</a>.
</b>
</p>
当你执行这个模板,栏目条会包含你在slot里提供的额外信息:
<p>
Links
<ul>
<li><a href="/">Home</a></li>
<li><a href="/products">Products</a></li>
<li><a href="/support">Support</a></li>
<li><a href="/contact">Contact Us</a></li>
</ul>
<b>
Make sure to check out our <a href="/specials">specials</a>.
</b>
</p>
注意定义slot的span元素是如何被slot填充b元素的。
+9.10 定制默认的外观
Slot的常见用途是来为可以定制的页面提供默认的外观。在上一部分里边的slot例子里,slot定义仅是一个空的span元素。然而,你可以在slot定义里提供默认的外观。例如,看以下这个修改过的栏目条macro:
<div metal:define-macro="sidebar">
<p metal:define-slot="links">
Links
<ul>
<li><a href="/">Home</a></li>
<li><a href="/products">Products</a></li>
<li><a href="/support">Support</a></li>
<li><a href="/contact">Contact Us</a></li>
</ul>
</p>
<span metal:define-slot="additional_info"></span>
</div>
现在,这个栏目条可以充分定制。你可以填充links slot来重新定义工具条链接。然而,如果你不填充links slot,那么,你将得到默认的链接。
你甚至可以通过在slots里定义slots,进一步使用这种技巧。这样就可以让你覆盖默认的外观。以下是一个在slot内部定义slot的栏目条::
<div metal:define-macro="sidebar">
<p metal:define-slot="links">
Links
<ul>
<li><a href="/">Home</a></li>
<li><a href="/products">Products</a></li>
<li><a href="/support">Support</a></li>
<li><a href="/contact">Contact Us</a></li>
<span metal:define-slot="additional_links"></span>
</ul>
</p>
<span metal:define-slot="additional_info"></span>
</div>
如果你想定制栏目条链接,你可以填充links slot来完全覆盖现有的链接,还可以填充additional_links slot来在默认链接后边插入一些额外的链接。你可以任意嵌套slot。
+9.11 混合METAL和TAL
在相同的元素里可以同时使用METAL和TAL。例如:
<ul metal:define-macro="links"
tal:repeat="link here/getLinks">
<li>
<a href="link url"
tal:attributes="url link/url"
tal:content="link/name">link name</a>
</li>
</ul>
由于METAL语句在TAL语句前求值,这里没有冲突。同时, 这个例子没有使用slot来定义macro。Macro调用getLinks脚本来决定链接。这样你就可以在站点里不同部分通过getLinks脚本来定制站点的链接。
通常, 在网站的不同部分都定制出最好的观感, 并不总是件容易的事情。一般来说,你应该使用slots来重载外观元素,并且使用脚本动态提供内容。在上边的例子里,需要确定链接是内容还是外观。通常脚本提供了一种更为灵活的方案,特别是如果你的站点包含链接内容对象。
+9.12 全页面Macros
使用macros不仅仅可以在不同页面之间共享外观元素,更可以使用macros来定义整个页面。Slots可以帮助实现这个功能。以下是一个定义整个页面的macro:
<html metal:define-macro="page">
<head>
<title tal:content="here/title">The title</title>
</head>
<body>
<h1 metal:define-slot="headline"
tal:content="here/title">title</h1>
<p metal:define-slot="body">
This is the body.
</p>
<span metal:define-slot="footer">
<p>Copyright 2001 Fluffy Enterprises</p>
</span>
</body>
</html>
这个macro定义一个带有三个slots, 即headline, body, 和 footer。注意headline slot是如何通过一个TAL语句来动态确定headline内容。
你就可以在页面里使用这个macro来表现不同类型的内容,或者站点不同的部分。例如,以下是一个模板如何使用这种macro来表现新闻条目的:
<html metal:use-macro="container/master.html/macros/page">
<h1 metal:fill-slot="headline">
Press Release:
<span tal:replace="here/getHeadline">Headline</span>
</h1>
<p metal:fill-slot="body"
tal:content="here/getBody">
News item body goes here
</p>
</html>
这个模板重新定义了headline slot,从而包含了单词"Press Release",并且对当前对象调用getHeadline 方法。他还重新定义了body slot ,从而对当前对象调用getBody 方法。
这种方法强大的一方面在于你可以更改页面macro,这样press release模板就会自动更新。例如,你应该表页面正文放入表格里,并在左边加入一个栏目条,press release模板就会自动使用这些新的外观元素。
比起DTML里边的standard_html_header 和 standard_html_footer方法,这是一种更为灵活的解决方式。事实上,Zope在root文件夹里带有一个名为standard_template.pt 的stock页面模板,它包含一个带有head和body slot的页面macro。以下显示了如何在模板里使用这个macro:
<html metal:use-macro="here/standard_template.pt/macros/page">
<div metal:fill-slot="body">
<h1 tal:content="here/title">Title</h1>
<p tal:content="here/getBody">Body text goes here</p>
</div>
</html>
使用standard_template.pt macro非常类似于使用其他的全页面macros。唯一需要说明的是用来定位macro的路径。在这个例子里,路径用here开始。这样,Zope就会使用获取机制来搜索standard_template.pt对象,从应用模板的那个对象所处位置开始。这样允许你通过在各个位置创建特定的standard_template.pt对象来定制外观和感觉。这种方法就像覆盖站点不同位置的standard_html_header 和 standard_html_footer来定制外观和感觉。然而,使用standard_template.pt,你可以有更多的选择。你可以像采用here那样,对root或container给macro起用path。如果path由root开始,那么你将总是得到位于root文件夹里边的标准模板。如果path由container开始,那么Zope将通过使用获取机制搜索标准模板,开始的文件夹是定义模板的文件夹。这样允许你定制模板的外观和感觉,但不允许你定制不同位置的对象的外观和感觉。
+9.13 缓存模板
通常执行页面模板是相当快的,有时不是足够快。对于经常访问的页面或需要长时间执行的页面,你需要适应速度的动态行为。缓存可以帮助你实现这些。关于缓存,可以查看第三章“基本对象”里边的“ 缓存管理器”部分。
你可以像缓存其他对象一样使用缓存管理器来缓存页面模板。要缓存页面模板,你必须用一个缓存管理器关联它。实现这点,你可以进入模板的Cache视图,并选择缓存管理器,还可以进入缓存管理器的Associate视图,然后选择你的模板。
以下是一个如何缓存页面模板的例子。首先,创建一个基于Python 的脚本long.py ,内容如下:
## Script (Python) "long.py"
##
for i in range(500):
for j in range(500):
for k in range(5):
pass
return 'Done'
这个脚本的目的是要限制执行时间。现在,创建一个使用这个脚本的页面模板,例如:
<html>
<body>
<p tal:content="here/long.py">results</p>
</body>
</html>
现在观看这个页面,注意它花费了一些执行时间。现在,让我们用缓存来提高执行速度。先创建一个Ram Cache Manager。并确定它创建在与页面模板相同的文件夹里,或者在更高一级的目录里。现在观看Cache视图。选择刚才创建的Ram Cache Manager,然后点击Save Changes。单击Cache Settings可查看配置情况。默认的情况下,缓存内的对象存储时间为一个小时(3600秒)。根据你的应用程序的需要,你可以调整这个数字。现在返回你的页面模板,再次观看它。这个过程会花费点时间来处理。现在重新调用这个页面,就会发现立刻就出来了。你可以一次又一次的调用这个页面,它总是迅速的出来,这就是缓存的作用。
如果你更改页面模板,它就会从缓存里删除。因此,下一次观看它时,就会花费点时间来处理。当它再次被存入缓存后,就会快了。
缓存是一种简单而非常强大的增进性能的技术。使用缓存很容易,并且可以明显的提高速度。那些对性能要求高的应用程序,使用缓存是非常值得的。
+9.14 页面模板工具
Zope页面模板强大而简单。它不象DTML,页面模板不提供一些便利的特性,比如批块处理,绘制树结构,排序等等。页面模板的创建者想让它简单。这样的话,就可能失去一些DTML提供的内建特性。为了满足这些需要,Zope提供了一些用于加强页面模板的工具。
+9.14.1 批块化处理巨大的信息集合
当一个用户查询数据库,得到上百个结果,比起在一个页面里显示这些结果,有一种通常更好的显示方式,那就是采用多个每页只显示20个结果的页面。把巨大的列表分割为多个小的列表就被称为批块化。
页面模板不象DTML那样把批块化功能内建到语句里边,页面模板支持批块化是通过使用一种特殊的批块对象,这种对象由ZTUtils 工具模块提供。有关这个模块的更多信息请参见附录B“API参考”。
以下是一个简单的例子,显示了如何创建一个批块对象(Batch object):
<ul tal:define="lots python:range(100);
batch python:modules['ZTUtils'].Batch(lots,
size=10,
start=0)">
<li tal:repeat="num batch"
tal:content="num">0
</li>
</ul>
这个例子按照每次10个条目来处理列表(数字从0到9)。批块对象把一个长的列表转化为群组或批块。在这个例子里,是把含有一百个条目的列表分成多个包含10个条目的批块。
你可以通过传递一个不同的其始数字来显示不同的批块:
<ul tal:define="lots python:range(100);
batch python:modules['ZTUtils'].Batch(lots,
size=10,
start=13)">
这个批块从第14个条目开始,并以第23个条目结束。换句话说,它显示的数字是从13到22。需要注意的是批块的start参数是第一个条目的索引(index)。索引从0开始计算,而不是从1。所以,索引13代表的是序列里边第14个条目。Python使用索引来指向列表条目。
通常,当你使用批块时,你需要使用导航元素来让用户在批块之间进行跳转。以下是一个说明如何在多个批块之间进行导航的例子:
<html>
<head>
<title tal:content="template/title">The title</title>
</head>
<body tal:define="employees here/getEmployees;
start python:path('request/start') or 0;
batch python:modules['ZTUtils'].Batch(employees,
size=10,
start=start);
previous python:batch.previous;
next python:batch.next">
<p>
<a tal:condition="previous"
tal:attributes="href string:${request/URL0}?start:int=${previous/first}"
href="previous_url">previous</a>
<a tal:condition="next"
tal:attributes="href string:${request/URL0}?start:int=${next/first}"
href="next_url">next</a>
</p>
<ul tal:repeat="employee batch" >
<li>
<span tal:replace="employee/name">Bob Jones</span>
makes $<span tal:replace="employee/salary">100,000</span>
a year.
</li>
</ul>
</body>
</html>
这个例子采用一个名为getEmployees 的ZSQL方法来处理批块数据。它根据需要来显示previous 和next链接,这样就可以通过每次浏览一个批块的方式来查看所有的结果。
让我们看看body元素里边的tal:define语句。它定义了一组批块变量。变量employees 是一个大的雇员对象的列表,它由名为getEmployees 的ZSQL方法返回的。第二个变量start被设为request/start 的值,或当request里边没有start变量时值为0。变量start跟踪你正在观看的雇员列表的位置。变量batch是一个含有10个条目的批块。这个批块从变量start指定的位置开始。变量previous 和 next指向前一个和后一个批块(如果有的话)。由于所有这些变量是在body元素里边定义的,因此它适用于body内部所有的元素。
接下来,让我们看看导航链接。它们创建可以浏览前一个和后一个批块的链接。语句tal:condition首先来判断是否存在前一个和后一个批块。如果有前一个或后一个批块,那么就显示链接,否则为空。语句tal:attributes创建前一个和后一个链接。链接根据含有批块起始索引的查询字符串,来显示为简单的URL或者当前的页面(request/URL0)。例如,如果当前的批块由索引10开始,那么前一个批块将从索引0开始。批块的first变量提供开始的索引,这样在这个例子里,前一个批块将从0开始。
全部理解这个例子的工作原理并不重要。先复制它,或者使用一个由Z Search Interface 创建的批块例子。然后,当你需要更为复杂的批块处理时就可以通过更改这个例子来实验。别忘了参考附录B“API参考”,其中可以查看ZTUtils模块和Batch对象。
+9.14.2其它的工具
Zope提供了一些Python模块,这些模块在使用页面模板时会很方便。string, math, 和 random模块可以在Python表达式里分别用于字符串处理,数学函数和随机数字的生成。这些模块同样存在于DTML和基于Python的脚本里边。有关这些模块的更多信息请参见附录B“API参考”。
Products.PythonScripts?.standard模块用于提供基于Python的脚本工具,同样适用于页面模板。它包含了各种字符串和数字处理函数。相关信息请参见附录B“API参考”
就像本章前面提到的那样,序列模块提供了一种方便的排序功能。详细请见附录B“API参考”。
AccessControl?模块包含了一个函数和类,它们可以用于测试权限,并得到授权的用户。更多信息,请参见附录B“API参考”
结论
这一章讲到了页面模板的方方面面,读后,你可能感觉有一些多。不用担心,要想有效的使用页面模板,你不必掌握本章所讲述的全部内容。你应该理解path类型和macros的不同,剩下的内容可以在需要的时候再回来查看。本章所学习的内容在需要的时候再查看是可以的,这些内容为你提供了需要知道的页面模板制作技巧。
