Apache Tapestry - Templates


Advertisements

Let us consider the Tapestry XML Template in this section. XML Template is a well-formed XML document. The presentation (User Interface) layer of a Page is XML Template. An XML Template have normal HTML markup in addition to the items given below −

  • Tapestry Namespace
  • Expansions
  • Elements
  • Components

Let us now discuss them in detail.

Tapestry Namespace

Tapestry Namespaces are nothing but XML Namespaces. Namespaces should be defined in the root element of the template. It is used to include Tapestry Components and component related information in the Template. The most commonly used namespaces are as follows −

  • xmlns:t = “https://tapestry.apache.org/schema/tapestry_5_4.xsd” — It is used to identify Tapestry's Elements, Components and Attributes.

  • xmlns:p = “tapestry:parameter” — It is used to pass arbitrary chunks of code to components.

An example of Tapestry Namespace is as follows −

<html xmlns:t = "https://tapestry.apache.org/schema/tapestry_5_3.xsd" 
   xmlns:p = "tapestry:parameter"> 
   
   <head> 
      <title>Hello World Page</title> 
   </head>  
   <body> 
      <h1>Hello World</h1> 
      <t:eventlink page = "Index">refresh page</t:eventlink> 
   </body> 
</html>

Expansions

Expansion is simple and efficient method to dynamically change the XML Template during rendering phase of the Page. Expansion uses ${<name>} syntax. There are many options to express the expansion in the XML Template. Let us see some of the most commonly used options −

Property Expansions

It maps the property defined in the corresponding Page class. It follows the Java Bean Specification for property definition in a Java class. It goes one step further by ignoring the cases for property name. Let us change the “Hello World” example using property expansion. The following code block is the modified Page class.

package com.example.MyFirstApplication.pages; 
public class HelloWorld {   
   // Java Bean Property 
   public String getName { 
      return "World!"; 
   } 
}

Then, change the corresponding XML Template as shown below.

<html xmlns:t = "http://tapestry.apache.org/schema/tapestry_5_4.xsd"> 
   <head> 
      <title>Hello World Page</title> 
   </head> 
   <body> 
      <!-- expansion --> 
      <h1>Hello ${name}</h1> 
   </body> 
</html>

Here, we have defined name as Java Bean Property in the Page class and dynamically processed it in XML Template using expansion ${name}.

Message Expansion

Each Page class may or may not have an associated Property file – «page_name».properties in the resources folder. The property files are plain text files having a single key / value pair (message) per line. Let us create a property file for HelloWorld Page at –

“/src/main/resources/com/example/MyFirstApplication/pages/helloworld.properties” and add a “Greeting” message.

Greeting = Hello

The Greeting message can be used in the XML Template as ${message:greeting}

<html xmlns:t = "http://tapestry.apache.org/schema/tapestry_5_4.xsd"> 
   <head> 
      <title>Hello World Page</title> 
   </head> 
   <body> 
      <!-- expansion --> 
      <h1>${message:greeting} ${name}</h1> 
   </body> 
</html>

Elements

Tapestry has a small set of elements to be used in XML Templates. Elements are predefined tags defined under the Tapestry namespace −

https://tapestry.apache.org/schema/tapestry_5_4.xsd

Each element is created for a specific purpose. The available tapestry elements are as follows −

<t:body>

When two components are nested, the parent component's template may have to wrap the child component's template. The <t:body> element is useful in this situation. One of the uses of <t:body> is in the Template Layout.

In general, the User Interface of a web application will have a Common Header, Footer, Menu, etc. These common items are defined in an XML Template and it is called Template Layout or Layout Component. In Tapestry, it needs to be created by an application developer. A Layout Component is just another component and is placed under the components folder, which has the following path – src/main/«java|resources»/«package_name»/components.

Let us create a simple layout component called MyCustomLayout. The code for MyCustomLayout is as follows −

<!DOCTYPE html> 
<html xmlns:t = "http://tapestry.apache.org/schema/tapestry_5_4.xsd"> 
   <head> 
      <meta charset = "UTF-8" />
      <title>${title}</title>  
   </head> 
   <body> 
      <div>Sample Web Application</div> 
      <h1>${title}</h1> 
      <t:body/> 
      
      <div>(C) 2016 Howcodex.</div> 
   </body> 
</html> 

package com.example.MyFirstApplication.components;  

import org.apache.tapestry5.*; 
import org.apache.tapestry5.annotations.*; 
import org.apache.tapestry5.BindingConstants;  

public class MyCustomLayout { 
   @Property 
   @Parameter(required = true, defaultPrefix = BindingConstants.LITERAL) 
      private String title; 
}

In the MyCustomLayout component class, we declared a title field and by using annotation, we have made it mandatory. Now, change HelloWorld.html template to use our custom layout as shown in the code block below.

<html>
   t:type = "mycustomlayout" title = "Hello World Test page"
      xmlns:t = "http://tapestry.apache.org/schema/tapestry_5_4.xsd"> 
   <h1>${message:greeting} ${name}</h1> 
</html>

We can see here that the XML Template does not have head and body tags. Tapestry will collect these details from the layout component and the <t:body> of the layout component will be replaced by the HelloWorld Template. Once everything is done, Tapestry will emit similar markup as specified below −

<!DOCTYPE html> 
<html> 
   <head> 
      <meta charset = "UTF-8" /> 
      <title>Hello World Test Page</title> 
   </head> 
   <body> 
      <div>Sample Web Application</div> 
      <h1>Hello World Test Page</h1> 
      <h1>Hello World!</h1> 
      <div>(C) 2016 Howcodex.</div> 
   </body> 
</html>

Layouts can be nested. For example, we may extend our custom layout by including administration functionality and use it for admin section as specified below.

<html t:type = "MyCommonLayout" 
   xmlns:t = "http://tapestry.apache.org/schema/tapestry_5_4.xsd"> 
   
   <div><!-- Admin related items --><div> 
   <t:body/> 
  
</html>

<t:container>

The <t:container> is a top-level element and includes a tapestry namespace. This is used to specify the dynamic section of a component.

For example, a grid component may need a template to identify how to render its rows - tr (and column td) within a HTML table.

<t:container xmlns:t = "http://tapestry.apache.org/schema/tapestry_5_4.xsd"> 
   <td>${name}</td> 
   <td>${age}</td> 
</t:container>

<t:block>

The <t:block> is a placeholder for a dynamic section in the template. Generally, block element does not render. Only, components defined in the template uses block element. Components will inject data dynamically into the block element and render it. One of the popular use case is AJAX.

The block element provides the exact position and markup for the dynamic data to be rendered. Every block element should have a corresponding Java Property. Only then it can be dynamically rendered. The id of the block element should follow Java variable identifier rules. The partial sample is provided below.

@Inject 
private Block block;  
<html t:type = "mycustomlayout" title = "block example" 
   xmlns:t = "https://tapestry.apache.org/schema/tapestry_5_4.xsd" 
   xmlns:p = "tapestry:parameter">  
<h1>${title}</h1>  
<!--  
   ... 
   ...  
--> 
<t:block t:id = "block"> 
   <h2>Highly dynamic section</h2> 
   I'v been updated through AJAX call 
   The current time is: <strong>${currentTime}</strong>
</t:block>  
<!--  
   ... 
   ...  
-->  
</html>

<t:content>

The <t:content> element is used to specify the actual content of the template. In general, all the markup is considered part of the template. If <t:content> is specified, only the markup inside it will be considered. This feature is used by designers to design a page without a layout component.

<t:remove>

The <t:remove> is just the opposite of content element. The markup inside the remove element is not considered part of the template. It can be used for server only comments and for designing purposes.

Assets

Assets are static resource files such as style sheets, images and JavaScript files. Generally, assets are placed in the web application root directory /src/main/webapp.

<head> 
   <link href = "/css/site.css" rel = "stylesheet" type = "text/css"/>

Tapestry also treats files stored in the Java Classpath as Assets. Tapestry provides advanced options to include Assets into the template through expansion option.

  • Context − Option to get assets available in web context.

<img src = "${context:image/tapestry_banner.gif}" alt = "Banner"/>

asset − Components usually store its own assets inside the jar file along with Java classes. Starting from Tapestry 5.4, the standard path to store assets in classpath is META-INF/assets. For libraries, the standard path to store assets is META-INF/assets/«library_name»/. asset: can also call context: expansion to get assets from the web context.

<img src = "${asset:context:image/tapestry_banner.gif}" alt = "Banner"/>

Assets can be injected into the Tapestry Page or Component using Inject and Path annotation. The parameter for the Path annotation is relative path of the assets.

@Inject 
@Path("images/edit.png") 
private Asset icon;

The Path parameter can also contain Tapestry symbols defined in the AppModule.java section.

For example, we can define a symbol, skin.root with value context:skins/basic and use it as shown below −

@Inject 
@Path("${skin.root}/style.css") 
private Asset style;

Localization

Including resources through tapestry provides extra functionality. One such functionality is “Localization”. Tapestry will check the current locale and include the proper resources.

For example, if the current locale is set as de, then edit_de.png will be included instead of edit.png.

CSS

Tapestry has built-in style sheet support. Tapestry will inject tapestry.css as a part of the core Javascript stack. From Tapestry 5.4, tapestry includes bootstrap css framework as well. We can include our own style sheet using normal link tag. In this case, the style sheets should be in the web root directory – /src/main/webapp/.

<head> 
   <link href = "/css/site.css" rel = "stylesheet" type = "text/css"/>

Tapestry provides advanced options to include style sheets into the template through expansion option as discussed earlier.

<head> 
   <link href = "${context:css/site.css}" rel = "stylesheet" type = "text/css"/> 

Tapestry also provides Import annotation to include style sheet directly into the Java classes.

@Import(stylesheet="context:css/site.css") 
public class MyCommonLayout { 
} 

Tapestry provides a lot of options to manage style sheet through AppModule.java. Some of the important options are −

  • The tapestry default style sheet may be removed.

@Contribute(MarkupRenderer.class) 

public static void 
deactiveDefaultCSS(OrderedConfiguration<MarkupRendererFilter> configuration) { 
   configuration.override("InjectDefaultStyleheet", null); 
} 
  • Bootstrap can also be disabled by overriding its path.

configuration.add(SymbolConstants.BOOTSTRAP_ROOT, "classpath:/METAINF/assets");
  • Enable dynamic minimizing of the assets (CSS and JavaScript). We need to include tapestry-webresources dependency (in pom.xml) as well.

@Contribute(SymbolProvider.class) 
@ApplicationDefaults 

public static void contributeApplicationDefaults( 
   MappedConfiguration<String, String> configuration) { 
   
   configuration.add(SymbolConstants.MINIFICATION_ENABLED, "true"); 
} 

<dependency> 
   <groupId>org.apache.tapestry</groupId> 
   <artifactId>tapestry-webresources</artifactId> 
   <version>5.4</version> 
</dependency> 

Client Side JavaScript

The current generation of web application heavily depends on JavaScript to provide rich client side experience. Tapestry acknowledges it and provide first class support for JavaScript. JavaScript support is deeply ingrained into the tapestry and available at every phase of the programming.

Earlier, Tapestry used to support only Prototype and Scriptaculous. But, from version 5.4, tapestry completely rewritten the JavaScript layer to make it as generic as possible and provide first class support for JQuery, the de-facto library for JavaScript. Also, tapestry encourages Modules based JavaScript programming and supports RequireJS, a popular client side implementation of AMD (Asynchronous Module Definition - JavaScript specification to support modules and its dependency in an asynchronous manner).

Location

JavaScript files are assets of the Tapestry Application. In accordance with asset rules, JavaScript files are placed either under web context, /sr/main/webapp/ or placed inside the jar under META-INF/assets/ location.

Linking JavaScript Files

The simplest way to link JavaScript files in the XML Template is by directly using the script tag, which is − <script language = "javascript" src = "relative/path/to/js"></script>. But, tapestry does not recommend these approaches. Tapestry provides several options to link JavaScript files right in the Page / Component itself. Some of these are given below.

  • @import annotation − @import annotation provides option to link multiple JavaScript library using context expression. It can be applied to both Page class and its method. If applied to a Page class, it applies to all its methods. If applied to a Page's Method, it only applies to that method and then Tapestry links the JavaScript library only when the method is invoked.

@Import(library = {"context:js/jquery.js","context:js/myeffects.js"}) 

public class MyComponent { 
   // ... 
}
  • JavaScriptSupport interface − The JavaScriptSupport is an interface defined by tapestry and it has a method, importJavaScriptLibrary to import JavaScript files. JavScriptSupport object can be easily created by simply declaring and annotating with @Environmental annotation.

@Inject @Path("context:/js/myeffects.js") 
private Asset myEffects;  

@Environmental 
private JavaScriptSupport javaScriptSupport;  
void setupRender() { 
   javaScriptSupport.importJavaScriptLibrary(myEffects); 
}
  • JavaScripSupport can only be injected into a component using the @Environmental annotation. For services, we need to use an @Inject annotation or add it as an argument in the service constructor method.

@Inject 
private JavaScriptSupport javaScriptSupport; 
public MyServiceImpl(JavaScriptSupport support) { 
   // ... 
}
  • addScript method − This is similar to the JavaScriptSupport interface except that it uses the addScript method and the code is directly added to the output at the bottom of the page.

void afterRender() { 
   javaScriptSupport.addScript(
      "$('%s').observe('click', hideMe());", container.getClientId()); 
}

JavaScript Stack

Tapestry allows a group of JavaScript files and related style sheets to be combined and used as one single entity. Currently, Tapestry includes Prototype based and JQuery based stacks.

A developer can develop their own stacks by implementing the JavaScriptStack interface and register it in the AppModule.java. Once registered, the stack can be imported using the @import annotation.

@Contribute(JavaScriptStackSource.class) 
public static void addMyStack(
   MappedConfiguration<String, JavaScriptStack> configuration) { 
   
   configuration.addInstance("MyStack", myStack.class); 
}  

@Import(stack = "MyStack") 
public class myPage { 
}
Advertisements