在前面一章,咱們看到了Tapestry如何處理簡單地連接,甚至於處理能在URL中傳遞信息的連接。在本章,咱們將會看到Tapestry如何以不一樣的方式作一樣的事情,以及至關多其它的事情,如HTML表單。html
Tapestry中的表單支持深刻並且豐富,以致於一個單獨章節的內容還裝不下。不過,咱們能夠展現一些基礎的,包括一些很是廣泛的開發模式。開始咱們先來建立一個簡單的地址簿應用程序。java
先從實體數據着手,這是一個咱們須要的用來存儲信息的簡單對象。這些類被放在entities子包下面。不一樣於pages子包(放component類的)的用法,這個子包並不受Tapestry的管束;只是一種約定(不過如咱們很快就會發現的,這是比較方便的一個約定)而已。git
Tapestry將公共的屬性域當作是JavaBean的屬性;由於Address 對象只是一些「呆數據dumb data」,因此沒有必要費力去寫什麼getter和setter。而是徹底把他們定義成public的屬性域。正則表達式
src/main/java/com/example/tutorial/entities/Address.javaapache
package com.example.tutorial1.entities;api
import com.example.tutorial1.data.Honorific;瀏覽器
public class Address測試
{ui
public Honorific honorific;spa
public String firstName;
public String lastName;
public String street1;
public String street2;
public String city;
public String state;
public String zip;
public String email;
public String phone;
}
咱們還要定義這個枚舉類型,Honorific:
src/main/java/com/example/tutorial/data/Honorific.java
package com.example.tutorial1.data;
public enum Honorific
{
MR, MRS, MISS, DR
}
咱們可能要去建立一些同地址相關的page:建立地址的page、編輯地址的page、搜索和列出地址的page。咱們將建立一個子文件夾,address,來放置他們。先從這些page的第一個開始,「address/Create」(這就是實際名稱,包括斜線——稍後咱們會明白它是如何映射到類和模板的)。
首先,咱們將Index.tml更新,建立一個到新的page的連接:
src/main/resources/com/example/tutorial/pages/Index.tml (partial)
<h1>Address Book</h1>
<ul>
<li><t:pagelink page="address/create">Create new address</t:pagelink></li>
</ul>
如今咱們須要address/Create page;先從一個空殼開始,只測試導航是不是通的:
src/main/resources/com/example/tutorial/pages/address/CreateAddress.tml
<html t:type="layout" title="Create New Address"
xmlns:t="http://tapestry.apache.org/schema/tapestry_5_3.xsd">
<em>coming soon ...</em>
</html>
(注意:對於Tapestry5.4,用tapestry_5_4.xsd)
接下來是對應的類:
src/main/java/com/example/tutorial/pages/address/CreateAddress.java
package com.example.tutorial1.pages.address;
public class CreateAddress
{
}
那麼……爲何類被命名爲「CreateAddress」而不是簡單的「Create」呢?實際上,咱們能夠把它命名爲「Create」,應用程序仍然會運做,不過更長的類名稱是一樣可用的。Tapestry知道類名稱(com.example.tutorial1.pages.address.CreateAddress)中多餘的東西是什麼意思,而且會截掉多餘的後綴。
實際上Tapestry爲你的page建立了一堆的別名;這些別名中的任何一個都是可使用的,而且能夠出如今URL或者PageLink的page 參數中。你能夠在控制檯中看到這個清單:
[INFO] TapestryModule.ComponentClassResolver Available pages (12):
(blank): com.example.tutorial1.pages.Index
ComponentLibraries: org.apache.tapestry5.corelib.pages.ComponentLibraries
Error404: com.example.tutorial1.pages.Error404
ExceptionReport: org.apache.tapestry5.corelib.pages.ExceptionReport
GameOver: com.example.tutorial1.pages.GameOver
Guess: com.example.tutorial1.pages.Guess
Index: com.example.tutorial1.pages.Index
PageCatalog: org.apache.tapestry5.corelib.pages.PageCatalog
PropertyDisplayBlocks: org.apache.tapestry5.corelib.pages.PropertyDisplayBlocks
PropertyEditBlocks: org.apache.tapestry5.corelib.pages.PropertyEditBlocks
ServiceStatus: org.apache.tapestry5.corelib.pages.ServiceStatus
T5Dashboard: org.apache.tapestry5.corelib.pages.T5Dashboard
address/Create: com.example.tutorial1.pages.address.CreateAddress
address/CreateAddress: com.example.tutorial1.pages.address.CreateAddress
Tapestry在構造URL時會使用最短的那個別名。
最終,你的應用程序可能會有更多的實體:可能你會有一個「user/Create」page和一個「payment/Create」page以及一個「account/Create」page。你可能會有一堆所有被命名爲Create的不一樣的類,分佈於許多不一樣的包中。這都是合法的Java,但並不理想。某一天你可能會忽然發現正在編輯建立Account的Java代碼,而你實際想要編輯的是建立Payment的代碼。
所以Tapestry鼓勵你使用更加具備描述性的名稱。CreateAddress,而不只僅只是Create,不過這並不須要你付出代價(好比更長更笨重的URL)。訪問這個page的URL仍然是 http://localhost:8080/tutorial1/address/create
還要記得,無論Tapestry給你的page分配的名稱是什麼,模板文件的名稱跟Java類必定是同樣的,CreateAddress.tml。
Index page在文件夾中也能起做用。一個叫作com.example.tutorial1.pages.address.AddressIndex的類可能會被分配名稱「address/Index」。然而,Tapestry對於叫作「Index」的page有特殊的對着,其渲染的URL可能會是 http://localhost:8080/tutorial1/address 。換言之,你能夠將Index page放到任何文件夾中,而Tapestry將會爲這個page構造一個簡短的URL……而你沒必要一直將類命名爲Index(讓許多的類、設置跨多個包的類擁有同一個名字會使人迷惑);你能夠將每一個index page以其所在包來命名。Tapestry 使用一個聰明的約定來保持直接並生成出簡短的URL。
是時候以這種形式來將邏輯組合到一塊兒了。Tapestry有一個用於客戶端表單的特殊的component:Form component,以及用於表單控制的 component,好比 Checkbox 和 TextField。咱們在稍後會涉及到它們……這裏咱們再一次經過BeanEditForm component讓Tapestry爲咱們幹了些體力活兒。
將以下代碼添加到 CreateAddress 模板(替換「coming soon...」消息):
CreateAddress.tml (局部)
<t:beaneditform object="address"/>
並對應到CeateAddress類中的一個屬性:
CreateAddress.java (局部)
@Property
private Address address;
當你刷新頁面的時候,可能會在頁面的頂部看到以下這樣一條警告信息:
若是你看到了這個,就意味着你須要爲應用建立一個HMAC密碼。只要像下面這樣編輯你的 AppModule.java 類(就在你的services 包中),添加幾行代碼到 contributeApplicationDefaulsts方法就好了:
// Set the HMAC pass phrase to secure object data serialized to client
configuration.add(SymbolConstants.HMAC_PASSPHRASE, "");
不過,不能是一個空的字符串,而是要插入一個長的,隨機字符串(就像是一個很是長並且複雜的密碼,至少30個字符),只有你本身知道。
在你作了這個以後,停掉應用而且從新啓動它,而後在Create new address 連接上再次點擊,就會看到像下面這樣的效果:
Tapestry在這裏作了許多的工做。它建立了一個表單,包含對應每一個屬性的輸入域。不止如此,它還知道 honorific 屬性是一個枚舉類型,因此就如下拉列表輸入框來呈現。
此外,Tapestry已經將屬性名稱(「city」,「email」,「firstName」)轉換成顯示給用戶看的樣子(「City」,「Email」,「First Name」)。事實上,它們都是<label>元素,所以用鼠標點擊一個label會將輸入指針移動到對應的輸入域當中去。
這是一個很棒的開頭;這是一個很具備表現力的界面,事實上幾分鐘的工做就能有一個至關不錯的效果。不過同完美相比它還遠遠不夠。讓咱們開始來作一些自定義吧。
BeanEditForm必須揣度這以正確的順序呈現輸入域,結果就是按照字母表的順序來的。對於標準的JavaBean屬性,BeanEditForm默認是以其getter方法在類中定義的順序排列的(它使用了行號信息,如何能夠獲取到這個信息的話)。
排列這些輸入域更好的順序就是它們在Address類中被定義的順序:
l honorific
l firstName
l lastName
l street1
l street2
l city
l state
l zip
l email
l phone
咱們能夠藉助於BeanEditForm的 reorder 參數的使用來完成這樣的排序,其值就是以逗號分隔的屬性(或者公共域)名稱的列表:
CreateAddress.tml(局部)
<t:beaneditform object="address"
reorder="honorific,firstName,lastName,street1,street2,city,state,zip,email,phone" />
Tapestry讓自定義使用在輸入域上的label變得至關的輕鬆。這是一個標準的Java properties文件,它被以跟page或者component類相同的名稱命名,帶有一個「.properties」擴展。一個消息清單包含了許多行,每一行都是一個消息鍵對應一條消息值,中間用等號分開。
其所有內容就是建立一個個帶有特殊名稱的消息詞條:以「-label」爲後綴的屬性的名稱。跟其它地方同樣,Tapestry是不在乎大小寫的。
src/main/resources/com/example/tutorial/pages/address/CreateAddress.properties
street1-label=Street 1
street2-label=Street 2
email-label=E-Mail
zip-label=Zip Code
phone-label=Phone Number
由於這是一個新的文件(並非對現有文件的修改),你可能要重啓Jetty來強制讓Tapestry獲取到這個新的文件才行。
咱們也能夠對下拉列表框中的選項進行自定義。須要作的就是網消息清單中添加更多的詞條,以將枚舉名稱匹配到想要的label上面。更新CreateAddress.properties,添加以下幾行:
MR=Mr.
MRS=Mrs.
DR=Dr.
注意咱們並不須要爲MISS添加一個選項,由於不管如何它都會被轉成「Miss」。你可能只是想爲了一致性而把它加進來……關鍵是,每一個選項的label是單獨檢索的。
最後,提交按鈕的默認label是「Create/Update」(BeanEditForm並不知道它是被如何使用的)。讓咱們來把它改爲「Create Address」。
<t:beaneditform submitlabel="Create Address" object="address"
reorder="honorific,firstName,lastName,street1,street2,city,state,zip,email,phone"/>
提交的label默認爲「Create/Update」,不過這裏咱們要將默認的覆蓋成一個特殊的值。
最後的結果顯示了從新調整的形式和label:
在繼續進行驗證以前,還有一個關於消息清單的點附帶要注意下。消息清單不僅僅值用來從新設置輸入域和選項的label,我稍後的章節中咱們還能夠看到消息清單是如何用於本地化和國際化的場景中的。
不把提交按鈕的label直接放到模板裏面,而是給label提供一個引用;實際的label將會被放在消息清單中。
在Tapestry中,每當要綁定一個參數,你所提供的值可能會包含一個前綴。前綴會指引Tapestry如何解釋參數值中(除了前綴以外)的餘下部分…它是否是一個屬性的名稱?是否是一個component的id?是不死消息的鍵?大多數參數都有一個默認的前綴,通常是「prop:」,在你沒有提供一個前綴的時候就會用到(這有助於讓模板儘量的簡潔)。
這裏咱們想要引用一條來自清單的消息,所以咱們使用了「message:」前綴:
<t:beaneditform object="address" submitlabel="message:submit-label"
reorder="honorific,firstName,lastName,street1,street2,city,state,zip,email,phone" />
而後咱們要在消息清單中定義submit-label:
Submit-label=Create Address
最後,無論你是直接在模板中包含了label文本,仍是間接地引用消息清單中的項,發送給客戶端的HTML是同樣的。長遠看來,後者會在以後你要選擇將應用程序進行國際化的時候運做得要更好。
在咱們關心 Address 對象的存儲以前,咱們應該確保用戶所提供的值是合理的。例如,有些輸入域是必填的,而phone number和email address則各有其特殊的格式。
BeanEditForm會在每一個屬性的輸入域、getter方法或者setter方法上檢查Tapestry特殊的註解,@Validate。
對Address實體進行修改,更新一下lastName、firstName、street1、city、state和zip輸入域,每一個都加一個@Validate註解:
@Validate("required")
public String firstName;
那個字符串「required」是什麼?它就是你指定的驗證。它是一系列用來指定須要什麼類型的驗證的名稱。Tapestry內置了許多驗證器,注入「required」、「minLength」以「maxLength」。和其它地方同樣,Tapestry對大小寫不敏感。
你能夠應用多個驗證,只要將驗證器的名稱以逗號分隔就好了。某些驗證器是能夠被配置的(用一個等於符號)。這樣你就能用「required,minLength=5」描述一個輸入域必須被指定值,並且必須至少有5個字符長,這樣的驗證。
你會很容易感到迷惑,當你修改了實體類,好比添加了一個@Validate註解,但是並無在瀏覽器中看到結果。只有component類,和(大多數)位於Tapestry service層的類是動態加載的。數據和實體對象並不會動態地從新加載。
重啓應用程序,並刷新你的瀏覽器,而後點擊Create Address 按鈕:
就在點擊Create Address的一瞬間:全部輸入域都已經完成了驗證並顯示出錯誤提示。每一個驗證出問題的輸入域都以紅色高亮顯示,並添加了錯誤消息。此外,每一個驗證出問題的輸入域的label也是紅色高亮的,以更加清晰的代表哪兒出錯了。鼠標輸入指針也已經被移動到第一個發現問題的輸入域中。而全部這些都發生在客戶端,沒有跟應用程序的後臺有任何通訊。
全部的錯誤都一更正,表單就會提交,而驗證也會在服務端被執行(以防客戶端的JavaScript已經被禁用了)。
那麼……再加更多一點有趣的驗證,而不只僅只是「required or not」,如何。Tapestry擁有對於基於輸入域長度和對於幾個輸入域值的驗證的驗證支持,包括正則表達式。Zip code就能至關容易的被表示成正則表達式。
@Validate("required,regexp=^\\d{5}(-\\d{4})?$")
public String zip;
讓咱們來試試,重啓應用程序並在zip code中輸入「abc」看看:
這就是在你輸入「abc」並點擊Create Address按鈕後所看到的。
現代瀏覽器在表單被提交時自動驗證正則表達式,如上所示。老一點的瀏覽器並無這種自動化的支持,不過仍然會驗證輸入框,在必填的輸入域上使用跟以前的截圖相同的樣式裝飾。
不管如何,這都是正確的驗證行爲,但反饋的消息是錯誤的。你的用戶不會想要知道、也並不關心什麼正則表達式。
幸運的是,自定義驗證消息也很容易。咱們所要作的一切就是知道屬性的名稱(「zip」)還有驗證器的名稱(「validator」)。而後咱們就能夠將其錄入到CreateAddress的消息清單中:
zip-regexp-message=Zip Codes are five or nine digits. Example: 02134 or 90125-1655.
刷新頁面並再次提交:
這個小把戲不僅是能用於正則表達式(regexp)驗證器,對於任何驗證器都有效。
讓咱們在更進一步。原來,咱們還能夠吧正則表達式一道消息清單中。若是你只是在@Validation註解中提供驗證器的名稱,Tapestry機會以限定的值,以及驗證器消息,來對包含了page的消息清單進行搜索。針對正則表達式驗證器的限定值就是對應的正則表達式。
@Validate("required,regexp")
public String zip;
如今,只要將正則表達式放到CreateAddress消息清單中就能夠了:
zip-regexp=^\\d{5}(-\\d{4})?$
zip-regexp-message=Zip Codes are five or nine digits. Example: 02134 or 90125-1655.
重啓以後你就會看到……效果是同樣的。不過當咱們開始要建立更加複雜的正則表達式時,把它們放在消息清單中就比放到註解的值裏面要好不少不少。而在消息清單裏面,你就不須要由於修改或者調整了正則表達式而每次都得重啓應用程序了。
這裏咱們還能夠更進一步,爲phone number和e-mail address加入更多的正則表達式。對於BeanEditForm component的進一步定製咱們瞭解的還遠不夠。
如今你也許會對錶單成功提交(沒有驗證錯誤)以後會發生生麼感到好奇,這就是咱們接下須要關心的事情了。