在這個教程中,我們會談到如何創建最基本的 Tapestry 應用,一個顯示當前時間的簡單的“Hello World”應用。然後我們會稍加擴展,加入一些交互性。

教程最終的源代碼打包為 helloworld.tar.gz。

Tapestry Application Basics

我們第一個應用運行時如下:

helloworld1

Tapestry 應用總是包含一個名為“Home”的頁面。這個主頁是應用第一次啟動時(也就是客戶端瀏覽器首次鏈接首頁)的首頁。

Tapestry 頁面總是 Java 類與模板的組合(我們可以說,“一個 HTML template”,但 Tapestry 並不僅限於 HTML)。在很多情況下,如果你沒有提供一個 Java 類,Tapestry 則會使用內建的 Java 類;而一個普通的頁面(譬如本例),我們就完全不需要提供一個 Java 類。

我們將會從 HTML 模板開始,即應用的根目錄下名為 Home.html 的文件。在這個項目中,它存儲於 src/context/Home.html:

<html>
<head>
<title>Tutorial: HelloWorld</title>
</head>
<body>
<h1>HelloWorld Tutorial</h1>
</body>
</html>

這個 HTML 模板沒什麼特別的,沒有動態的內容(應該說還沒有)。我們可以鏈接到 http://localhost:8080/helloworld/Home.html 然後看到同樣的頁面;但是注意,在截圖中的 URL 是 http://locahost:8080/helloword/app。這意味著在這個 web 應用中有一個映射到路徑 /app 的 servlet 負責在瀏覽器中輸出。

Tapestry 應用總是會用到框架提供的一個特定的 servlet 類。這個類在部署描述文件 web.xml 中定義。這個文件存儲在 src/context/WEB-INF/web.xml,如下:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>
<display-name>Tutorial: HelloWorld</display-name>
<servlet>
<servlet-name>app</servlet-name>
<servlet-class>org.apache.tapestry.ApplicationServlet</servlet-class>
<load-on-startup>0</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>app</servlet-name>
<url-pattern>/app</url-pattern>
</servlet-mapping>
</web-app>

這裡我們為我們的應用取了個名字,“app”。我們使用標準的 Tapestry ApplicationServlet 類作為我們的 servlet,然後映射到路徑 /app 下。相對來而言,你的應用起什麼名字並不是很重要,無論你選什麼名字,Tapestry 都可以接受。

另一方面,路徑需要使用 /app。這不是 Tapestry 的硬編碼,但如果你選擇使用別的路徑,那就確實需要一些設置。在稍後的教程中可以看到,Tapestry 在建立應用的 URL 上非常的靈活、強大,所以並不需要擔心這方面。

在現階段,這就是 HelloWorld 的全部。完全沒有 Java 代碼。進階之前,我們會先為這個應用加入一些東西,好讓你可以切實的感覺一下 Tapestry...但我們仍會避免編寫任何的 Java 代碼。

Adding dynamic output

我們將會更改首頁,讓它顯示當前的日期和時間。如下:

helloworld2

在 Tapestry 中,有很多的動態事件,這將會牽涉到組件。Tapestry 組件和 Tapestry 頁面非常的相似... 它們由一個模板和一個 Java 類組成(細心的人或許會注意到可能會有一個 XML 文件將它們捆綁在一齊,而模板和 Java 類都是可選的 -- 我們會在稍後詳細介紹)。

Tapestry 組織“隱藏”在 HTML 模板中。它們看起來像是普通的 HTML 標籤,但它們有著額外的屬性,常常還有些奇特的屬性值。修正後的 Home.html 模板:

<html>
<head>
<title>Tutorial: HelloWorld</title>
</head>
<body>
<h1>HelloWorld Tutorial</h1>

<p>
The current data and time is:
<strong><span jwcid="@Insert" value="ognl:new java.util.Date()">June 26 2005</span></strong>
</p>

</body>
</html>

模板中的 <span> 標籤是組件的持有者。jwcid 這個特殊的屬性表明它是一個 Tapestry 組件,而不是普通的 HTML 標記。

jwcie 的屬性值“@insert”可被看作 Insert 組件的實例。Insert 是 Tapestry 內建的眾多組件中的一個。它並不是一個純粹的 Java 類而是一個組件類型,Tapestry 用它來查找關於組件的信息,譬如可設置什麼參數以及什麼 Java 類包含了組件的業務邏輯。我們會在稍後詳細介紹。

在介紹屬性值參數之前,我們先來認識一下組件的主體,它是模板中組件的開始到結束標籤之間的部分(在本例中是 <span> 和 </span>)。最後,組件自身決定何時、是否又或是輸出它的主體幾次(即輸出 HTML)。

很明顯,組件 Insert 並沒有進行輸出。組件體內的任何文字在運行時將會被忽略,事實上我們可以將模板裡的組件簡寫為 <span jwcid="@insert" value="ognl:new java.util.Date()" /> 然後在標籤中不加任何的文本。那麼我們為什麼不這麼幹?

答案是預覽性,也就是即使不實際運行但最起碼可以看到頁面運行後大致的樣子。你可以在瀏覽器中裝載 Home.html,或是專門的編輯器(譬如 Dreamweaver 或是 HomeSite)來查看。舉個例子,如果我們繞過 Tapestry servlet 直接鏈接模板,我們會看到:

helloworld3

“June 26 2005”這段臨時的文本並不是應用程序運行時顯示的... 但它很接近;它不是空白而也幾乎是準確的。在一個有著樣式表和布局的真實應用中,這足以驗證應用運行的正確性。

注意
另外,關於預覽性,實際上這是 Tapestry 的基礎之一:將邏輯層與內容徹底的分開。一個 HTML 開發者可以編輯這個模板以及作出重要的改動,然後在他們選擇的編輯器內進行驗證而不會影響到 Java 開發人員。只要 HTML 團隊尊重這些組件(帶有 jwcid 屬性的標籤),不去改動它們,模板內其它的內容都可以自由的進行編輯。只有在連接動、靜態內容時,HTML 和 Java 的開發人員才需要在組件內協同工作。

那麼 value=“ognl:new java.util.Date()”又是什麼意思呢?讓我們從屬性值 ognl:new java.util.Date 開始。前綴“ognl:”通知 Tapestry 是這一個需要驗證的表達式而不是一個普通的字符串。如果我們確實需要 Insert 組件總是輸出同樣的字符串,譬如“Tapestry Rocks!”,我們就不需要使用前綴,只需這樣:value="Tapestry Rocks!"。

OGNL(Object Graph Navigation Language,對象圖導航語言)是一門開源的表達式語言,有幾個開源項目都在使用它,包括 Tapestry、WebWorkSpring。OGNL 有著一些讓人吃驚的能力,不僅是讀取和更新對象的屬性,而且還包括了對創建新對象的支持(如本例),如 list、map 和 array。

驗證這個表達式會產生一個 java.util.Date 的實例。這個 Date 實例綁定在 Insert 組件的屬性值參數上。“綁定”是另一個 Tapestry 的專用詞,涉及組件參數與容器屬性(或表達式)間的關係。在這裡,組件是 Insert,容器是 Home.html,表達式是 new java.util.Date。綁定看起來就像是分配給 Insert 組件屬性的一個任務,但不僅如此,組件還常常使用綁定來更新它們容器的屬性,在我們談到 form 元素組件時會有提及。

Insert 組件的 Java 代碼的邏輯結構是獲得屬性值參數以及將其轉換成一個用於提供響應的字符串。

因此,表達式提供 Date 的實例,屬性值參數讓 Insert 組件可以訪問 Date 的實例,Insert 組件則提供將 Date 轉換為字符串以及結果輸出的業務邏輯。每次 Insert 組件運行時,它都會重新讀取屬性值參數,再次驗證表達式,然後產生一個新的 Date 實例。重複點擊你瀏覽器的刷新按鈕就可以看到顯示的日期會持續的改變。

在下一節中,我們會看看如果如何創建一個更新顯示日期的鏈接。

Creating Links

我們將再一次對這個應用進行擴展,加入一個點擊刷新的鏈接以取代瀏覽器的刷新按鈕。結果如下:

helloworld4

我們在 Home.html 中加入下面的部分創建了這個新的鏈接:

<p>
<a href="#" jwcid="@PageLink" page="Home">refresh</a>
</p>

再次重申,Tapestry 中任何的動態內容都涉及組件;這裡是 PageLink 組件,組件族中的一員,(在本例)負責生成連接到 Tapestry 應用的 callback 鏈接。

Tapestry 會自動生成這樣的 URL;你可以在截圖中看到:http://localhost:8080/workbench/app? page=Home&service=page。這個 URL 提供了兩個關鍵的信息:service= page 意思是輸出一個頁面,而 page=Home 是確定輸出的頁面。

為此我真的需要一個組件嗎?你可能很想把模板改為:

  <a href="/app?service=page&amp;page=Home">refresh</a>

這是個很糟的想法。Tapestry 並不僅僅是釋出一個 URL,它為你生成的是一個 session 編碼的 URL,我們稍後將會看到還有一些有用的東西。再者,推測 URL 從來就不是一個好主意;Tapestry 的下一版本仍會提供 PageLink 組件,但 URL 格式卻可能在發佈版本之間出現改動。

另外一個問題:屬性中的 href 跟隨的是什麼?href="#" 是什麼意思?這就是預覽性的另一方面了:要預覽這個頁面,由 PageLink 最終生成的鏈接必須要以一個 link 的形式來預覽。沒有 href 屬性的 <a> 標籤是一個錨點。再次重申,在樣式表的支持下,實際應用中會更能體現出區別。

HTML 模板提供的 href 會被由 PageLink 組件內部生成 href 屬性所覆蓋(/app?service=...)。實際上你可以將這些由組件生成的屬性與模板所提供的額外屬性混合和配對使用;這些是非正式的參數,稍後的教程會涉及到。