Saturday, January 31, 2009

Task 5 - Customize Eclipse for Tapestry 5 code editing

When coding, sometimes we (at least me) forget to use all the help from an IDE we are coding in. One of techniques is context help.

I am sure you are all used to typing

so you quicker create a for loop.

To speed up developing web applications with Tapestry 5, I have created 4 templates just to show the principle. I will most likely create more as I learn the framework and find the patterns that are recurring more often.

Get the templates at http://sites.google.com/site/bbwebcraft/ and import them into the Eclipse.


After importing you should see 4 new templates in the list:


Let’s try them out one by one.

Open any Tapestry 5 project you have in your workspace – maybe you have the one we created in task 1.

Open some page class like Index.java, put the cursor in the class body, type t5 and press Ctrl+Space.

You can see the 4 templates we have imported. Just select the first one – ActionLink.

A lot of code gets inserted which is good for a complete Tapestry 5 newbie and not so good for others.


package org.driving.school.pages;

import java.util.Date;

import org.apache.tapestry5.PersistenceConstants;
import org.apache.tapestry5.annotations.Persist;

/**
* Start page of application school.
*/
public class Index {
public Date getCurrentTime() {
return new Date();
}

public String getMyDemoMessage() {
return "My Demo Message Changed";
}


/*
Component that triggers an action on the server with a subsequent full page refresh.

Put this into the Index.tml

<a t:type="actionlink" t:id="myAction" t:context="literal:FooBar" href="#">My ActionLink</a>
<div>${myMessageAfterPressingTheActionLink}</div>

See http://tapestry.apache.org/tapestry5/tapestry-core/ref/org/apache/tapestry5/corelib/components/ActionLink.html
for a full component description.
*/

// Only keep the message until it has been displayed once. You must persist it
// because ActionLink performs redirect after post. If you click refresh browser
// button the message is lost, because it is persisted just for one request.
@Persist(PersistenceConstants.FLASH)
private String myMessageAfterPressingTheActionLink;

void onActionFromMyAction(String contextParameter) {
myMessageAfterPressingTheActionLink = "You pressed an action link which had a literal string "
+ contextParameter + " in its context.";
}

public String getMyMessageAfterPressingTheActionLink() {
return myMessageAfterPressingTheActionLink;
}
}


The inserted code is more like a cheat sheet as it tells you what to insert in the appropriate template file to have a working example of the ActionLink component. It also gives a tip on why the field must be persisted.

All the templates also import the necessary classes from the framework, so you don’t even have to bother pressing Ctrl+Shift+O (Organize Imports).

Now, delete the code which has been inserted and try the second template (t5-addScriptLink).


/*
The method addScriptLink adds a link to a script file, a JavaScript
library. A component can inject such a script and pass one or more of
assets to this method. Tapestry will ensure that the necessary <link>
elements are added to the top of the document (just inside the
element). The same can be achieved with the annotation

@IncludeJavaScriptLibrary("${tapestry.scriptaculous}/dragdrop.js")
on a page (Index) class.
*/
@Inject
@Path("${tapestry.scriptaculous}/dragdrop.js")
private Asset dragDropLibrary;

@Environmental
private RenderSupport renderSupport;

void setupRender() {
renderSupport.addScriptLink(dragDropLibrary);
}


One can now quickly modify the name of the JavaScript library that is to be added on this page. It also includes an alternative way of doing the same thing.

The third template is a real beast. It includes the code and instructions to have a working autocomplete example in minutes. Show this to your boss to demonstrate how quickly you can have this done in Tapestry 5. :-)


/*
The Autocomplete mixin exists to allow a text field to query the server for completions
for a partially entered phrase. It is often used in situations where the field exists to
select a single value from a large set, too large to succesfully download to the client
as a drop down list; for example, when the number of values to select from is numbered
in the thousands.

Autocomplete can be added to Index.tml with:

<form t:type="form">
<input t:id="cityName" t:type="TextField" t:mixins="autocomplete"/>
</form>

Create a service interface:

public interface CityNames {
List<String> getAll();
}

And the implementation:

public class CityNamesImpl implements CityNames {
private List<String> cityNames = new ArrayList<String>();

public CityNamesImpl() {
// top 7 cities by population
cityNames.add("Mumbai");
cityNames.add("Shanghai");
cityNames.add("Karachi");
cityNames.add("Istanbul");
cityNames.add("Delhi");
cityNames.add("Sao Paulo");
cityNames.add("Moscow");
}

public List<String> getAll() {
Collections.sort(cityNames);
return Collections.unmodifiableList(cityNames);
}
}

Then in the AppModule bind the service:

binder.bind(CityNames.class, CityNamesImpl.class);

Change the look of the autocomplete menu by overriding CSS classes
DIV.t-autocomplete-menu UL
DIV.t-autocomplete-menu LI
DIV.t-autocomplete-menu LI.selected
*/
@Inject
private CityNames cityNames;

@Property
private String cityName;

List<String> onProvideCompletionsFromCityName(String partial) {
List<String> matches = new ArrayList<String>();

for (String cityName : cityNames.getAll()) {
if (cityName.toLowerCase().startsWith(partial.toLowerCase())) {
matches.add(cityName);
}
}
return matches;
}


And the last one (t5-zone) which demonstrates the usage of AJAX in Tapestry 5.


/*
A Zone can be updated via an ActionLink component. ActionLink supports a zone parameter,
which is the id of the Zone's <div>. Clicking such a link will invoke an event handler
method on the server as normal ... except that the return value of the event handler method
is used to send a partial page response to the client, and the content of that response
is used to update the Zone's <div> in place.

Put this into the Index.tml,

<t:block t:id="myBlockToBeUpdated">
The current time is: ${time}
</t:block>
<t:zone t:id="myZone">
<t:delegate to="myBlockToBeUpdated"/>
</t:zone><br/>
<a t:type="actionlink" t:id="refreshZone" href="#" t:zone="myZone">Refresh time with AJAX request.</a>
*/
@Inject
@Property
private Block myBlockToBeUpdated;

Block onActionFromRefreshZone() {
// Return the zone we want rendered.
return myBlockToBeUpdated;
}

public Date getTime() {
return new Date();
}


Again, if you follow the comments in the template, you will have a working example in under a minute.

And this is all about – getting beginners on the track fast! Including me.

1 comment:

  1. Hi Borut,

    I hope you are doing well. I have been reading your blog and noticed you're an expert in the area and thought you may be interested in this.

    Please download here: http://refcardz.dzone.com/, and if you think this is useful we'd be honored if you could post a mention.

    Thanks,
    Wei Ling Chen
    DZone, Inc.

    ReplyDelete