Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: Migration of unmigrated content due to installation of a new plugin

Scrollbar

 

...

Excerpt
hiddentrue

How to create a library of your custom components

Warning

This page has not yet been fully updated for Tapestry 5.4. Things are different and simpler in 5.4 than in previous releases.

Creating Component Libraries

Nearly every Tapestry application includes a least a couple of custom components, specific to the application. What's exciting about Tapestry is how easy it is to package components for reuse across many applications ... and the fact that applications using a component library need no special configuration.

Div
stylefloat:right
titleRelated Articles
classaui-label
Content by Label
showLabelsfalse
showSpacefalse
titleRelated Articles
cqllabel = "components" and space = currentSpace()

A Tapestry component library consists of components (and optionally mixins, pages and component base classes). In addition, a component library will have a module that can define new services (needed by the components) or configure other services present in Tapestry. Finally, components can be packaged with assets: resources such as images, stylesheets and JavaScript libraries that need to be provided to the client web browser.

...

Tapestry doesn't mandate that you use any build system, but we'll assume for the moment that you are using Maven 2. In that case, you'll have a pom.xml file something like the following:

Code Block
XML
languageXMLxml
titlepom.xml

<project>
  <modelVersion>4.0.0</modelVersion>
  <groupId>org.example</groupId>
  <artifactId>happylib</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>jar</packaging>
  <name>happylib Tapestry 5 Library</name>

  <dependencies>
    <dependency>
      <groupId>org.apache.tapestry</groupId>
      <artifactId>tapestry-core</artifactId>
      <version>${tapestry-release-version}</version>
    </dependency>

    <dependency>
      <groupId>org.testng</groupId>
      <artifactId>testng</artifactId>
      <version>5.1</version>
      <classifier>jdk15</classifier>
      <scope>test</scope>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <configuration>
          <source>1.5</source>
          <target>1.5</target>
          <optimize>true</optimize>
        </configuration>
      </plugin>

      <plugin>
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-jar-plugin</artifactId>
           <configuration>
           <archive>
             <manifestEntries>
               <Tapestry-Module-Classes>org.example.happylib.services.HappyModule</Tapestry-Module-Classes>
             </manifestEntries>
           </archive>
           </configuration>
       </plugin>

    </plugins>
  </build>

  <repositories>
    <repository>
      <id>codehaus.snapshots</id>
      <url>http://snapshots.repository.codehaus.org</url>
    </repository>
    <repository>
      <id>OpenQA_Release</id>
      <name>OpenQA Release Repository</name>
      <url>http://archiva.openqa.org/repository/releases/</url>
    </repository>
  </repositories>

  <properties>
    <tapestry-release-version>5.2.0<4-beta-28</tapestry-release-version>
  </properties>
</project>

...

Our component is very simple:

Code Block
java
languagejava
titleHappyIcon.java

package org.example.happylib.components;

import org.apache.tapestry5.Asset;
import org.apache.tapestry5.MarkupWriter;
import org.apache.tapestry5.annotations.Path;
import org.apache.tapestry5.ioc.annotations.Inject;

public class HappyIcon
{
    @Inject
    @Path("happy.jpg")
    private Asset happyIcon;

    boolean beginRender(MarkupWriter writer)
    {
        writer.element("img", "src", happyIcon);
        writer.end();

        return false;
    }
}

...

In our example, we'll use "happy" as the folder name. That means the application will can include the HappyIcon component in the template asusing any of the following, which are all equivalent:

  • <t:happy.happyicon/>
  • or <t:happy.icon/>
  • <img t:type="happy.happyicon"/> or
  • <img t:type="happy/icon"/">

Why "icon" vs. "happyicon"? Tapestry notices that the folder name, "happy" is a prefix or suffix of the class name ("HappyIcon") and creates an alias that strips off the prefix (or suffix). To Tapestry, they are completely identical: two different aliases for the same component class name.

The above naming is somewhat clumsy, and can be improved by introducing an additional namespace into the template:

Code Block
xml
languagexml

<html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_1_0.xsd"
  xmlns:h="tapestry-library:happy">

  ...

  <h:icon/>

  ...
</html>

...

At application startup, Tapestry will read the library module along with all other modules and configure the ComponentClassResolver service using information in the module:

Code Block
java
languagejava
titleHappyModule.java

package org.example.happylib.services;

import org.apache.tapestry5.ioc.Configuration;
import org.apache.tapestry5.services.LibraryMapping;

public class HappyModule
{
    public static void contributeComponentClassResolver(Configuration<LibraryMapping> configuration)
    {
        configuration.add(new LibraryMapping("happy", "org.example.happylib"));
    }
}

...

For Tapestry to load your module at application startup, it is necessary to put an entry in the JAR manifest. This is taken care of in the pom.xml above:

Code Block
xml
languagexml
titlepom.xml (partial)

      <plugin>
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-jar-plugin</artifactId>
           <configuration>
           <archive>
             <manifestEntries>
             <Tapestry-Module-Classes>org.example.happylib.services.HappyModule</Tapestry-Module-Classes>
             </manifestEntries>
           </archive>
           </configuration>
       </plugin>

...

As of Tapestry 5.2, a new step is needed: extending access for the assets. This is accomplished in your library's module class, HappyModule:

Code Block
java
languagejava

public static void contributeRegexAuthorizer(Configuration<String> configuration)
{
    configuration.add("^org/example/happylib/.*\\.jpg$");
}

...

Classpath assets, those packaged in JAR files (such as the happy.jpg asset) are retrieved by the client web browser using a URL that reflects the package name. Tapestry users a special virtual folder, /assets, under the context folder for this purpose.

The image file here would is exposed to the web browser via the URL /happyapp/assets/org/example/happylib/components/happy.jpg (this assumes that the application was deployed as happyapp.war).

...

To handle this problem in Tapestry 5.1 and earlier, you should map your library assets to a versioned folder. This can be accomplished using another contribution from the HappyModule, this time to the ClasspathAssetAliasManager service whose configuration maps a virtual folder underneath /assets to a package:

Code Block
languagejava

public static void contributeClasspathAssetAliasManager(MappedConfiguration<String, String> configuration)
{
    configuration.add("happylib/1.0", "org/example/happylib");
}

With this in place, and the library and applications rebuilt and redeployed, the URL for happy.jpg becomes /happyapp/assets/happylib/1.0/components/happy.jpg. This is shorter, but also incorporates a version number ("1.0") that can be changed in a later release.

In version 5.2 and later, Tapestry automatically creates a mapping for assets inside your JAR. In the above example, the icon image will be exposed as /assets/application version/happy/components/happy.jpg (the application version number is incorporated into the URL). The "happy" portion is a virtual folder that maps to the library's root package (as folder org/example/happylib on the Java classpath). The application version is a configurable value.
Since
since5.2

Conclusion

That's it! Autoloading plus the virtual folders for components and for assets takes care of all the issues related to components. Just build your JARs, setup the JAR Manifest, and drop them into your applications.