Tapestry and testng.xml

You can use a testng.xml file to help control the tests and browser used by Selenium in Tapestry.  However, you have to tell Maven to look for the testng.xml file or it will ignore it.  You do this by putting the following in your pom.xml -> build -> plugins:

			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-surefire-plugin</artifactId>
				<version>2.6</version>
				<configuration>
					<suiteXmlFiles>
						<suiteXmlFile>src/test/conf/testng.xml</suiteXmlFile>
					</suiteXmlFiles>
				</configuration>
			</plugin>

Tapestry 5 – 10 Minute Demo of Apache Tapestry

This is a demonstration of about 10 minutes of programming in Tapestry 5 creating a sample application. The app is pretty basic and just lets you add URLs to a list and then vote on them-similar to the idea behind Digg or Reddit. I didn’t really explain things in great detail so it is more of a demonstration of some of the things you can do than it is a step by step tutorial.
Continue reading “Tapestry 5 – 10 Minute Demo of Apache Tapestry”

Tapestry Facebook Share Component

I am working on a project where we need to be able to do a Facebook share on a particular page for an event from multiple places within a web application. Putting this functionality into a Tapestry component makes it easy to call it from where ever it is needed and the code makes for a nice concise example of how components work in Tapestry.

In Tapestry components consist of two parts. The first part is the .tml file and the second is the .java file. In general the .tml file is concerned with how things render out in HTML and the .java file is concerned with the programatic side of things and retrieving the data.

The end result is going to be a tag that we can use in other .tml files that will look like this:

<t:facebookshare event="myEvent"/>

First lets look at the .tml file. This file is saved under:
/src/main/resources/com/example/myapp/components/FacebookShare.tml

<t:container xmlns:t="http://tapestry.apache.org/schema/tapestry_5_1_0.xsd" >
<a href="#" onclick="window.open('http://www.facebook.com/sharer.php?u=${facebookShareURL}&amp;display=popup','fbshare','menubar=0,location=0,resizable=1,width=700,height=350')">
	Please share this event on Facebook
</a><br/>
</t:container>

The ${facebookShareURL} is what will call getFacebookShareUrl() on the java class in order to fill out the component with the data it needs. In this case it will be an encoded URL.

The .java component file is saved in:
/src/main/java/com/example/myapp/components/FacebookShare.java

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;

import javax.persistence.Transient;

import net.xeric.register.entities.Event;

import org.apache.tapestry5.Link;
import org.apache.tapestry5.annotations.Parameter;
import org.apache.tapestry5.annotations.Property;
import org.apache.tapestry5.ioc.annotations.Inject;
import org.apache.tapestry5.services.PageRenderLinkSource;

public abstract class FacebookCommon {
    
    @Inject
    private PageRenderLinkSource linkSource;
    
    @Parameter(required=true)
    @Property
    private Event event;
    
    public String getFacebookShareURL() {
        Link link = linkSource.createPageRenderLinkWithContext("EventInfo", event);
        String linkURL = "";
        try {
            linkURL =  URLEncoder.encode(link.toAbsoluteURI(),"UTF-8");
        } catch (UnsupportedEncodingException e) {          
            e.printStackTrace();
        }
        return linkURL;
    }
}

I have a page called EventInfo that shows all the information about a particular event. So this component takes an event as a parameter and then constructs the link to share on the EventInfo page.

This is how we tell Tapestry that we have a required parameter that needs to be bound to an event:

    @Parameter(required=true)
    @Property
    private Event event;

That means that when we use the component like this:

<t:facebookshare event="myEvent"/>

Tapestry is going to expect the parameter event to be bound to an event object.

Line 42 has the code that constructs our link back to the EventInfo page using the context of the event that we’ve passed in as a parameter to the component.

        Link link = linkSource.createPageRenderLinkWithContext("EventInfo", event);
        String linkURL = "";
        try {
            linkURL =  URLEncoder.encode(link.toAbsoluteURI(),"UTF-8");

The rest of the getFacebookShareURL method simply encodes the URL as a string and returns it. This is the value that replaces the ${facebookShareUrl} in the template when the component is rendered.

Get the URL of a Page in Tapestry 5

Sometimes your program will need to get the full URL of a page to send in an email, share on Facebook or run through a URL shortener to send to Twitter. You can do this by injecting the HTTPRequest and the building a string representation of the URL manually, but this is a lot more work that it needs to be. Here is a simple way to do it using Tapestry’s PageRenderLinkSource and a Link object.

@Inject
PageRenderLinkSource linkSource

/**
 * Return a string with the full URL of MyPage with a 
 * context of 5.
 */
public String getPageURL() {
	Link link = linkSource.createPageRenderLinkWithContext("MyPage", 5);
	return link.toAbsoluteURI();
}

I’ve hard coded the value 5 as the context–you’d normally set that based on whatever you are wanting to share. This method will return a link to MyPage with a context of 5, so it will probably look something like: http://www.myserver.com/MyPage/5. One of the nice things about this solution is that it will automatically handle things if your application needs to use a URL like: http://www.myserver.com/myapp/MyPage/5

Adding Context to Links in Tapestry

Introduction

Tapestry looks at the URL and assumes that anything after the page name is the context that is being passed to the page. So a URL like:

http://www.site.com/MyPage/5

Is going to expect that the number 5 is the context needed on MyPage.  You can have multiple contexts passed to a page like this:

http://www.site.com/MyPage/5/foo

This will pass two contexts to the page.  Depending on what the page is expecting to receive in the context, these values may be uses as they are or they might be coerced into something else.  For example, the five might represent the ID of some object that will be retrieved from the Hibernate.

In the case of a single context value being passed, MyPage can get the value into a page property by doing something like this:

@PageActivationContext
@Property
private int myNumber;

The PageActivationContext annotation tells Tapestry to put the value on the URL that comes after the page into that variable. With Hibernate involved you can do something like this:

@PageActivationContext
@Property
private Person aPerson;

Tapestry will tell Hibernate to get the person object with an id of 5 and assign it to the variable aPerson.

If the page has multiple context values being passed, they are handled through the onActivate method:

public void onActivate(int myNumber, String myString) {
//set appropriate page properties
}

PageLink

Thats a little background. The main thing I wanted to look at in this post is how to get links that pass in these context values in the first place.

This snippet shows a pagelink that will give us something of the form:
http://www.site.com/MyPage/5
Assuming that the person.id is 5.

<t:pagelink page="MyPage" context="person.id">${person.name}</t:pagelink>

If we need to pass multiple context values it can be done like this.

<t:pagelink page="MyPage" context="[person.id,order.id]">view order</t:pagelink>

If person.id is 5 and order.id is 22 this link will produce:
http://www.site.com/MyPage/5/22

Returning Page and Context from a Method

Tapestry lets us return values in Java that specify the next page to load. This is commonly seen in the onSuccess method which can look like this:


    public Object onSuccess() {
       //Do what needs to be done
       return "MyPage";
    }

At first it looks like we can simply append the values like:


    public Object onSuccess() {
       //Do what needs to be done
       return "MyPage/" + person.id + "/" + order.id;
    }

But this does not work because Tapestry can’t find the page “MyPage/5/22”. It only knows about “MyPage”. To add a context you will need to inject the pageRenderLinkSource and use it to build the context.

    @Inject
    private PageRenderLinkSource pageRenderLS;

    public Object onSuccess() {
       //Do what needs to be done
       return pageRenderLS.createPageRenderLinkWithContext("MyPage", person.id);
    }

You can add additional context values to the parameters as well.

    @Inject
    private PageRenderLinkSource pageRenderLS;

    public Object onSuccess() {
       //Do what needs to be done
       return pageRenderLS.createPageRenderLinkWithContext("MyPage", person.id, order.id);
    }