Tapestry-Again

一旦你已经开始使用Tapestry了,那么你就走上了一条不归路。。。

Once you work in Tapestry there's no going back!

Page或者Component里面的元素都是private类型的。

@InjectPage,Tapestry是一个被管理的环境,我们不直接去创建一个对象,而是让Tapestry去管理所有对象的生命周期。如果我们需要一个Page对象,那么只需要用@InjectPage这个注解。

When creating your own applications, make sure that the objects stored in final variables are thread safe.

当创建我们的应用时,确保我们声明的final类型的变量是线程安全的。

Tapestry uses an approach based on the Post/Redirect/Get pattern.

Tapestry使用的是一个基于POST/REDIRECT/GET的方式来获取服务器信息的。

<t:actionLink t:id="makeGuess" context="1">1</t:actionLink> -> <a href="/bootcss/guess.actionlink/1">1</a>

<t:actionLink id="makeGuess" context="1">1</t:actionLink> -> <a id="makeGuess" href="/bootcss/guess.actionlink/1">1</a>

@Persist

Tapestry的持久化默认是session级别的。

Flash级别的话,只会保持一个请求,下一次请求发出的时候就会消失。

<img src="${request.contextPath}/images/catalog/product_${productId}.png"/>

犯了一个超级2的毛病,

<t:loop source="1..10" value="var:index">${var:index}</t:loop>

这种写法可以说是没问题的,小猿想不明白了就,为啥他就能够循环着把1到10打印出来呢,渲染的过程就是如何把控件中的值写成html的过程。

当afterrender返回true的时候可能就完成渲染了,如果返回的是false的话会继续渲染。

下图中的各个阶段,据官方介绍,SetupRender, BeginRender, AfterRender, CleanupRender是经常被使用的。其它的貌似是给mixin专门设计的。

@SetupRender

This is a good place to read component parameters and use them to set temporary instance variables.

这是一个读取组件参数,并且设定临时变量的好地方。

The general rule is, only use the ${...} syntax in non-Tapestry-controlled locations in your template, such as in attributes of ordinary HTML elements and in plain-text areas of your template.

不要随意地使用${...}语法,基本准则是我们仅仅在非tapestry控制的地方使用,例如在标准的HTML元素的属性处,或者是一个基本的描述处。。。

<t:textfield t:id="color" value="${color}"/> => <t:textfield t:id="color" value="color"/>

<img src="context:images/banner.png"/> => <img src="${context:images/banner.png}"/>

看如下的tml文件,其中有一个<html>标签,并且有一个t:type,其实这个<html>标签是必须的,在Tapestry渲染的时候,会把页面中的<h1>和<p>放到layout.tml的<t:body />处,然后把<html>标签去掉。

<html t:type="layout" xmlns:t="http://tapestry.apache.org/schema/tapestry_5_1_0.xsd">
   <h1>Welcome to the Nifty Web Application!</h1>
   <p>
        Would you like to <t:pagelink page="login">Log In</t:pagelink>?
   </p>
</html>

Page Render Requests

1 Pages may have an activation context. The activation context represents persistent information about the state of the page.

页面可以有些激活内容(表现为URL上的参数)。它是用来显示页面内容的一个决定因素,例如ID什么的。

2 When no explicit activation context is provided, the page itself is queried for its activation context.

然而并不是所有的页面都有参数,如果没有提供参数的时候,页面就会自己去问(请求)这个参数。

The event name is "passivate" . The return value of the method is used as the context. 

这个请求触发的方法就是onPassivate(),他会返回一个参数,这个参数就会被当做页面请求的参数。

1 一个页面中如果存在一个pagelink的话,如果这个pagelink没有参数(context)的话,那么这个pagelink的page所指定的页面java类的onPassivate就会被触发,以获取相应的context;

2 如果这个页面的pagelink存在参数的话,那么就不会触发java类的onPassivate方法。

3 进入到一个页面的时候,首先会触发它的onActivate方法,@SetupRender之后,@AfterRender之前,触发onPassivate方法

下面的这个tml代码:

<t:ActionLink context="${literal:actionLink}">ActionLink Test</t:ActionLink>
被render之后形成:
<a href="/bootcss/action/index.actionlink/actionLink">ActionLink Test</a>

<t:EventLink context="${literal:eventLink}" event="testEvent">EventLink Test</t:EventLink>
被render之后形成:
<a href="/bootcss/action/index:testevent/eventLink">EventLink Test</a>

@OnEvent注解的使用

1 Form或者ActionLink的时候,

@OnEvent(value="action", component="componentId")

其中value的值固定,EventConstants.ACTION/action,component的值是actionLink的t:id指定的值。并且,t:ActionLink的id不要指定。

INSTEAD: void onActionFromComponentId() {...}

2 EventLink的时候,

@OnEvent的value的值是eventLink的event指定的值。component的值不用指定也可以。

INSTEAD: void onEventName() {...}

当有多个方法满足条件时,按照下面的顺序执行:

  • Base class methods before sub-class methods.
  • 基类早于子类
  • Matching methods within a class in alphabetical order.
  • 一个类中有多个方法满足时,按照字母顺序执行
  • For a single method name with multiple overrides, by number of parameters, descending.
  • 如果一个方法被重载了,按照参数个数的降序执行。

如果想要阻止其继续寻找合适的方法的话,可以让方法返回一个true。

Tapestry持久化

针对于单个page而言

Tapestry有一个@Persist注解,它有三种类型,Session,Flash,Client(不推荐)。

Session,默认级别,经过多次请求,它会保存一个页面上的数据。

Flash,(PersistenceConstants.FLASH),信息仍然是储存在session里面,但是它不会被保存太长时间,一旦它在页面上显示过一次了就会被从session中删除。

Tapestry的请求时Post-Redirect-Get类型的,应该说它会在一个请求完成之后就删除。

凡是被@Persist标注的变量是不允许设定默认值的,无论直接赋值或者在构造器中设定值。

针对多个multi-page而言

Session State Objects for complex objects, and Session Attributes for simple types.

Session State Object(SSO),是一个被@SessionState注解标记的变量,这个变量储存的内容被一个用户在所有的page中使用,别的用户不可以。

如果一个页面中定义了一个@SessionState类型的变量,那么在别的页面中定义这么一个类型相同的,同样被@SessionState标准的变量,那么这两个变量指向的就是同一个变量。

注意点: 不要把@SessionState用到一个简单类型上。因为Tapestry在@SessionState这个事儿上是认类型不认名字的,可能会因为存在相同类型的两个变量而把前面的变量给覆盖掉了。

当Tapestry第一次发现一个SSO的时候,就会自动的创建这个对象。所以这个对象需要有一个无参构造器。

如果给一个SSO设定了null的话,这个SSO就会被销毁。

@SessionState(create=false)注解决定这个变量不会被自动创建。

    public void contributeApplicationStateManager(MappedConfiguration<Class, ApplicationStateContribution> configuration)
    {
        ApplicationStateCreator<ShoppingCart> creator = new ApplicationStateCreator<ShoppingCart>()
        {
            public ShoppingCart create()
            {
                return new ShoppingCart(new Date());
            }
        };

        configuration.add(ShoppingCart.class, new ApplicationStateContribution("session", creator));
    }

Session Attribute, 是一个被@SessionAttribute标记的变量,他同样是被所有的页面共享的一个seesion级别的变量。

他跟SSO不同,并不是靠类型来获取一个对象,而是靠名称来获取的,这也帮助了在一个大型的项目中工程之间调用session中的内容。

以下两个代码是等价的:

public class Page {
    @SessionAttribute
    private User loggedInUserName;
}
public class Page {
    @SessionAttribute("loggedInUserName")
    private User userName;
}

在使用这个SSO或者SessionAttribute的时候,被共享的session是存在于多个module之间的。所以我们最好定义一个与包名相关的变量,然后赋予它,如下:

public static final String USER_NAME_SESSION_ATTRIBUTE = "com.example.shoppingapp.username";
 
public class Page {
    @SessionAttribute(USER_NAME_SESSION_ATTRIBUTE)
    private User userName;
}

@Inject

1 Block Injection,推荐方式

@Inject
@Id("bar")
private Block barBlock;

2 Resource Injection

  • java.lang.String – The complete id of the component, which incorporates the complete class name of the containing page and the nested id of the component within the page.

java.util.Locale – The locale for the component (all components within a page use the same locale).

org.slf4j.Logger – A Logger instance configured for the component, based on the component's class name. SLF4J is a wrapper around Log4J or other logging toolkits.

org.apache.tapestry5.ComponentResources – The resources for the component, often used to generate links related to the component.

org.apache.tapestry5.ioc.Messages – The component message catalog for the component, from which localized messages can be generated.

3 Asset Injection

@Inject
@Path("context:images/top_banner.png")
private Asset banner;

4 Service Injection

Ajax and Zone

Zones是Tapestry用来实现刷新部分页面的方式。Zone组件会被渲染成一个HTML元素,基本上是DIV。

Zone可以被EventLink,ActionLink,Select,或者是一个Form来实现更新。

有点需要说明的是,如果我们需要使用jquery,那么我们需要引入一个新的包Tapestry-jquery.jar,这样我们就不需要再引入jquery.js了

<dependencies>
...
        <dependency>
            <groupId>org.got5</groupId>
            <artifactId>tapestry5-jquery</artifactId>
            <version>3.3.7</version>
        </dependency>
</dependencies>

    <repositories>
      <repository>
          <id>devlab722-repo</id>
          <url>http://nexus.devlab722.net/nexus/content/repositories/releases
          </url>
          <snapshots>
              <enabled>false</enabled>
          </snapshots>
      </repository>
  
      <repository>
          <id>devlab722-snapshot-repo</id>
          <url>http://nexus.devlab722.net/nexus/content/repositories/snapshots
          </url>
          <releases>
              <enabled>false</enabled>
          </releases>
      </repository>
    </repositories>

Zone是一个被@InjectComponent注解的变量,它的名称跟页面中的t:zone的id相同

@Inject private Request request;是用来解析一个请求是否是ajax请求的,如:request.isXHR() ? /* YES */ someZone.getBody() : /* NO, reture page itself */ null;

t:zone标签有一个visible属性,用来定义是否直接显示该组件;

有一个update属性,用来定义更新的时候的样式,默认为highlight,show,slidedown,slideup,fade可选。

Form and Validation

@Component private Form form;

这样引入一个form组件,这个组件的一个用途就是在上面追加错误信息。

如: form.recordError("Check your information buddy~");

另外还可以指定到某一个特定的控件,这个控件也需要被引入:

@InjectComponent private TextField username;

然后,form.recordError(username, "Check your information buddy~");

java类中可能会有一个方法,名称是这样的:onValidateFromSomeForm() {...};其中somefrom指的是页面上的form的id。

当完成了验证之后,java类中的onSuccess()方法就会被触发。。。

t:textfield控件,我们需要给它指定t:id,这个t:id会生成html中input的name和id,我们也可以给它指定一个value,但是这没有什么必要,Tapestry会自动去绑定具有相同id的变量。

FormValidation,输入控件可以有一个validate属性,这个属性可以指定这些值:required, min, max, minlength, maxlength, none, email, regexp,这些都是Tapestry内置的客户端验证。

修改默认的错误信息,只需要追加相应的properties信息即可。
app.properties中key的形式如:formid-componentid-checkname-message;
pagename.properties中key的形式如:componentid-checkname-message;

File upload

在一个t:form中,<t:FileUpload t:id="file" t:value="file" validate="required"/>就会形成一个文件上传控件。

java端代码:

    @Persist(PersistenceConstants.FLASH)
    @Property
    private String message;
    
    @Property
    private UploadedFile file;
    
    public void onSuccess() {
        File copied = new File("c:/" + file.getFileName());
        
        file.write(copied);
    }
    
    
    Object onUploadException(FileUploadException ex)
    {
        message = "Upload exception: " + ex.getMessage();
    
        return this;
    }

Tapestry Hibernate

Tapestry Hibernate管理的entity们,会被自动的创建一个valueEncoder。不明白encode和decode的区别。

总之Tapestry会自动的通过一个entity的id来确定一个entity实体。并且这个id会被强转成string类型。

所以,我们想要根据一个entity的id获取一个entity只需要用:

void onActivate(SomeEntity entity) {
    this.entity = entity;
}

SomeEntity onPassivate() {
    return this.entity;
}

然而 ,Tapestry提供了另外一种方式,即注解@PageActivationContext,

/**
    Annotation for a field for which the page activation context handlers (onActivate and onPassivate) should be created.
    In order to use this annotation you must contribute a org.apache.tapestry5.ValueEncoder for the class of the annotated property.
    You should not use this annotation within a class that already has an onActivate() or onPassivate() method; doing so will result in a runtime exception.
*/

由于ValueEncoder已经被Tapestry Hibernate创建了,所以就无需费心了^.^, 另外,这个注解是不能跟onPassivate和onAcitivate一起使用的。

如果给一个entity追加一个@Persist("entity")注解的话,那么这个变量就会被保存在session中,包括类型和该类型的id。

这个时候是不需要onPassivate()方法的,Tapestry会直接去session当中去获取这个变量类型的id,onActivate()还是需要的。

@CommitAfter

所有的Hibernate操作都发生在一个事务当中,只有这个request请求完成之后事务才会提交。因此我们需要@CommitAfter这个注解来帮忙。这个注解放到哪一个方法上面,执行哪一个方法的时候事务就会被提交。

 

原文地址:https://www.cnblogs.com/voctrals/p/3714955.html