Skip to content. | Skip to navigation

Personal tools

Navigation

You are here: Home / Tutorial / Page Template

Page Template

Plone 使用 Zope Page Template (ZPT) 或稱為 Page Template (PT) 工具,來完成動態網頁的顯示。本文件將說明基本的 Page Template 功能,並介紹主要的 Template Attribute Language (TAL) 語法範例。

如何產生動態網頁內容,是程式人員關心的議題,常見的例子,像是在網頁上顯示今天的日期,或是顯示來自資料庫的欄位內容。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 有下列三項目的:

  1. 與網頁編輯工具 (如 Dreamweaver) 完美搭配。
  2. 所視如所得 (What You See Is Similar To What You Get)。
  3. 除了語法結構邏輯外,儘量將程式碼移出 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 的方法,依照場合不同,至少有下列三種:

  1. 直接從管理介面 (Zope Management Interface, ZMI) 的物件新增選單,點選「Page Template」。初學或是撰寫簡短 Page Template 的場合,適合使用這種方法。
  2. 透過 FTP 或 WebDAV 來編輯。對於網頁設計人員而言,搭配網頁編輯工具來處理 Page Template 檔案內容。
  3. 在 filesystem 編輯 Page Template 檔案。通常是搭配 Vim 或 Emacs 等工具來編輯。

以下是 ZMI 環境裡的操作範例:

    1. 在 root folder 裡,點選 Page Template 後,Id 欄位填寫「simple_page」,然後點選「Add and Edit」按鈕。

    1. 出現編輯畫面,此時 Title 欄位為空白,Content-Type 欄位預設值為 text/html,進階功能可設定為 text/xml,此處使用 text/html 即可,文字方框區域則產生預設之資料內容。

    1. 請在 Title 欄位填寫「a Simple Page」,然後文字方框內只填寫如下的內容:
This is <b tal:replace="template/title">the Title</b>.
    1. 點選「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 標籤,會被轉換成「&lt; &gt;」。如果我們想要取用「未被轉換的資料」,語法裡就要引用 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 語法包含多行敘述時,其執行順序如下:

  1. define
  2. condition
  3. repeat
  4. content or replace
  5. attributes
  6. 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>

當使用多重敘述時,應該要注意下列幾個事項:

  1. 每一種語法,在同一個標籤裡,只能被使用一次。例如,你不能在同一個標籤裡重覆使用 tal:define,但可以 define 多個變數,以「; 符號」將變數隔開。
  2. tal:content 與 tal:replace 不能使用在同一個標籤裡,因為它們的功能彼此互斥。
  3. 你安排的語法順序,並不會影響 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)" />

想要指定進入 Sharing 頁面時,才顯示某個資訊

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"

在 Table 裡指定 id 的範例

在 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;

Remove DT DL DD

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 相互轉換的文件。