Tapestry includes sophisticated JavaScript and Ajax support, based on the Prototype and Scriptaculous libraries. These libraries are all packaged with Tapestry itself ... no extra download required.
The goal for Tapestry is to have many basic and useful components available within the application itself, and make it easy for other JavaScript widgets to be encapsulated as Tapestry components.
Ajax support takes the form of new components and, occasionally, component mixins.
Tapestry currently uses Prototype 1.6.0.1. Version 1.6.0.2 appears to have some issues supporting Internet Explorer. Tapestry's version is slightly patched for TAPESTRY-2108.
Scriptaculous normally has a special script loading option. Loading just the Scriptaculous main library, scriptaculous.js, will also load all the other scripts in the library. Normally, you can fine-tune this using "load" query parameter.
This doesn't fit well with the Tapestry; as discussed below, Tapestry has the capability to allow individual components to control which JavaScript libraries are loaded with the page. Further, the exact set of scripts needed is determined over the course of rendering the page, and depends on the needs of the specific components that have rendered.
The main Scriptaculous library, scriptaculous.js, is modified to turn off the autoloading behavior. Tapestry will automatically link in prototype.js, scriptaculous.js, effects.js and the Tapestry library, tapestry.js. You can add additional libraries as needed.
The general strategy in Tapestry is that any significant amount of JavaScript should be packaged up as a static JavaScript library, a .js file that can be downloaded to the client.
Page specific JavaScript should be in the form of minimal statements to initialize objects, referencing the JavaScript libraries.
Most of this is accomplished via the RenderSupport object.
RenderSupport include a number of methods that will be used by components, or event by services that are called from components.
void addScriptLink(Asset... scriptAssets);
This method 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 <head> element).
Adding the same asset multiple times does not create duplicate links. The subsequent ones are simply ignored. In this way, each component can add the assets it needs, without worrying about conflicts with other components.
Note that the Prototype, Scriptaculous main and effects libraries, and the base Tapestry library (which largely consists of support for form input validation) are included automatically.
If you are need access to other Scriptaculous libraries, you can provide them as follows:
@Inject @Path("${tapestry.scriptaculous}/dragdrop.js")
private Asset dragDropLibrary;
@Environmental
private RenderSupport renderSupport;
void setupRender()
{
renderSupport.addScriptLink(dragDropLibrary);
}
The Asset is injected, using the ${tapestry.scriptaculous} symbol to reference the location of the Scriptaculous library.
The RenderSupport is accessed as an Environmental service.
The setupRender() method (the name is specifically linked to a render phase) is the correct place to inform the RenderSupport service that the library is needed.
void addScript(String format, Object... arguments);
This method adds some initialization JavaScript to the page. By initialization we mean that it goes at the bottom of the document, and will only be executed when the document has finished loading on the client (i.e., from the window.onload event handler).
When calling the method, the format string can include standard substitutions (such as '%s') for arguments. This saves you the trouble of calling String.format() yourself. In any case, the formatting JavaScript is added to the script block.
RenderSupport is an environmental object, so you will normally inject it via the Environmental annotation:
@Environmental private RenderSupport renderSupport;
Environmental only works inside components and occasionally a service may want to inject RenderSupport. Fortunately, a proxy of RenderSupport has been set up. The upshot of which is, you may also:
@Inject private RenderSupport renderSupport;
... or, in a service implementation constructor:
public MyServiceImpl(RenderSupport support)
{
. . .
}Inside a component, you should use Environmental, to highlight the fact that RenderSupport (like most environmental objects) is only available during rendering, not during action requests.
The IncludeJavaScriptLibrary annotation is the easy way to include one or more JavaScript libraries.
The previous example could be re-written as:
@IncludeJavaScriptLibrary("${tapestry.scriptaculous}/dragdrop.js")
public class MyComponent
{
. . .
}This saves you the effort of injecting the asset and making the call to RenderSupport. Chances are you will still inject RenderSupport so as to add some initialization JavaScript.
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 an existing text field:
<t:textfield t:id="accountName" t:mixins="autocomplete" size="100"/>
The mixin can be configured in a number of ways, see the component reference.
When the user types into the field, the client-side JavaScript will send a request to the server to get completions.
You must write an event handler to provide these completions. The name of the event is "providecompletions". The context is the partial input value, and the return value will be converted into the selections for the user.
For example:
List<String> onProvideCompletionsFromAccountName(String partial)
{
List<Account> matches = accountDAO.findByPartialAccountName(partial);
List<String> result = new ArrayList<String>():
for (Account a : matches)
{
result.add(a.getName());
}
return result;
}This presumes that findByPartialAccountName() will sort the values, otherwise you will probably want to sort them. Certainly the Autocomplete mixin does not do any sorting.
You can return an object array, a list, even a single object. You may return objects instead of strings ... and toString() will be used to convert them into client-side strings.
Tapestry's default stylesheet includes entries for controlling the look of the floating popup of selections.
You may override DIV.t-autocomplete-menu UL to change the main look and feel, DIV.t-autocomplete-menu LI for a normal item in the popup list, and DIV.t-autocomplete-menu LI.selected for the element under the cursor (or selecting using the arrow keys).
Initial support for Zones is now in place. Zones are Tapestry's approach to performing partial updates to the client side. A Zone component renders as a <div> element with the "t-zone" CSS class. It also adds some JavaScript to the page to "wire up" a Tapestry.Zone object to control updating the <div> element.
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.
In many situations, a Zone is a kind of "wrapper" or "container" for dynamic content, that provides a look and feel ... a bit of wrapping markup to create a border. In that situtation, the Zone <div> may contain an update <div>.
An update <div> is a <div> with the CSS class "t-zone-update", inside the Zone's <div>.
When an update occurs, the update <div>'s content will be changed, rather than the entire Zone <div>.
The show and update functions apply to the Zone <div>.
In a traditional request, the return value of an event handler method is used to determine which page will render a complete response, and a redirect is sent to the client to render the new page (as a new request).
With a Zone update, the return value is used to render a partial response within the same request.
This return value should be an injected component or block. The value will be rendered, and that markup will be used on the client side to update the Zone's <div>.
Users who do not have JavaScript enabled may click ActionLinks that are configured to update a Zone.
When that occurs, the request will still be sent to the server, but will be handled as a traditional request.
To support graceful degradation, you should detect that case and return a traditional response: a page, page name or page class.
This is accomplished by injecting the Request object, and invoking the isXHR() method. This value will be true for Ajax requests, and false for traditional request.
A Zone may be initially visible or invisible. When a Zone is updated, it is made visible if not currently so. This is accomplished via a function on the Tapestry.ElementEffect client-side object. By default, the show() function is used for this purpose. The Zone's show parameter is the name of a Tapestry.ElementEffect function.
If a Zone is already visible, then a different function is used to highlight the change. Here it is the Zone's update parameter, and a default highlight() function, which perfroms a yellow fade to highlight that the content of the Zone has changed.
Unlike many other situations, Tapestry relies on you to specify useful and unique ids to Zone components, then reference those ids inside ActionLink components. Using Zone components inside any kind of loop may cause additional problems, as Tapestry will uniqueify the client id you specify (appending an index number).
The show and update function names are converted to lower case; all the methods of Tapestry.ElementEffect should have all lower-case names. Because client-side JavaScript is so fluid (new methods may be added to existing objects), Tapestry makes no attempt to validate the function names ... however, if the names are not valid, then the default show and highlight methods will be used.
Currently, the partial page content that is rendered may not use an Environmental services. This is expected to change soon.
A study of the Autocomplete mixin's code should be very helpful: it shows how to ask the ComponentResources object to create a link.
The key part is the way Tapestry invokes a component event handler method on the component.
For an Ajax request, the return value from an event handler method is processed differently than for a traditional action request. In an normal request, the return value is the normally name of a page (to redirect to), or the Class of a page to redirect to, or an instance of a page to redirect to.
For an Ajax request, a redirect is not sent: any response is rendered as part of the same request and sent back immediately.
The possible return values are: