Tapestry Snippets(转)

Tapestry Snippets


1. Spilt Tapestry Application into Modules

在实际的开发过程中,随着规模的不断扩大,把所有的Page都放在一个目录下,不管是WEBROOT下或是/WEB-INF/下或是/WEB-INF/${servlet.name}/下,显然都不是很好的选择。 幸好在Tapestry中,我们可以把页面分门别类的放在各自的目录中。举个例子:

webroot
   │   Home.html
   │   Home.page
   └───auth
           Login.html
           Login.page

如上,我们把Login页面放置在auth目录中,那么此时Login页面的全名为auth/Login而不是简单的Login了 ,如我们在PageLink中引用:

<a jwcid="@PageLink" page="auth/Login" >Login</a>

2. Tapestry Specifications

所有的Specification,不管扩展名是什么,内容都是XML文件。

2.1. Application Specification - ${servlet.name}.application

每个应用程序一般只有一个。

2.2. Library Specification - ${library.name}.library

和Application Specification的结构基本相当,一般每个组件库的jar中一个。

2.3. Page Specification - ${page.name}.page

一个页面一个,应用程序是由许多Page组成。

2.4. Component Specification - ${component.name}.jwc

每个组件一个,如文本框,日期选择等。每个Page可以包含许多Component,每个Component中还可以包含许多Component。

3. 让Tapestry找到你的Application Specification

关联源文件: org.apache.tapestry.services.impl.ApplicationSpecificationInitializer

Application Specification,顾名思义,应用程序定义书。一般情况下,一个WEB应用程序只有一个。文件名为${servlet.name}.application。 其中${servlet.name}为web.xml中配置ApplicationServlet的Servlet名称,其放置的路径必须是/WEB-INF/${servlet.name}.application 或者/WEB-INF/${servlet.name}/${servlet.name}.application如:

<!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>Components for Tapestry Examples Web Application</display-name>
    ...
    <servlet>
        <servlet-name>comp4t-examples</servlet-name>
        <servlet-class>org.apache.tapestry.ApplicationServlet</servlet-class>.
        <load-on-startup>0</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>comp4t-examples</servlet-name>
        <url-pattern>/app</url-pattern>
    </servlet-mapping>
    ...

</web-app>

这样配置的web.xml文件的话,那么Application Specification应该为comp4t-examples.application, 其位于的路径为/WEB-INF/comp4t-examples.application或者/WEB-INF/comp4t-examples/comp4t-examples.application

4. 让Tapestry找到你的Page Specification

关联源文件: org.apache.tapestry.resolver.PageSpecificationResolverImpl

4.1. 置Page Specification于Application Specification相同路径中

最简单,最直接的办法就是这个,只要和Application Specification保持相同路径,就不怕Tapestry找不到它。

4.2. 置Page Specification于/WEB-INF/中

 

4.3. 置Page Specification于/WEB-INF/${servlet.name}/中

 

4.4. 置Page Specification于/中,即WEB应用程序最根目录中

 

4.5. 置Page Specification于相应java文件路径下,但需Application/Library Specification中配置其相应路径。

一般可以放置相应的java文件相同的路径下,如com.javaforge.comp4t.examples.FrameworkComponentExamples, 那么把Page Specification放在/com/javaforge/comp4t/examples/下比较整齐。然后须在Application Specification指定。

<application name="Components for Tapestry Examples">
    ...
    <page name="FrameworkComponentExamples" specification-path="/com/javaforge/comp4t/examples/FrameworkComponentExamples.page"/>
    <page name="Home" specification-path="/com/javaforge/comp4t/examples/Home.page"/>
    ...
</application>

这样的好处是所有的Tapestry Page都列在了Application Specification。便于管理和整理。

5. 管理服务器端状态

每次提交中需要保存以备后续请求处理中使用的数据。

5.1. 保持住Page的属性(Page属性的持久化)

提到Session,大家不会陌生,因为我们要想在其他的服务请求中得到之前的状态,就用到它,最典型的就是用户验证后的登陆信息。 最普通的做法就是在用户正常的Login之后,把用户的信息放到Session中,在后面处理中需要用户信息时,从Session中取出。 在Tapestry中是不推荐直接使用Session的。框架的作用是什么?就是用封装,抽象还使你需要做的工作得到简化。

下面是一个简单的例子,在Tapestry中我们不再需要和Session打交道。Tapestry已经为你做好了这一切。

public abstract class FrameworkComponentExamples extends BasePage {
    ...
    public abstract List getRecords();

    public abstract void setRecords(List records);
    ...

}
<page-specification class="com.javaforge.comp4t.examples.FrameworkComponentExamples">
    ...
    <property name="records" persist="session"/>
    ...
</page-specification>

这样的配置非常明确的告诉了Tapestry将FrameworkComponentExamples页面的records属性放置到Session中。在以后的调用中, 只要你调用了getRecords()方法,那么Tapestry会从Session中取出这个对象, 要是你调用了setRecords(myList), 那么Tapestry会将myList设置到Session中,那么Session中的键值是如何维护的呢,我们并没有提供给Tapestry呀,一句话,Tapestry自己拼凑出来的。 后面提到拼凑方法。

需要注意的一点,我们的Page中的属性必须是abstract的, Tapestry文档中会说到,不用abstract也可以,但我告诉你,最好使用上面的写法。 想象一下,只有是抽象的,不提供实现,才有可能是从Session或Client中取得属性值, 如果你给他写了实现, 而你的实现很可能定义一个成员变量,那到底是从你这个成员变量中取,还是从Session中取。

5.2. 关于Page属性的Session Key

关联源文件: org.apache.tapestry.record.SessionPropertyPersistenceStrategy

拼凑的方法是${applicationId},${pageName},${idPath},${propertyName}其中${idPath}可能没有,

${applicationId}为前面提到的Servlet名,即web.xml中配置的ApplicationServlet的Servlet名,如comp4t-examples。

${pageName}为页面名,如FrameworkComponentExamples。

如果该页面中调用和其他组件,而且组件中的某个属性指定了持久化(persist)属性,那么${idPath}就会存在。

${propertyName}为属性名,如records。

这样的话整个Session Key为comp4t-examples,FrameworkComponentExamples,records,不会重复。

5.3. 页面属性的持久化策略(PersistenceStrategy)

tapestry.persist.xml

  <contribution configuration-id="PersistenceStrategy">
    <strategy name="session" object="service:SessionPropertyPersistenceStrategy"/>
    <strategy name="client" object="service:PageClientPropertyPersistenceStrategy"/>
    <strategy name="client:page" object="service:PageClientPropertyPersistenceStrategy"/>
    <strategy name="client:app" object="service:AppClientPropertyPersistenceStrategy"/>
  </contribution>

6. 构造友好的URL。

何为不友好的URL,如

/comp4t-examples/app?component=%24DirectLink&page=Home&service=direct&sp=S123456
/comp4t-examples/app?digest=a4008189d74c98218e216c1e2ce53193&path=%2Fcom%2Fjavaforge%2Fcomp4t%2Fexamples%2Fstyle.css&service=asset

上面的的两例称之为非友好URL,为什么呢,太乱了,不仔细分析是无法弄清其中道理,那为什么会存在呢?因为机器能看懂。虽然乱,但也是标准的URL。

看看下面两例

/comp4t-examples/Home,$DirectLink.d?sp=S123456
/comp4t-examples/assets/a4008189d74c98218e216c1e2ce53193/com/javaforge/comp4t/examples/style.css

虽然没有达到最佳效果,但是进步是很明显的,他和上面的可以表达相同的信息量,因为都能被Tapestry解释。

当我们不对Tapestry进行任何配置的时候,在程序运行过程中,出现的URL是第一种,也是在Tapestry 3.x中固定使用的一种。 但在Tapestry 4.0中有了很大的改观,只要稍加配置,即可形成第二种的效果。

或许你对Hivemind还不了解, 但是我不得不告诉你,Tapestry 从4.0 开始基于Hivemind, 可以这样说,不了解Hivemind, 你便精通不了Tapestry。 虽然你很可能不想涉入新的东西,但是新的东西也会给你带来新的效果。 你的学习不会浪费。

下面就是一段Hivemind配置,来达到第二种URL效果。位于classpath:/META-INF/hivemodule.xml文件中

<module id="comp4t.examples" version="1.0.0" package="com.javaforge.comp4t.examples">
    ...
    <contribution configuration-id="tapestry.url.ServiceEncoders">
        <page-service-encoder id="page" extension="html" service="page"/>
        <direct-service-encoder id="direct" stateless-extension="d" stateful-extension="sd"/>
        <asset-encoder id="asset" path="/assets/"/>
    </contribution>
    ...
</module>

其实很多时候,我们并不需要知道是什么意思,只需要知道代码要写的位置,和能起到的效果。 但是如果你真想知道其中原委,那我给你解释。 TODO:解释

7. 对象和字符串之间的转换 - DataSqueezer

关联源文件: tapestry.data.xml

在WEB应用中,不可避免出现对字符串和对象进行转换,因为浏览器表单数据或者是HTTP URL,或者Cookie全部都是字符串,而我们的应用中, 非常普遍的做法,就是对提交上来的数据和对象进行绑定,在Struts和Spring中用表单中项目的名称和对象的属性进行对应(并做了简单原始类型的转换)。 但在Tapestry中做法是不同的,DataSqueezerSqueezeAdaptor起着关键性的作用。

我们简单看一下Tapestry中的Test,就不难发现其中转换的规律。

org.apache.tapestry.junit.utils.TestDataSqueezer

    public void testBoolean()
    {
        attempt(Boolean.TRUE, "T");
        attempt(Boolean.FALSE, "F");
    }

    public void testNull()
    {
        attempt(null, "X");
    }

    public void testByte()
    {
        attempt(new Byte((byte) 0), "b0");
        attempt(new Byte((byte) -5), "b-5");
        attempt(new Byte((byte) 72), "b72");
    }

    public void testFloat()
    {
        attempt(new Float(0), "f0.0");
        attempt(new Float(3.1459), "f3.1459");
        attempt(new Float(-37.23), "f-37.23");
    }

    public void testDouble()
    {
        attempt(new Double(0), "d0.0");
        attempt(new Double(3.1459), "d3.1459");
        attempt(new Double(-37.23), "d-37.23");
    }

    public void testInteger()
    {
        attempt(new Integer(0), "0");
        attempt(new Integer(205), "205");
        attempt(new Integer(-173), "-173");
    }

    public void testLong()
    {
        attempt(new Long(0), "l0");
        attempt(new Long(800400300l), "l800400300");
        attempt(new Long(-987654321l), "l-987654321");
    }

    public void testShort()
    {
        attempt(new Short((short) 0), "s0");
        attempt(new Short((short) -10), "s-10");
        attempt(new Short((short) 57), "s57");
    }

    /** @since 2.2 * */

    public void testCharacter()
    {
        attempt(new Character('a'), "ca");
        attempt(new Character('Z'), "cZ");
    }

    public void testString()
    {
        attempt("Now is the time for all good men ...", "SNow is the time for all good men ...");
        attempt("X marks the spot!", "SX marks the spot!");
        attempt("So long, sucker!", "SSo long, sucker!");
    }

    public void testComponentAddress()
    {
        ComponentAddress objAddress = new ComponentAddress("framework:DirectLink",
                "component.subcomponent");
        attempt(objAddress, "Aframework:DirectLink,component.subcomponent");

        objAddress = new ComponentAddress("framework:DirectLink", null);
        attempt(objAddress, "Aframework:DirectLink,");
    }

每种类型都对应一个或者一串前缀,一个字符串如果开头单个字母是前缀中的一个的时候,那么就被确定为该类型,如"T""TF"中之一,那么"T"字符串被确定为Boolean形。

Table 1. SqueezeAdaptor

类型Adaptor前缀举例
null XX
BooleanBooleanAdaptorTFT, F
ByteByteAdaptorbb127 ,b0, b-5
CharacterCharacterAdaptorcca, cZ
DoubleDoubleAdaptordd0.0, d3.1459, d-37.23
FloatFloatAdaptorff0.0, f3.1459, f-37.23
IntegerIntegerAdaptor-01234567890, 205, -173
LongLongAdaptorll0, l800400300, l-987654321
ShortShortAdaptorss0, s-10, s57
StringStringAdaptorSSNow is the time, SX marks
SerializableSerializableAdaptorOZO123NWEQ..., Z23423EXF...
ComponentAddressComponentAddressAdaptorAAframework:DirectLink,component.subcomponent

8. Form处理的核心阶段Rewind

所有的Form组件(Form Control Component),如:文本框,文本域,提交按钮,选择列表等等(但是Form本身不属于此列,它只是一个装Form组件的容器) 都继承自AbstractFormComponent, 在AbstractFormComponent中有一个非常重要的方法rewindFormComponent。 当画面中的Form进行提交时,第一个阶段就是Rewind阶段。 Form对象会首先进行循环调用其中所有的Form组件的rewindFormComponent方法。

根据Form组件的性质,每个Form组件中的rewindFormComponent方法的实现是不同的, 如文本框,列表,单选,多选等都是简单的把提交上去的值重新付给这些组件对象, 并且在同时进行了对Page中相应属性的绑定(即把这些值更新到Page的属性当中去)。但是如提交按钮,提交链接中所做的则是把Template中指定的监听加到Form对象当中去, 以便Rewind过程结束之前,调用这些Page中的监听方法(FormSupportImpl的runDeferredRunnables()方法)。

9. Tapestry中的Validation

9.1. Tapestry中提供的Validator

Tapestry中提供了许多Validator,我们直接使用他们就可能完成大部分通常的Check。

关联源文件tapestry.form.validator.xml
  <contribution configuration-id="Validators">
    <validator name="email" configurable="false" class="Email"/>
    <validator name="required" configurable="false" class="Required"/>
    <validator name="max" class="Max"/>
    <validator name="maxDate" class="MaxDate"/>
    <validator name="maxLength" class="MaxLength"/>
    <validator name="min" class="Min"/>
    <validator name="minDate" class="MinDate"/>
    <validator name="minLength" class="MinLength"/>
    <validator name="pattern" class="Pattern"/>    
  </contribution>

9.2. 简单的例子

<input type="text" jwcid="minAndMax@TextField"
       value="ognl:minAndMax"
       translator="translator:number,pattern=#####"
       displayName="literal:Min,Max"
       validators="validators:min=3,max=5"/>

上面是一个非常简单的例子,达到的目的就是改组件对应的Page的int属性minAndMax必须在3和5之间,由于Page的minAndMax属性为int型, 不是字符串,所以我们必须告诉Tapestry如何将页面中的值转换为Page的属性,所以这里面就指定了translator。

9.3. Validation Specification - validators属性的格式

关联源文件org.apache.tapestry.form.validator.ValidatorFactory

里面的内容前缀首先是validators:,Validator之间用逗号分隔,每个Validator的格式可以是下面形式

  1. name
  2. name=value
  3. name[message]
  4. name[%message-key]
  5. name=value[message]
  6. name=value[%message-key]
  7. $name

其中name为Validator名,如:email, required, max, maxDate, min等等。

value为传入的参数值,如:min=3, max=5等。

message为当该Validator校验失败后显示的Message。

message-key为本地化文件中Message的Key值, 如Tapestry Page Name为Login的话, 那么相应的有Login.html,Login.page,Login.properties,Login_zh_CN.properties等,这里面的两个properties文件就是本地化文件。

下面是具体的例子

ValidationExamples.html中

<input type="text" jwcid="minAndMaxWithMessage@TextField"
       value="ognl:minAndMaxWithMessage"
       translator="translator:number,pattern=#####"
       displayName="literal:Min,Max"
       validators="validators:min=3[%must-larger-than-3],max=5[Must less than 5]"/></td>

那么在ValidationExamples.properties中为

must-larger-than-3=Field {0} must larger than 3.

需要注意的是,其中的{0}参数会被自动用Field名替换。

$name中的name是对一个bean的引用,该bean可以在Page Specification中定义。

如在ValidationExamples.page中定义为

    <bean name="zipValidator" class="org.apache.tapestry.form.validator.Pattern">
        <set name="pattern" value="message:zip-code-pattern"/>
        <set name="message" value="message:zip-code-message"/>
    </bean>

那么在ValidationExamples.html中就可以像下面这样引用。

<input type="text" jwcid="pattern@TextField" 
       value="ognl:pattern" 
       displayName="literal:Pattern" 
       validators="validators:$zipValidator"/>

10. 特殊用语的解释

10.1. classpath:/META-INF/hivemodule.xml中的classpath

表示在classpath下/META-INF/中的hivemodule.xml文件 , 所谓classpath,在WEB应用程序中,通常指/WEB-INF/lib下的每一个jar中,或者/WEB-INF/classes下。

10.2. ${servlet.name}.application中的${}

是一个替代符号,或者叫变量名,上面的表达表示一个通用的名字,如在应用程序中可能具体为MyTapestryExamples.application, PetStore.application等。

 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值