Page Template
如何產生動態網頁內容,是程式人員關心的議題,常見的例子,像是在網頁上顯示今天的日期,或是顯示來自資料庫的欄位內容。Plone 使用 Python 程式語言來完成資料的計算或存取,接著透過 Zope Page Template (ZPT) 或稱為 Page Template (PT) 工具,來完成資料的顯示。
另一方面,由於 ZPT 相容 XHTML 語法,能跟版面設計人員的工具結合,例如 Dreamweaver 之類的軟體,可以無誤地讀取及展示 Page Template 的內容,不會被當中的程式碼干擾,讓程式人員與版面設計人員能夠無縫地同步合作。
設計目的
讓程式人員與版面設計人員緊密合作,互不干擾,是 Page Template 的特色之一。程式人員為了產生動態網頁內容,需要在 HTML 裡嵌入程式碼,但版面設計人員在修改網頁時,可能會蓋掉舊有的程式碼,造成分工合作的困擾,這是多數網頁產生工具常見的問題。
除去動態內容的控制語法後,Page Template 的格式跟 HTML 長得一樣,它以 XHTML 為基礎,也就是相容於 XML 標準的 HTML 語法,XHTML 要求程式碼符合 well formed 格式,例如使用 <img />、<span></span> 這樣的格式,還有正確的巢狀結構,並使用小寫英文的標籤格式。
XHTML 允許程式人員依照需求建立客制化標籤,Page Template 也利用這項特點,建立 <tal:block></tal:block> 或 <metal:block></metal:block> 之類的語法,以便強化動態網頁的彈性,其中的 TAL 和 METAL 分別是 Page Template 使用的語法名稱。
表面上,Page Template 扮演的角色跟 ASP、JSP、PHP 很像,但它們的設計目的並不同,簡言之,Page Template 有下列三項目的:
- 與網頁編輯工具 (如 Dreamweaver) 完美搭配。
- 所視如所得 (What You See Is Similar To What You Get)。
- 除了語法結構邏輯外,儘量將程式碼移出 HTML 檔案裡。
使用 Page Template 後,版面設計人員可以先使用自己所熟悉的 WYSIWYG HTML 編輯器,例如 Dreamweaver,來快速完成網頁初稿,然後交由程式人員在當中嵌入特定的程式碼,如果有必要,版面設計人員可以再次載入已嵌入程式碼的 HTML 檔案,繼續進行網頁的修改工作,只要遵守一些簡單的原則,版面設計人員並不會影響或破壞程式人員所設計的邏輯部份。
Page Template 只處理網頁的文件結構,並不能用來建立 subroutine 或 class 等複雜的程式功能,想要完成這類的效果,請優先考慮撰寫 Python 程式。
安裝方式
Page Template 本身就能獨立運作,使用者可以從 http://zpt.sourceforge.net/ 網址下載新版軟體。不過,無須如此費力,只要安裝 Zope 2.5.0 版本以上,就可以完整擁有 Page template 操作環境,包括 TAL、ZTUtils、PageTemplates。以 Zope2 的檔案系統為例,其相關程式位置如下:
- $ZOPE2_HOME/lib/python/TAL
- $ZOPE2_HOME/lib/python/ZTUtils
- $ZOPE2_HOME/lib/python/Products/PageTemplates
更多 Page Template 的完整資訊,可參考 http://wiki.zope.org/ZPT/FrontPage 內容。
運作原理
幾乎每個 XHTML 的標籤,都有 id、class、title 的屬性元素,它們用來描述標籤的細部資訊,例如 <img src="" alt="" /> 之類的語法。
Page Template 使用的語言稱為 Template Attribute Language (TAL),內含許多描述屬性資訊的標籤語法,下列即是 TAL 語法範例:
<h1 tal:content="here/title">Page Title</h1>
其中 <h1 ...></h1> 部份是原本的 HTML 語法,tal: 的部份則是 XML namespace,TAL 語法都會以 tal: 為首,content 部份用來指明是設定 <h1> 的屬性,而 "here/title" 就是 TAL 的 expression 內容,這類的 expression 被規定在 TAL Expression Syntax (TALES) 裡。當版面設計人員使用 Dreamweaver 工具,讀取上述 HTML 原始碼時,並不會造成無法解讀的困擾,畫面上會出現「Page Title」的標題字樣。
當這樣的 Page Template 檔案存放於 Zope 系統裡,它會搭配物件的屬性與物件方法,動態更換標題字樣,或產生其他更複雜的運算結果,甚至是邏輯及錯誤處理等。不過,Page Template 不應該拿來處理 subroutine 或 class,這類的程式功能,應該利用 Python Script 才好。舉例來說,Page Template 適於拿來產生會員資料報表,但卻不適於儲存記錄到資料庫。
新增與編輯
建立 Page Template 的方法,依照場合不同,至少有下列三種:
- 直接從管理介面 (Zope Management Interface, ZMI) 的物件新增選單,點選「Page Template」。初學或是撰寫簡短 Page Template 的場合,適合使用這種方法。
- 透過 FTP 或 WebDAV 來編輯。對於網頁設計人員而言,搭配網頁編輯工具來處理 Page Template 檔案內容。
- 在 filesystem 編輯 Page Template 檔案。通常是搭配 Vim 或 Emacs 等工具來編輯。
以下是 ZMI 環境裡的操作範例:
- 在 root folder 裡,點選 Page Template 後,Id 欄位填寫「simple_page」,然後點選「Add and Edit」按鈕。
- 出現編輯畫面,此時 Title 欄位為空白,Content-Type 欄位預設值為 text/html,進階功能可設定為 text/xml,此處使用 text/html 即可,文字方框區域則產生預設之資料內容。
- 請在 Title 欄位填寫「a Simple Page」,然後文字方框內只填寫如下的內容:
This is <b tal:replace="template/title">the Title</b>.
- 點選「Save Changes」按鈕,順利的話,會發現畫面上提示修改成功。
以上是新增 Page Template 最簡化的範例步驟。
注意:如果出現錯誤訊息,或是看到 <-- Page Template Diagnostics 的字樣,請依照提示訊息,檢查是否有打錯字之類的錯誤。只要修訂之後,上述的額外字樣會自動消失。
先略過 TAL 語法的話,Page Template 的內容,就跟一般 HTML 一樣。在編輯畫面的右上方,有個 Browse HTML source 項目,點選後的畫面會顯示「This is the Title.」的字樣,這就是略過 TAL 語法的 HTML 顯示效果。
再回到編輯畫面,點選上方的「Test」頁籤,畫面會顯示「This is a Simple Page」的字樣,這是 TAL 語法被執行後的畫面效果。
試著把 replace 改成 content,也就是,文字方框內的內容改成如下,觀察有何不同:
This is <b tal:content="template/title">the Title</b>.
注意:在 TAL 語法裡,template/title 跟 context/title 是不同的,前者表示要回傳 Page Template 本身的標題值,後者表示要回傳物件的標題值。多數的場合下,Page Template 都是扮演物件 page view 的角色,因此常見使用 context/title 的表示式,也就是說,物件執行 Page Template 時,context/title 代表要回傳物件的標題值,而不是 Page Template 的標題值。
基本語法
在 Plone 裡常見的語法範例:
- request/URL : 目前網址的 URL。
- user/getUserName : 登入使用者的帳號名稱。
- container/objectIds : template 所在目錄裡所有物件的 Id。
插入文字
The URL is <span tal:replace="request/URL">URL</span>.
迴圈結構
<table border="1" width="100%"> <tr> <th>#</th><th>Id</th><th>Meta-Type</th><th>Title</th> </tr> <tr tal:repeat="item container/objectValues"> <td tal:content="repeat/item/number">#</td> <td tal:content="item/id">Id</td> <td tal:content="item/meta_type">Meta-Type</td> <td tal:content="item/title">Title</td> </tr> </table>
條件判斷
<table border="1" width="100%"> <tr> <th>#</th><th>Id</th><th>Meta-Type</th><th>Title</th> </tr> <tbody tal:repeat="item container/objectValues"> <tr bgcolor="#EEEEEE" tal:condition="repeat/item/even"> <td tal:content="repeat/item/number">#</td> <td tal:content="item/id">Id</td> <td tal:content="item/meta_type">Meta-Type</td> <td tal:content="item/title">Title</td> </tr> <tr tal:condition="repeat/item/odd"> <td tal:content="repeat/item/number">#</td> <td tal:content="item/id">Id</td> <td tal:content="item/meta_type">Meta-Type</td> <td tal:content="item/title">Title</td> </tr> </tbody> </table>
定義變數
前面的範例裡,在沒有物件的情況下,整個 table 都不會被包含在輸出結果裡。但是,有物件要處理時,"container/objectValues" 會被執行兩次,造成無謂的效能浪費,而且,日後想要修改程式碼的話,必須重覆調整兩個地方。
為了避免上述問題,可以定義一個變數,用來記錄執行的結果,範例如下:
<table border="1" width="100%" tal:define="item container/objectValues" tal:condition="items"> <tr> <th>#</th><th>Id</th><th>Meta-Type</th><th>Title</th> </tr> <tbody tal:repeat="item items">
同樣的道理,想要取得物件的識別碼,就要使用 context/getId 表示式,下列是個範例:
<tal:define="itemId context/getId; itemIdUpper python: itemId.upper()"> <a href="" tal:content="itemUpper" tal:attributes="href python: 'http://www.google.com/search?q='+itemIdUpper; title itemIdUpper;">Google Search</a>
Path Expression
以 "template/title" 為例,它是 path expression 的一項範例,也是 TAL 語法裡相當常見的例子,它會讀取 template 物件的 title 屬性值,以供 TAL 語法裡其他指令內容使用。其他有用的 path expression 例子還有:
- request/URL - 傳回值類似 http://localhost:8080/MySite/my-page。
- user/getUserName - 登入時就傳回使用者帳號名稱,未登入時就傳回 Anonymous User。
- container/news/objectIds - 傳回 news 目錄裡所有物件的識別碼。
path expression 語法結構以「一個變數名稱」為首,如果想要的屬性值已經取得,語法解析的動作便會停止,不然可以利用「/」(斜線) 符號,再接必要的子物件或屬性名稱,例如 container/news/objectIds 或 template/title 等,此處的 container 或 template 是內建變數名稱,而 objectIds 是物件方法 (method),title 則是屬性名稱。
文字內容的替換
上述範例中的 tal:replace 是用來「執行文字內容替換工作」,
它不一定會出現在 <b> 的 HTML 標籤中,較常見的使用方式,是出現在 <span> 的 HTML 標籤內,如下例:
The URL is <span tal:replace="request/URL">URL</span>.
<span> 在 HTML 語法裡屬於「結構式標籤」,意謂它所代表的 HTML 效果,並無任何視覺上的影響。其網頁結果應顯示如下:
The URL is http://localhost:8080/simple_page
使用 tal:replace 時要注意,標籤裡的內容會全部被取代,所以下列的例子中:
The URL is <span tal:replace="request/URL"><font color=red>URL</font></span>.
<font ...> 標籤的功能便無用武之地,因為同樣會受 tal:replace 影響而被取代。
如果想要在進行文字內容替換動作時,依舊保留著 HTML 標籤,應該改用 tal:content,而非 tal:replace。下列是一個設定 title 屬性值的例子:
<head> <title tal:content="template/title">The Title</title> </head>
迴圈語法
迴圈是非常實用而強大的功能,通常用於批次資料的循序處理,
並搭配 HTML 語法裡的 <table ...>、<tr>、<th>、<td> 系列標籤,以達到資料操弄與表格彙報的效果。熟悉 DTML 的使用者自然難以忘懷 <dtml-in ...> 的迴圈功能,在下列例子中,將看到 ZPT 如何完成同樣的工作。
在 Zope Management Interface 裡,可以使用 Examples (Example Applications) 來練習,請在 root folder 裡找一個名為 Examples 的 folder 目錄。
結構式內容
使用 tal:replace 與 tal:content 的時候,內容要是包含「<>」之類的 HTML 標籤,會被轉換成「< >」。如果我們想要取用「未被轉換的資料」,語法裡就要引用 structure 這個關鍵字,例如:
<p replace="structure here/story"> the <b>story</b> </p>
這項語法屬性值適用於讀取 HTML 或 XML 資料格式的場合,舉例來說,假設系統裡已存放一些 news item 物件,這些物件內含部份的 HTML 標籤,下列範例可用來讀取這類的資料:
<p tal:repeat="newsItem here/topNews" tal:content="structure newsItem"> A news item with <code>HTML</code> markup. </p>
裝飾式內容
有時候,你需要使用一些標籤,讓它們在 template 裡出現,但實際產生之 HTML 結果中是不存在的。此時所引用的變數是 nothing,範例如下:
<tr tal:replace="nothing"> <td>10213</td> <td>Example Item</td> <td>$15.34</td> </tr>
舉例來說,你想安排一份顯示資料庫內容的網頁,每張網頁畫面上預計顯示十筆 (即十排資料列),雖然在 template 裡設計一排資料列即可正常運作,但為求畫面整齊美觀,有需要額外製造出九筆「裝飾用的資料列」。此時可考慮應用 nothing 變數的技巧。
預設式內容
有時候,你可以不進行文字內容的替換,而直接保留標籤內的文字,下列即是一個簡單的範例:
<p tal:content="default">Spam<p>
不過,較常見的例子是「選擇性的預設式內容」,處理方式如下:
<p tal:content="python:here.getFood() or default">Spam</p>
它使用了 Python 表示式,以 pytohn: 為首,後頭接了 here.getFood() 這樣的物件方法,再接了一個 or 以及 default 變數,意指「當 here.getFood() 物件方法回傳非真之值時,便使用預設值」。
忽略式語法
忽略式語法以 tal:omit-tag 表示,此項語法並不常被使用到,但常搭配 tal:repeat 語法併用,下列是一個簡單的範例:
<span tal:repeat="n python:range(10)" tal:omit-tag=""> <p tal:content="n">1</p> </span>
雖然觀察 HTML 原始碼,只會看到一個 <p> 標籤區段,但實際上會搭配 Python 表示式的變數個數,重覆執行 <p> 標籤區段內容。
進階迴圈技巧
下列是一個九九乘法表的動態網頁程式,同樣使用了 Python 表示式,以及一個 omit-tag 的語法:
<table border="1"> <tr tal:repeat="x python:range(1, 10)"> <div tal:repeat="y python:range(1, 10)" tal:omit-tag=""> <td tal:content="python:'%d x %d = %d' % (x, y, x*y)"> X x Y = Z </td> </div> </tr> </table>
上述的程式碼中,x 與 y 分別是迴圈裡的整數串列變數。
排序功能
將迴圈結果進行排序,最後顯示在網頁畫面上,屬於進階的功能,必須借助 sequence.sort 特別的物件方法,以下即是一個修改自前述範例的迴圈程式:
<table tal:define="objects container/newfolder/objectValues; sort_on python:(('getId', 'nocase', 'asc'), ('bobobase_modification_time', 'cmp', 'desc')); sorted_objects python:sequence.sort(objects, sort_on)" border="1" width="100%"> <tr> <th>Number</th> <th>Id</th> <th>Meta-Type</th> <th>Title</th> </tr> <tr tal:repeat="item sorted_objects"> <td tal:content="repeat/item/number">#</td> <td tal:content="item/getId">Id</td> <td><img src="/misc_/OFSP/Folder_icon.gif" tal:attributes="src item/icon"> <span tal:replace="item/meta_type">Meta-Type</span></td> <td tal:content="item/title">Title</td> </tr> </table>
程式碼一開始,以 tal:define 語法定義了 objects、sort_on、sorted_objects 三個變數,由於變數個數超過一個,因此必須以「; 符號」來分隔每一個變數定義。sort_on 變數設定的是排序的條件,分別是依照 id 大小,以及修改時間的順序。
TAL 語法的執行順序
當一個 TAL 語法只有一行敘述時,分析及執行都很容易了解,但當一個 TAL 語法包含多行敘述時,其執行順序如下:
- define
- condition
- repeat
- content or replace
- attributes
- omit-tag
下列即是一個多重敘述的 TAL 語法:
<p tal:define="x /root/a/long/path/x | nothing" tal:condition="x" tal:content="x/txt" tal:attributes="class x/class">Ex Text</p>
當使用多重敘述時,應該要注意下列幾個事項:
- 每一種語法,在同一個標籤裡,只能被使用一次。例如,你不能在同一個標籤裡重覆使用 tal:define,但可以 define 多個變數,以「; 符號」將變數隔開。
- tal:content 與 tal:replace 不能使用在同一個標籤裡,因為它們的功能彼此互斥。
- 你安排的語法順序,並不會影響 TAL 被執行的順序,TAL 實際執行的順序,會固定依照上述的順序進行。
搭配 Plone 環境的撇步
Page Template 搭配 Plone 環境來執行,是常見的應用方式,最基本的結合方式,就是呼叫 main_template 的 master macro 產生 Plone 佈景主題的畫面,請先查看 Products.CMFPlone/browser/templates/main_template.pt 內容的開頭:
<metal:page define-macro="master">
先有 define-macro 完成定義,再利用 use-macro 來載入定義內容,範例如下:
<html metal:use-macro="here/main_template/macros/master"> <head> <title tal:content="context/title">The title</title> <meta http-equiv="content-type" content="text/html;charset=utf-8"> </head> <body> <metal:main fill-slot="main"> <h2 tal:content="context/title">content title</h2> <p tal:content="python: '%d' % (2**10)">the answer</p> </metal:main> </body> </html>
接著在 main_template.pt 可以看到 Title Description 之後,有 define-slot="content-core" 用來顯示 Page Body Text
<metal:text define-slot="content-core" tal:content="nothing"> Page body text </metal:text>
在 plone.app.contenttypes/browser/templates 可發現常見的套用範例,像是 listing.pt 或 full_view.pt 等,都透過 <metal:content-core fill-slot="content-core"> 來套用樣版。以 full_view.pt 為例,可以先 define-macro 再 use-macro,這方法可以額外加進想要的 uuid 定義值:
<metal:block define-macro="content-core" tal:define="uuid context/@@uuid | nothing"> <metal:listing use-macro="context/@@listing_view/macros/content-core">
如果不想顯示 content tab 欄位,也就「內容」「檢視」「編輯」等選項,可以指定 disable_border 變數,範例如下:
<html metal:use-macro="here/main_template/macros/master"> <head> <title tal:content="context/title">The title</title> <meta http-equiv="content-type" content="text/html;charset=utf-8"> </head> <body> <metal:block fill-slot="top_slot" tal:define="dummy python:request.set('disable_border', 1)" /> <metal:main fill-slot="main"> <h2 tal:content="context/title">content title</h2> <p tal:content="python: '%d' % (2**10)">the answer</p> </metal:main> </body> </html>
想要比對某個變數字串,符合才能看到內容的話,可使用下列範例:
<div tal:condition="python: context.dstrct() == u'某字串'"> <span tal:replace="string: some words" /> </div>
想要指定 login user 才能看到內容的話,可以使用下列程式碼片段範例:
<span tal:define="isAnon context/@@plone_portal_state/anonymous"> <p tal:condition="python: not isAnon"></p> </span>
想要指定 home page 為條件的話,可以使用下列程式碼片段範例:
tal:define="state context/@@plone_context_state; is_home python:state.is_portal_root() and state.is_default_page()"
想要指定 redirection 轉址功能的話,可以使用下列程式碼片段範例:
<tal:redirect define="dummy request.RESPONSE.redirect(new_absolute_location)" />
tal:condition="python:request.URL.find('@@sharing') != -1"
tal:define="subdomain python:domain.partition('.')[0]"
比對 Text 欄位是否有內容:
tal:define="has_text exists:context/aq_explicit/text/output; text python:has_text and here.text.output or ''" tal:condition="text"
在 view.pt 裡 head slot 嵌入 CSS 或 JavaScript 的範例。collective.fingerpointing
Resource Registry Condition to Differentiate Authors and Users
expression="python:portal['portal_membership'].checkPermission('Modify portal content', context)"
error_image here/linkOpaque.png|here/linkOpaque.gif;
no call 想要取用 method 卻沒有想要執行它
a href="#" tal:condition="python:request.URL.find('current') != -1" tal:attributes="href string:index_html" 返回地圖檢視模式/a
不管 condition 是否成功,第二行都會繼續被執行,造成 file/filename 找不到的問題,處理方式是拆成兩段來執行。
<div tal:condition="context/file" tal:define="file/filename">
學習資源
ZPT 用到的 METAL (Macro Expansion for TAL) 巨集語法,目的是在 run time 讀進設定好的程式碼片段,本身並不能傳遞變數值。
如果你還未開始學習 DTML,請直接學習 ZPT,基本上,ZPT 與 Python Script能夠完成所有 DTML 的語法功能。至於已經熟悉 DTML 的朋友,可以閱讀下列的參考資料,目前已有一些教導 DTML 與 ZPT 相互轉換的文件。
- Simple Tutorial : http://wiki.zope.org/ZPT/SimpleTutorial
- Zope Page Templates (from plone.org) : http://plone.org/documentation/tutorial/zpt
- Advanced Page Templates : http://docs.zope.org/zope2/zope2book/AdvZPT.html
- Template Attribute Language : http://wiki.zope.org/ZPT/TAL
- Template Attribute Language Expression Syntax : http://wiki.zope.org/ZPT/TALES
- DTML to ZPT conversion Examples : http://www.zope.org/Members/peterbe/DTML2ZPT
- A DTML user's Introduction to Page Templates : http://wiki.zope.org/ZPT/IntroductionForDTMLers
- z3c.pt: Fast ZPT Engine