组件(Components)

优质
小牛编辑
134浏览
2023-12-01

如前所述,除了Page是根组件并包含一个或多个子组件之外,组件和页面是相同的。 组件始终驻留在页面内,并几乎完成页面的所有动态功能。

Tapestry组件使用interactive AJAX呈现简单的HTML链接到复杂的网格功能。 组件也可以包括另一个组件。 Tapestry组件包括以下项目 -

  • Component Class - Component Class的主要Java类。

  • XML Template - XML模板类似于页面模板。 组件类将模板呈现为最终输出。 某些组件可能没有模板。 在这种情况下,输出将由组件类本身使用MarkupWriter类生成。

  • 正文 - 页面模板中指定的组件可能具有自定义标记,并且它被称为“组件主体”。 如果组件模板具有《body /》元素,则“body /”元素将被组件的主体替换。 这类似于前面在XML模板部分中讨论的布局。

  • Rendering - 渲染是将组件的XML模板和主体转换为组件的实际输出的过程。

  • Parameters - 用于在组件和页面之间创建通信,从而在它们之间传递数据。

  • Events - 将组件的功能委托给其容器/父级(页面或其他组件)。 它广泛用于页面导航目的。

渲染(Rendering)

组件的呈现是在一系列预定义的阶段中完成的。 组件系统中的每个阶段都应该具有由组件类中的约定或注释定义的相应方法。

// Using annotaion 
@SetupRender 
void initializeValues() { 
   // initialize values 
}
// using convention 
boolean afterRender() { 
   // do logic 
   return true; 
}

下面列出了阶段,方法名称和注释。

注解默认方法名称
@SetupRendersetupRender()
@BeginRenderbeginRender()
@BeforeRenderTemplatebeforeRenderTemplate()
@BeforeRenderBodybeforeRenderBody()
@AfterRenderBodyafterRenderBody()
@AfterRenderTemplateafterRenderTemplate()
@AfterRenderafterRender()
@CleanupRendercleanupRender()

每个阶段都有特定的目的,它们如下 -

SetupRender (SetupRender)

SetupRender启动渲染过程。 它通常设置组件的参数。

BeginRender (BeginRender)

BeginRender开始渲染组件。 它通常呈现组件的begin/start标记。

BeforeRenderTemplate

BeforeRenderTemplate用于装饰XML模板,在模板周围添加特殊标记。 它还提供跳过模板渲染的选项。

BeforeRenderBody (BeforeRenderBody)

BeforeRenderTemplate提供了跳过组件body元素渲染的选项。

AfterRenderBody (AfterRenderBody)

在渲染组件的主体之后将调用AfterRenderBody。

AfterRenderTemplate

在渲染组件的模板后将调用AfterRenderTemplate。

AfterRender (AfterRender)

AfterRender是BeginRender的对应物,通常呈现close标签。

CleanupRender (CleanupRender)

CleanupRender是SetupRender的对应物。 它释放/处理渲染过程中创建的所有对象。

渲染阶段的流程不仅仅是前向的。 它根据阶段的返回值在阶段之间来回移动。

例如,如果SetupRender方法返回false,则渲染跳转到CleanupRender阶段,反之亦然。 要清楚地了解不同阶段之间的流动,请检查下图中的流程。

注释列表

简单组件

让我们创建一个简单的组件Hello,它将输出消息作为“Hello,Tapestry”。 以下是Hello组件及其模板的代码。

package com.example.MyFirstApplication.components;  
public class Hello {  
}
<html  
   xmlns:t = "https://tapestry.apache.org/schema/tapestry_5_4.xsd" 
   xmlns:p = "tapestry:parameter"> 
   <div> 
      <p>Hello, Tapestry (from component).</p> 
   </div> 
</html>

可以在页面模板中调用Hello组件 -

<html title = "Hello component test page" 
   xmlns:t = "https://tapestry.apache.org/schema/tapestry_5_4.xsd" 
   xmlns:p = "tapestry:parameter"> 
<t:hello />  
</html>

类似地,组件可以使用MarkupWriter而不是模板呈现相同的输出,如下所示。

package com.example.MyFirstApplication.components; 
import org.apache.tapestry5.MarkupWriter; 
import org.apache.tapestry5.annotations.BeginRender;   
public class Hello { 
   @BeginRender 
   void renderMessage(MarkupWriter writer) { 
      writer.write("<p>Hello, Tapestry (from component)</p>"); 
   } 
}

让我们更改组件模板并包含

元素,如下面的代码块所示。
<html>  
   xmlns:t = "https://tapestry.apache.org/schema/tapestry_5_4.xsd" 
   xmlns:p = "tapestry:parameter"> 
   <div> 
      <t:body /> 
   </div> 
</html>

现在,页面模板可以在组件标记中包含主体,如下所示。

<html title = "Hello component test page" 
   xmlns:t = "https://tapestry.apache.org/schema/tapestry_5_4.xsd" 
   xmlns:p = "tapestry:parameter"> 
   <t:hello> 
      <p>Hello, Tapestry (from page).</p> 
   </t:hello> 
</html>

输出如下 -

<html> 
   <div> 
      <p>Hello, Tapestry (from page).</p> 
   </div> 
</html>

参数 (Parameters)

这些参数的主要目的是在组件的字段和页面的属性/资源之间创建连接。 使用参数,组件及其相应的页面在彼此之间进行通信和传输数据。 这称为Two Way Data Binding

例如,用于表示用户管理页面中的年龄的文本框组件通过参数获得其初始值(在数据库中可用)。 同样,在用户的年龄更新并提交回来之后,组件将通过相同的参数发回更新的年龄。

要在组件类中创建新参数,请声明一个字段并指定@Parameter注释。 这个@Parameter有两个可选参数,它们是 -

  • required - 使参数成为必需参数。 如果没有提供,Tapestry会引发异常。

  • value - 指定参数的默认值。

该参数应在页面模板中指定为组件标记的属性。 应使用Binding Expression/Expansion指定属性的值,我们在前面的章节中对此进行了讨论。 我们之前学到的一些扩展是 -

  • Property expansion (prop:«val») - 从页面类的属性中获取数据。

  • Message expansion (message:«val») - 从index.properties文件中定义的密钥获取数据。

  • Context expansion (context:«val») - 从Web上下文文件夹/ src/main/webapp获取数据。

  • Asset expansion (asset:«val») - 从jar文件中嵌入的资源中获取数据,/ META-INF/assets。

  • Symbol expansion (symbol:«val») - 从AppModule.javafile中定义的符号中获取数据。

Tapestry有许多有用的扩展,其中一些扩展如下 -

  • Literal expansion (literal:«val») - 文字字符串。

  • Var expansion (var:«val») - 允许读取或更新组件的渲染变量。

  • Validate expansion (validate:«val») - 用于指定对象验证规则的专用字符串。 例如,验证:required,minLength = 5。

  • Translate (translate:«val») - 用于在输入验证中指定Translator类(将客户端转换为服务器端表示)。

  • Block (block:«val») - 模板中块元素的id。

  • Component (component:«val») - 模板中另一个组件的id。

除属性扩展和Var扩展外,所有上述扩展都是只读的。 组件使用它们与页面交换数据。 使用扩展作为属性值时,不应使用${...} 。 相反,只使用没有美元和大括号符号的扩展。

组件使用参数

让我们通过修改Hello组件来创建一个新组件HelloWithParameter,通过在组件类中添加name参数并相应地更改组件模板和页面模板来动态呈现消息。

  • 创建一个新的组件类HelloWithParameter.java

  • 添加一个私有字段,并使用@Parameter注释命名。 使用必需的参数使其成为必需参数。

@Parameter(required = true) 
private String name;
  • 使用@Propery注释添加私有字段,结果。 result属性将在组件模板中使用。 组件模板无权访问使用@Parameter注释的字段,并且只能访问使用@Property注释的字段。 组件模板中可用的变量称为“渲染变量”。

@Property 
 private String result;
  • 添加RenderBody方法并将name参数中的值复制到result属性。

@BeginRender 
void initializeValues() { 
   result = name; 
}
  • 添加新的组件模板HelloWithParamter.tml并使用result属性呈现消息。

<div> Hello, ${result} </div>
  • 在测试页面(testhello.java)中添加一个新属性Username。

public String getUsername() { 
   return "User1"; 
}
  • 在页面模板中使用新创建的组件,并在HelloWithParameter组件的name参数中设置Username属性。

<t:helloWithParameter name = "username" /> 

完整清单如下 -

package com.example.MyFirstApplication.components;  
import org.apache.tapestry5.annotations.*;  
public class HelloWithParameter { 
   @Parameter(required = true) 
   private String name; 
   @Property 
   private String result; 
   @BeginRender 
   void initializeValues() { 
      result = name; 
   } 
}
<html  
   xmlns:t = "https://tapestry.apache.org/schema/tapestry_5_4.xsd" 
   xmlns:p = "tapestry:parameter"> 
   <div> Hello, ${result} </div> 
</html>
package com.example.MyFirstApplication.pages;  
import org.apache.tapestry5.annotations.*;  
public class TestHello { 
   public String getUsername() { 
      return "User1"; 
   } 
}
<html title = "Hello component test page" 
   xmlns:t = "https://tapestry.apache.org/schema/tapestry_5_4.xsd" 
   xmlns:p = "tapestry:parameter"> 
   <t:helloWithParameter name = "username" />
</html> 

结果如下 -

<div> Hello, User1 </div>

高级参数

在前面的章节中,我们分析了如何在自定义组件中创建和使用简单参数。 高级参数也可以包含完整标记。 在这种情况下,应在组件标记内指定标记,例如页面模板中的子部分。 内置的if组件具有成功和失败条件的标记。 成功标记指定为组件标记的主体,并使用elseparameter参数指定失败标记。

让我们看看如何使用if组件。 if组件有两个参数 -

  • test - 基于简单属性的参数。

  • Else - 如果条件失败,用于指定备用标记的高级参数

Tapestry将使用以下逻辑检查test属性的值,并返回true或false。 这称为Type Coercion ,一种将一种类型的对象转换为具有相同内容的另一种类型的方法。

  • 如果数据类型为String ,则为“True”(如果为非空)而不是文字字符串“False”(不区分大小写)。

  • 如果数据类型为Number ,则为True,如果非零。

  • 如果数据类型为Collection ,则为True(如果非空)。

  • 如果数据类型是Object ,则为True(只要它不为null)。

如果条件通过,则组件呈现其主体; 否则,它呈现else参数的主体。

完整清单如下 -

package com.example.MyFirstApplication.pages; 
public class TestIf { 
   public String getUser() { 
      return "User1"; 
   } 
}

<html title = "If Test Page" 
   xmlns:t = "http://tapestry.apache.org/schema/tapestry_5_4.xsd" 
   xmlns:p = "tapestry:parameter">  
   <body> 
      <h1>Welcome!</h1>  
      <t:if test = "user"> 
         Welcome back, ${user} 
         <p:else>
            Please <t:pagelink page = "login">Login</t:pagelink>  
         </p:else> 
      </t:if>
   </body>
</html>

组件事件/页面导航

Tapestry应用程序是一collection of Pages彼此交互collection of Pages 。 到目前为止,我们已经学会了如何在没有任何通信的情况下创建单个页面。 Component事件的主要目的是使用服务器端事件在页面之间(在页面内)提供交互。 大多数组件事件源自客户端事件。

例如,当用户单击页面中的链接时,Tapestry将使用目标信息调用同一页面本身,而不是调用目标页面并引发服务器端事件。 Tapestry页面将捕获事件,处理目标信息并执行服务器端重定向到目标页面。

Tapestry遵循Post/Redirect/Get (RPG) design pattern进行页面导航。 在RPG中,当用户通过提交表单执行发布请求时,服务器将处理发布的数据,但不会直接返回响应。 相反,它将对另一个页面进行客户端重定向,这将输出结果。 RPG模式用于通过浏览器后退按钮,浏览器刷新按钮等防止重复表单提交,Tapestry通过提供以下两种类型的请求来提供RPG模式。

  • Component Event Request - 此类请求以页面中的特定组件为目标,并在组件内引发事件。 此请求仅执行重定向,不输出响应。

  • Render Request - 这些类型的请求以页面为目标,并将响应流回客户端。

要了解组件事件和页面导航,我们需要知道tapestry请求的URL模式。 两种请求的URL模式如下 -

  • Component Event Requests -

/<<page_name_with_path>>.<<component_id|event_id>>/<<context_information>>
  • Render Request -

/<<page_name_with_path>>/<<context_information>>

URL模式的一些示例是 -

  • 索引页面可以通过https://«domain»/«app»/index请求。

  • 如果索引页面在子文件夹admin下可用,则可以通过https://«domain»/«app»/admin/index请求它。

  • 如果用户在索引页面中单击带有id testActionLink component ,则URL将为https://«domain»/«app»/index.test

事件 (Events)

默认情况下,Tapestry会为所有请求引发OnPassivateOnActivate事件。 对于组件事件请求类型,tapestry根据组件引发其他一个或多个事件。 ActionLink组件引发一个Action事件,而Form组件引发多个事件,如Validate, Success等,

可以使用相应的方法处理程序在页面类中处理事件。 方法处理程序通过方法命名约定或@OnEvent批注创建。 方法命名约定的格式为On«EventName»From«ComponentId»

具有id test的ActionLink组件的操作事件可以通过以下方法之一处理 -

void OnActionFromTest() { 
}  
@OnEvent(component = "test", name = "action") 
void CustomFunctionName() { 
} 

如果方法名称没有任何特定组件,则将为具有匹配事件的所有组件调用该方法。

void OnAction() { 
} 

OnPassivate和OnActivate事件

OnPassivate用于为OnActivate事件处理程序提供上下文信息。 通常,Tapestry提供上下文信息,它可以在OnActivateevent处理程序中用作参数。

例如,如果上下文信息是int类型的3,那么OnActivate事件可以被称为 -

void OnActivate(int id) { 
} 

在某些情况下,上下文信息可能不可用。 在这种情况下,我们可以通过OnPassivate事件处理程序向OnActivate事件处理程序提供上下文信息。 OnPassivate事件处理程序的返回类型应该用作OnActivate事件处理程序的参数。

int OnPassivate() { 
   int id = 3; 
   return id; 
} 
void OnActivate(int id) { 
} 

事件处理程序返回值

Tapestry根据事件处理程序的返回值发出页面重定向。 事件处理程序应返回以下任何一个值。

  • Null Response - 返回空值。 Tapestry将构造当前页面URL并作为重定向发送到客户端。

public Object onAction() { 
   return null; 
}
  • String Response - 返回字符串值。 Tapestry将构造与值匹配的页面的URL,并作为重定向发送给客户端。

public String onAction() { 
   return "Index"; 
}
  • Class Response - 返回页面类。 Tapestry将构造返回的页面类的URL并作为重定向发送到客户端。

public Object onAction() { 
   return Index.class 
}
  • Page Response - 返回使用@InjectPage注释的字段。 Tapestry将构造注入页面的URL并作为重定向发送到客户端。

@InjectPage 
private Index index;  
public Object onAction(){ 
   return index; 
}
  • HttpError - 返回HTTPError对象。 Tapestry将发出客户端HTTP错误。

public Object onAction(){ 
   return new HttpError(302, "The Error message); 
}
  • Link Response - 直接返回链接实例。 Tapestry将从Link对象构造URL并作为重定向发送到客户端。

  • Stream Response - 返回StreamResponse对象。 Tapestry将直接将流作为响应发送到客户端浏览器。 它用于直接生成报告和图像并将其发送到客户端。

  • Url Response - 返回java.net.URL对象。 Tapestry将从对象获取相应的URL并作为重定向发送到客户端。

  • Object Response - 返回除上述指定值以外的任何值。 Tapestry会引发错误。

事件背景

通常,事件处理程序可以使用参数获取上下文信息。 例如,如果上下文信息是int类型的3,那么事件处理程序将是 -

Object onActionFromTest(int id) {  
} 

Tapestry正确处理上下文信息并通过参数将其提供给方法。 有时,由于编程的复杂性,Tapestry可能无法正确处理它。 那时,我们可能会获得完整的上下文信息并自行处理。

Object onActionFromEdit(EventContext context) { 
   if (context.getCount() > 0) { 
      this.selectedId = context.get(0); 
   } else { 
      alertManager.warn("Please select a document."); 
      return null; 
   } 
}