Tapestry 教程(四)探索項目結構

項目的格局遵循的是Maven倡導的一個很合適的標準:html

Java源代碼文件放在 src/main/java 下面java

Web應用程序文件放在 src/main/webapp(包括src/main/webapp/WEB-INFweb

Java測試資源放在src/test/java下面ajax

非代碼資源(包括Tapestry頁面和組件模板)放在src/main/resourcessrc/test/resources下面apache

讓咱們來看看Maven根據原型建立了寫什麼,先從web.xml配置文件開始:瀏覽器

 

src/main/webapp/WEB-INF/web.xml安全

<?xml version="1.0" encoding="UTF-8"?>app

<!DOCTYPE web-app框架

        PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"webapp

        "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>

    <display-name>tutorial1 Tapestry 5 Application</display-name>

    <context-param>

        <!-- The only significant configuration for Tapestry 5, this informs Tapestry

of where to look for pages, components and mixins. -->

        <param-name>tapestry.app-package</param-name>

        <param-value>com.example.tutorial1</param-value>

    </context-param>

    <!--

    Specify some additional Modules for two different execution

    modes: development and qa.

    Remember that the default execution mode is production

    -->

    <context-param>

        <param-name>tapestry.development-modules</param-name>

        <param-value>

            com.example.tutorial1.services.DevelopmentModule

        </param-value>

    </context-param>

    <context-param>

        <param-name>tapestry.qa-modules</param-name>

        <param-value>

            com.example.tutorial1.services.QaModule

        </param-value>

    </context-param>

    <filter>

        <filter-name>app</filter-name>

        <filter-class>org.apache.tapestry5.TapestryFilter</filter-class>

    </filter>

    <filter-mapping>

        <filter-name>app</filter-name>

        <url-pattern>/*</url-pattern>

    </filter-mapping>

</web-app>

 

這個文件比較簡短:你能夠看到本身早先提供的包名做爲 tapestry.app-package 上下文參數顯示在這個文件中;TapestryFilter實例會利用這個信息訂購pagecomponentJava類。

Tapestry操做的是一個servlet Filter而不是傳統的servlet。用這種方法,Tapestry就有機會攔截到全部的傳入請求,以據此決定哪一個請求對應到哪一個Tapestry頁面(或者其它的資源)。最終的效果就是你沒必要爲Tapestry維護任何額外的配置了,不管你要嚮應用程序添加多少pagecomponent

web.xml剩餘的大部分都是配置用來匹配Tapestry執行模式對應的模塊類的。執行模式定義了應用程序將如何運行:默認的執行模式是「production」,不過web.xml還定義了兩個模式:「development」和「qa」(表明「Quality Assurance」)。模塊類將會針對這些執行模式而被加載,並能以各類方式修改應用程序的配置。本教程稍後會回過頭來再來說這個執行模式和模塊類。

Tapestrypage至少包含一個普通的Java類和一個組件模板文件。

web應用程序的根目錄,有一個叫作「Index」的page江北被用於任何沒有在上下文名稱後面指定額外路徑的請求。

Index Java 

Tapestry對於哪裏放置page類有很是特殊的規定。Tapestry將一個子包,「pages」添加到了應用程序根包(「com.example.tutorial1」)下面;用於pageJava 類就放在這兒。一次Java類的全稱就是com.example.tutorial1.pages.Index

 

src/main/java/com/example/tutorial/pages/Index.java

package com.example.tutorial1.pages;

 

import org.apache.tapestry5.Block;

import org.apache.tapestry5.EventContext;

import org.apache.tapestry5.SymbolConstants;

import org.apache.tapestry5.annotations.InjectPage;

import org.apache.tapestry5.annotations.Log;

import org.apache.tapestry5.annotations.Property;

import org.apache.tapestry5.ioc.annotations.Inject;

import org.apache.tapestry5.ioc.annotations.Symbol;

import org.apache.tapestry5.services.HttpError;

import org.apache.tapestry5.services.ajax.AjaxResponseRenderer;

import org.slf4j.Logger;

 

import java.util.Date;

 

/**

 * Start page of application tutorial1.

 */

public class Index

{

    @Inject

    private Logger logger;

 

    @Inject

    private AjaxResponseRenderer ajaxResponseRenderer;

 

    @Property

    @Inject

    @Symbol(SymbolConstants.TAPESTRY_VERSION)

    private String tapestryVersion;

 

    @InjectPage

    private About about;

 

    @Inject

    private Block block;

 

    // Handle call with an unwanted context

    Object onActivate(EventContext eventContext)

    {

        return eventContext.getCount() > 0 ? new HttpError(404, "Resource not found") : null;

    }

 

    Object onActionFromLearnMore()

    {

        about.setLearn("LearnMore");

 

        return about;

    }

 

    @Log

    void onComplete()

    {

        logger.info("Complete call on Index page");

    }

 

    @Log

    void onAjax()

    {

        logger.info("Ajax call on Index page");

 

        ajaxResponseRenderer.addRender("middlezone", block);

    }

 

    public Date getCurrentTime()

    {

        return new Date();

    }

}

 

在這份代碼裏面有許多東西,Index page想要精力向你呈現Tapestry中一堆不一樣的理念。即便如此,這個類看起來仍是至關的簡單:Tapestrypagecomponent沒有積累須要擴展,也沒有接口須要實現,而僅僅只是一個純粹的POJOPlain Old Java Object)……屬性和方法都帶有一些特殊的命名約定和註解。

這其中你必須得知足Tapestry框架的要求:

須要把Java類放在預約的包中,這裏就是com.example.tutorial1.page

類必須是public

須要確保有一個public的,沒有參數的構造器(這裏Java編譯器已經悄悄地爲咱們提供了一個)

全部的非靜態屬性都必須是private

在運行這個應用程序時,如咱們所見,page會展現當前的日期和時間,還有一些額外的連接。currentTime屬性就是這些值的來源;很快咱們會明白這個值是如何被模板引用到的,那樣它就能夠從page和輸出那裏獲取到了。

Tapestry老是會把page對應到一個模板;它們缺乏了對方都是沒有用的。事實上,一個page中的component也是以一樣的方式被對待的(此外component並不老是有對應的模板)。

你回經常聽到模型-視圖-控制器模式MVC)。模板就是MVC中視圖。而做爲模型的page會暴露出能夠在模板中被引用到的JavaBean

讓咱們來看看component模板是如何在Java類上構建出完整的用戶界面的。

Component模板

Tapestrypage是一個POJO Java 類同一個Tapestry component模板的組合。模板會有跟Java類相同的名稱,不事後綴名會是.tml。由於這裏的Java類是com.example.tutorial.pages.Index,因此模板文件就會被放置在src/main/resource/com/example/tutorail/pages/Index.tml。最終,Java類和component 模板文件都會被存儲在用於部署的WAR文件的同一個目錄之中。

Tapestrycomponent模板是形式良好的XML文檔。這就意味着你能夠利用上任何可用的XML編輯器。模板可能甚至會有一個DOCTYPE或者一個XML schema來驗證模板page的結構是否正確。

注意Tapestry回用一個非驗證性質的解析器來解析component模板:它只會檢查形式是否良好:正確的語法,對應平衡的元素,屬性值是在雙引號中,注入此類。你本身決定要不要構建流程來執行某些類型的模板驗證,而只要能順利的解析,Tapestry仍是會照常接受模板。

Tapestry component模板的大部分都像是一個普通的XHTML

 

src/main/resources/com/example/tutorial1/pages/Index.tml

<html t:type="layout" title="tutorial1 Index"

      xmlns:t="http://tapestry.apache.org/schema/tapestry_5_4.xsd"

      xmlns:p="tapestry:parameter">

 

    <div class="hero-unit">

        <p>

            <img src="${asset:context:images/tapestry.png}"

                 alt="${message:greeting}" title="${message:greeting}"/>

        </p>

        <h3>${message:greeting}</h3>

        <p>The current time is: <strong>${currentTime}</strong></p>

        <p>

            This is a template for a simple marketing or informational website. It includes a large callout called

            the hero unit and three supporting pieces of content. Use it as a starting point to create something

            more unique.

        </p>

        <p><t:actionlink t:id="learnmore" class="btn btn-primary btn-large">Learn more »</t:actionlink></p>

    </div>

 

    <div class="row">

        <div class="span4">

            <h2>Normal link</h2>

            <p>Clink the bottom link and the page refresh with event <code>complete</code></p>

            <p><t:eventlink event="complete" class="btn btn-default">Complete»</t:eventlink></p>

        </div>

        <t:zone t:id="middlezone" class="span4">

 

        </t:zone>

        <div class="span4">

            <h2>Ajax link</h2>

            <p>Click the bottom link to update just the middle column with Ajax call with event <code>ajax</code></p>

            <p><t:eventlink event="ajax" zone="middlezone" class="btn btn-default">Ajax»</t:eventlink></p>

        </div>

    </div>

 

    <t:block t:id="block">

        <h2>Ajax updated</h2>

        <p>I'v been updated through AJAX call</p>

        <p>The current time is: <strong>${currentTime}</strong></p>

    </t:block>

 

</html>

 

你必定得用跟component 類的名稱Index的每一個字母都同樣的名稱來命名你的component模板文件,也就是Index.tml。若是有一個字符錯了,就有可能在某些操做系統(好比Mac OS XWindows)上仍然有效,不過在其它的(Linux和大多數其它的)上面就不行了。這可真使人煩心,由於在Windows上面作開發,而部署到LinuxSolaris上面是很常見的事情,因此這一處仍是當心爲是。

Tapestry中,對於諸如Index.tml這樣的component模板,其目標就是儘量想一個普通的,靜態的HTML文件。(這裏的靜態static的意思是對比一個動態生成的Tapestry page,不用修改的意思)。

事實上,在許多狀況下咱們所指望的是,模板開始是一個靜態的HTML文件,有web開發者建立出來,而後被組裝做爲一個動態的Tapestry page

TapestryXML命名空間裏面隱藏了非標準的元素和屬性。按照約定,前綴「t:」被用於主命名空間,不過這不是必須的,任何你想要使用的前綴均可以。

這個簡短的模板展現了Tapestry至關多的特性。

Quickstart原型的部分意圖是展現一堆不一樣的功能特定、方法以及在Tapestry被用到的通用模式。所以確實咱們是在一次性地用所有的東西來打動你。

首先,有兩個XML命名空間是一般都要被定義的:

 

xmlns:t="http://tapestry.apache.org/schema/tapestry_5_4.xsd"

xmlns:p="tapestry:parameter"

 

第一個命名空間,「t:」,被用來識別Tapestry特定的元素和屬性。儘管有了一個XSD(就是一個XML schema定義),不過不完整(緣由很快就會解釋)。

第二個命名空間,「P:」,是一種把至關多的模板標記爲一個參數傳入到另外的組件的方式。很快咱們會展開來描述這個東西。

Tapestry component模板包含大多數標準的XHTML,它們不作修改就會被向下傳遞給玩野瀏覽器。模板的動態部分由componentexpansion來呈現。

模板中的擴展(expansion

讓咱們從exansion開始。Expansion是在渲染頁面時包含一些動態輸出的簡便方式。Expansion默認會引用pageJavaBean中的屬性。

 

<p>The current time is: ${currentTime}</p>

 

大括弧中的值是一個屬性表達式。Tapestry使用其本身的屬性表達式語言,富有表現力,快速,且類型安全。

Tapestry並無使用反射來實現屬性表達式。

更高級的屬性表達式能夠橫向引用多個屬性(例如:user.address.city),或者甚至調用公共方法。這裏的expansion簡單的讀取了pagecurrentTime屬性。

Tapestry遵行有Sun JavaBean規範定義的規則:屬性的名稱currentTime映射到兩個方法:getCurrentTime()setCurrentTime()。若是你省略了其中一個方法或者兩個都省略掉,屬性就會是隻可讀的(如這個示例中所示),或則是隻可寫的。(請牢記,不用管什麼JavaBean的屬性,它的方法纔是關鍵;實例標量的名稱,以致於它們是否存在,都可有可無。)

Tapestry則更近一步:在匹配expansion中的屬性到page的屬性時,它會忽略大小寫。在模板中咱們能夠用${currenttime}或者${CurrentTime}或者其它變體,而Tapestry仍舊是調用getCurrentTime()方法。

注意在Tapestry中不必設置有什麼對象擁有currentTime屬性;一個模板或者一個page老是會組合在一塊兒互相利用;表達式老是以page實例爲根,在這種狀況下,就是Index類的一個實例。

Index.tml模板包含的第二個expansion

 

<p>${message:greeting}</p>

 

這裏greeting並不是是page的一個屬性;它其實是一個本地的消息鍵。每一個Tapestry pagecomponent均可以擁有其本身的消息目錄(message catalog)。(還有一個全局的消息目錄,稍後咱們會描述一下。)

 

src/main/resources/com/example/tutorial/pages/Index.properties

greeting=Welcome to Tapestry 5!  We hope that this project template will get you going in style.

 

消息目錄對於在代碼或者模板以外存儲重複的字符串時很是有用,儘管主要目的同應用程序的本地化有關(這會在稍後的章節中有詳細描述)。可能被多個page用到的消息能夠被存儲在應用程序的全局消息目錄中,也就是src/main/webapp/WEB-INF/app.properties

這個「message:」前綴並非某種特殊狀況;實際上有一些這樣的綁定前綴被構建到了Tapestry,每個都有特殊的目的。事實上,表達式中忽略半丁前綴就跟使用了「prop:」是同樣的,意思是將綁定看做是一個屬性表達式。

Expansion在用來獲取一塊信息並將其做爲字符串渲染到客戶端時是頗有用的,不過Tapestry中重要的擔子都放在了component裏面。

模板中的組件(component

Component以兩種方式在component模板中表示:

做爲一普通的元素,不過帶有一個t:type屬性,用來定義component的類型。

做爲Tapestry命名空間中的一個元素,這種狀況下元素的名稱決定其類型。

這裏咱們使用了一個<html>元素來表示應用程序的Layout(佈局)component

 

<html t:type="layout" ...> 

  ...

</html>

 

而對於 EventLink component,咱們使用了Tapestry 命名空間中的一個元素:

 

<t:eventlink page="Index">refresh page</t:eventlink>

 

選哪中形式就是一種選擇而已。在多數狀況下,二者幾乎是同樣的。

跟其餘地方同樣,大小寫是無關的。這裏的類型(「layout」和「eventlink」)都是用的小寫;實際的類名稱是 Layout 和 EventLinkTapestry會進一步將核心庫的component同這個應用程序定義的component「混淆」;如此類型「layout」會被映射到應用程序的componentcom.example.tutorial.components.Layout,而「eventlink」會被映射到Tapestry內置的org.apache.tapestry5.corelib.components.EventLink類。

Tapestrycomponent是使用參數來配置的;對於每一個component,都有一堆參數,每個都帶有一個特殊的類型和目的。某些參數是必需的,其它是可選的。元素的屬性被用來將參數綁定到特定的字面值,或者是page的屬性。Tapestry在此處是很靈活的;你老是可以將屬性放到Tapestry的命名空間中(使用「t:」前綴),不過在大多數狀況下,不必這麼作。

 

<html t:type="layout" title="tutorial1 Index"

      p:sidebarTitle="Framework Version" ...

 

這個將Layout component的兩個參數,titlesidebarTitle對應綁定到了字面值「tutorial Index」和「Framework Version」。

Layout component將實際給瀏覽器發送最終的HTML;咱們將會在稍後的章節中查看這個模板。這裏要點是,page的模板被集成到了Layout component的模板中。下圖展現了參數是如何被傳到Layout component並被渲染成最終的頁面的:

這裏有點意思(也是Tapestry中的一個高級概念,稍後回頭來再講)的是咱們能夠見Index.tml的一塊做爲sidebar參數傳入Layout component。這就是tapestry:parameter命名空間(也就是「p:」前綴)的用處;元素的名稱被匹配到component的一個參數,而template的一整快被傳入了Layout component……它決定了在其模板的哪一個位置渲染這一塊。

 

<t:eventlink event="complete" class="btn btn-default">Complete»</t:eventlink>

 

這一次是PageLink componentpage參數被綁定到了字面值「Index」(就是這個page的名稱)。其或被渲染成一個從新渲染這個pageURL,解釋了當前時間是如何被更新的。你也能夠建立到應用程序中其它page的連接,稍後的章節中咱們會看到,且除了page名稱意外,還能夠將額外的信息附加到URL上。

一個魔術小把戲

如今是時候玩一個魔術小把戲了。修改Index.java,將getCurrentTime()方法修改爲:

 

Index.java (部分)

public String getCurrentTime()

{

  return "A great day to learn Tapestry";

}

 

確保你對修改進行了保存,而後在網頁瀏覽器中點擊刷新:

這是Tapestry早期的一個使人叫絕的特性,對component類的修改能夠當即生效(一個咱們稱做動態類從新加載Live Class Reloading的特性)。無需重啓。也無需從新部署。作出修改而後立刻就能看到效果。沒有什麼能拖你的後腿,或者當你的道兒。

若是Live Class Reloading不起做用,看看Class Reloading中的Troubleshooting一節。

不過……要是你代碼寫錯了呢?若是你把模板中的名稱搞錯了會怎樣。能夠試一試;就在模板中,將${currentTime}改爲比方說${currenTime},看看你會獲得什麼結果:

這是Tapestry的異常報告頁面。它至關的詳細。清楚的指明Tapestry正在作什麼,還將問題同模板中的特定行關聯起來,在上下文中顯示出來。Tapestry老是展開顯示整個異常跟蹤棧,由於異常的拋出、捕獲和在其餘異常中從新拋出是如此廣泛。事實上,若是咱們將頁面向下只是滾動一點點,就能夠看到有關這個異常的更多信息,還有一點點幫助信息:

這就是Tapestry行事方式的一部分:不只指出正在作的是什麼還有出了什麼問題,甚至於還要幫助你找到解決方案;在這裏它告訴你應該已經使用過的屬性名稱。

其詳細程度代表應用程序已經被配置成development模式而不是production模式。在production模式中,異常報告只會簡單的顯示頂層的異常消息。不過,大多數應用程序更進一步,會對Tapestry如何處理和報告異常進行自定義。

Tapestry會顯示最深層異常的跟蹤棧,與此同時還有許多關於運行時華景的詳細信息:關於當前請求,HttpSession(若是存在一個的話)的詳細信息,甚至還會有全部JVM系統屬性的詳細清單。往下滾動就能夠看到全部的這些信息。

接下來是:實現Hi-Lo猜謎遊戲

相關文章
相關標籤/搜索