Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: restore
Div
stylefloat:right
titleRelated Articles
classaui-label
Content by Label
showLabelsfalse
showSpacefalse
titleRelated Articles
cqllabel in ("ajax","javascript") and space = currentSpace()

Tapestry provides easy-to-use support for Ajax, the technique of using JavaScript to dynamically update parts of a web page with content from the server without redrawing the whole page. With Tapestry, you can do simple Ajax updates without having to write any JavaScript code at all.

Ajax support is included in many built-in components and component mixins via the async parameter (in Tapestry 5.4+) and the zone parameter (for earlier versions). Here we use an EventLink component to trigger an Ajax update of another area of the page:

Code Block
languagexml
titlePage or component template (partial)
<t:eventlink event="updateTime" async="true">update</t:eventlink>
...
<t:zone t:id="timeArea" id="timeArea">
    The current time is ${currentTime}
</t:zone>

The corresponding Java code might look like this:

Code Block
languagejava
titlePage or component class (partial)
@Inject
private AjaxResponseRenderer ajaxResponseRenderer;

@InjectComponent
private Zone timeArea;

@Property
private Date currentTime;
 ...
/** respond to the "updateTime" event */
void onUpdateTime()
{
    currentTime = new Date();
    ajaxResponseRenderer.addRender(timeArea);
} 

That onUpdateTime method is just an ordinary Tapestry event handler, except that it uses an injected AjaxResponseRenderer to tell Tapestry what zone to update when the link is clicked.

Since Tapestry 5.4.2, you can also easily invoke server-side event handlers using the @PublishEvents annotation and the t5/core/ajax JavaScript function, as explained in the "Invoking server-side event handler methods from JavaScript" section below.

Zones

Zones are Tapestry's approach to performing partial page updates. A Zone component renders as an HTML element, typically a <div>, and serves as a marker for where dynamically-updated content should be replaced. A zone is recognizable in the DOM because it will have the attribute data-container-type=zone. The client-side support for Zones is keyed off of this attribute and value.

Starting in Tapestry 5.4 you can use any HTML element in your template as a zone marker, by passing its client-side id to the two-argument version of the addRender method.

A Zone updated can be triggered by an EventLink, ActionLink or Select component, or by a Form. All of these components support the async and/or zone parameters. Clicking such a link will invoke an event handler method on the server as normal ... except that a partial page response is sent to the client, and the content of that response is used to update the Zone's <div> in place.

Wiki Markup
{float:right|background=#eee|padding=0 1em}
    *JumpStart Demo:*
    [AJAX ActionLink|http://jumpstart.doublenegative.com.au/jumpstart/examples/ajax/actionlink]
{float}

Event Handler Return Types

...

In contrast, with a Zone update, the return value may used to render a partial response within the same request.

Note

Starting in Tapestry 5.3, Ajax event handlers typically have a void return type and use AjaxResponseRenderer to indicate which zone to update. The AjaxResponseRender approach means that the zone parameter's value (oridinarily indicating which zone to update) is no longer needed. Tapestry 5.4 introduced the async="true" parameter to avoid having to redundantly indicate which zone to update.

If you only have one zone to update and don't want to use AjaxResponseRenderer, you can instead return a value from your event handler method. The simplest case is just to return the zone's own body:

Code Block
languagejava
@Inject
private Request request;

@InjectComponent
private Zone myZone;
...
Object onActionFromSomeLink()
{
   return myZone.getBody(); // AJAX request, return zone's own body
}

The possible return values are:

  • An injected Block or Component to render as the response. The response will be a JSON hash, with a "content" key whose value is the rendered markup. This is the basis for updates with the Zone component.
  • The zone's own body (using Zone's getBody() method)
  • null (to redraw the current page)
  • A JSONObject or JSONArray, which will be sent as the response.
  • A StreamResponse, which will be sent as the response.
  • A Link, which will send a redirect to the client.
  • A page name (as a String), or a page class, or a page instance, which will send a redirect to the indicated page.

See Page Navigation Ajax and Zones for full descriptions of the above.

...

However, to support graceful degradation when your event handler method has a non-void return type, you should detect non-Ajax requests and return a traditional response, typically null to redraw the whole page. 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.

Code Block
languagejava
@Inject
private Request request;

@InjectComponent
private Zone myZone;
...
Object onActionFromSomeLink()
{
    // return either the zone body (ajax) or whole page (non-ajax)
    return request.isXHR() ? myZone.getBody() : null;
} 

Multiple Zone Updates

Wiki Markup
{float:right|background=#eee|padding=0 1em}
    *JumpStart Demo:*
    [AJAX Multiple Zone Update|http://jumpstart.doublenegative.com.au/jumpstart/examples/ajax/multiplezoneupdate]
{float}
An event handler often needs to update multiple zones on the client side. To accomplish this, use an AjaxResponseRenderer, indicating the zones to update. You must know the client-side id for each zone to update (the best way for this is to lock down the zone's id using the id parameter of the Zone component).

...

The renderer for each zone can be the zone itself, a block, a component, a Renderable or a RenderCommand ... or an object, such as String, that can be coerced to either of these.

Code Block
languagejava
titleFor Tapestry 5.3 and later
@InjectComponent
private Zone userInput;

@InjectComponent
private Zone helpPanel;

@Inject
private AjaxResponseRenderer ajaxResponseRenderer;

void onActionFromRegister()
{
    ajaxResponseRenderer.addRender("userInput",
        userInput).addRender("helpPanel", helpPanel);
}

This example assumes that there are two zones, "userInput" and "helpPanel", somewhere in the rendered page, waiting to receive the updated content.

 

Note

In this example, the Zone receives the update but does not provide any content. That's OK, the other client-side elements (userInput and helpPanel) will be updated, and the zone's content left unchanged.

This demonstrates why it is necessary for the developer to specify a particular client-side id for Zone components; if they were dynamically allocated ids, as is typical in most other elements, it would be impossible for this code to know what client-side id was used for the Zone.

...

Remember that the component id (t:id) is used to inject the Zone component into the containing page or component. The client-side id (id) is used on the client side to orchestrate requests and updates. You will often seen the following construct:

Code Block
languagexml
<t:zone t:id="myZone" id="myzone"> ... </t:zone>

The Containing Zone (zone="^")

...

To have Tapestry update a zone without the usual yellow highlight effect, just specify "show" for the update parameter:

Code Block
languagexml
<t:zone t:id="myZone" t:update="show">

You may also define and use your own JavaScript effect function (with lower-case names), like this:

Code Block
languagejs
Tapestry.ElementEffect.myeffectname = function(element){ YourJavascriptCodeGoesHere; };

Zone Limitations

Unlike many other situations, Tapestry relies on you to specify useful and unique ids to Zone components, then reference those ids inside EventLink (or ActionLink, or Form) 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).

If you create a component that contains a zone, and you use that component in a loop, you'll likely need to set the client-side id like this:

 

Code Block
languagexml
<t:zone t:id="myzone" id="prop:componentResources.id">

 

See this JumpStart Example for details.

...

For examples of extending a Form with a Zone and updating multiple zones at once, see the Ajax Components FAQand Zones.

There are also a number of Ajax-related examples at the  Tapestry JumpStart site.

Anchor
autocomplete
autocomplete

Autocomplete Mixin

Wiki Markup
{float:right|background=#eee|padding=0 1em}
    *JumpStart Demo:*
    [Autocomplete Mixin|http://jumpstart.doublenegative.com.au/jumpstart/examples/ajax/autocompletemixin]
{float}
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 successfully 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:

Code Block
languagejava
  <t:textfield t:id="accountName" t:mixins="autocomplete" size="100"/>

The mixin can be configured in a number of ways, see the component reference.

...

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:

Code Block
languagejava
  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. The Autocomplete mixin does not do any sorting.

...

  1. The element itself
  2. The element's previous siblings, closest first (bottom-up)
  3. The element's parents
  4. The page's <body> element

 

Here's one example:

Code Block
languagejava
public class PublishEventDemoComponent
{
    @OnEvent("answer")
    @PublishEvent
    JSONObject answer() {
        return new JSONObject("origin", "componentAnswer");
    }
    
    @PublishEvent
    JSONObject onAction()
    {
        return new JSONObject("origin", "componentAction");
    }   
}

Notice that answer() and onAction() are ordinary event handlers, with nothing specific besides the @PublishEvent annotation.

Code Block
languagexml
<div id="component" xmlns:t="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd">
	<p id="componentParagraph">I'm a component</p>
    <p id="result">(no result yet)</p>
</div>

The template also has nothing special. When rendered, the component's events information is placed in the outer <div> (id="component"). 

We want to update the text of <p id="result"> with the value of the origin property of the returned JSON object when that element itself is clicked, so here's our JavaScript code, supposing we want to trigger the answer event:

Code Block
languagejs
linenumberstrue
require(["t5/core/ajax", "jquery"], function (ajax, $) {
    // Creating a callback to be invoked with <p id="result"> is clicked.
	$('#result').click(function() {
		ajax('answer', { 
			element: $('#result'), // This doesn't need to be the same element as the one two lines above
            // Callback called when the request is finished. 
            // response.json is the object returned by the event handler method
			success: function(response) {
				$('#result').text(response.json.origin);
			}
		});
	});
});

If you're trying to invoke a page class event handler, you can change line 5 above to element: null. You do need to explicitly set the element property, otherwise the ajax function will treat the first parameter, url, as an URL and not as an event name.