blob: 8d48d7cd11608b2b427f8285859f39aa00ecb8f4 [file] [log] [blame]
---
Tapestry Ajax Support
---
Tapestry Ajax Support
Tapestry includes sophisticated JavaScript and Ajax support, based on the
{{{http://www.prototypejs.org/}Prototype}} and
{{{http://script.aculo.us/}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,
{{{mixins.html}component mixins}}.
Changes to Prototype
Tapestry currently uses
{{{http://www.thebungeebook.net/wp-content/uploads/2008/01/prototype-1601.js}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 {{{https://issues.apache.org/jira/browse/TAPESTRY-2108}TAPESTRY-2108}}.
Changes to Scriptaculous
Scriptaculous normally has a special {{{http://wiki.script.aculo.us/scriptaculous/show/Usage}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.
Basic JavaScript
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
{{{../../apidocs/org/apache/tapestry5/RenderSupport.html}RenderSupport}} object.
RenderSupport include a number of methods that will be used by components, or event by
services that are called from components.
* addScriptLink()
<<<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
{{{rendering.html}render phase}}) is the correct place to inform the RenderSupport service that
the library is needed.
* addScript()
<<<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.
* Injecting RenderSupport
RenderSupport is an <environmental> object, so you will normally inject it via the
{{{../../apidocs/org/apache/tapestry5/annotations/Environmental.html}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.
IncludeJavaScriptLibrary Annotation
The
{{{../../apidocs/org/apache/tapestry5/annotations/IncludeJavaScriptLibrary.html}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.
Ajax Components and Mixins
* Autocomplete Mixin
The
{{{../../apidocs/org/apache/tapestry5/corelib/mixins/Autocomplete.html}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-parameters.html}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).
* Zone
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.
** Zone div vs. update div
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\>.
** Event Handler Return Types
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\>.
** Graceful Degradation
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
{{{../../apidocs/org/apache/tapestry5/services/Request.html}Request}} object, and invoking the isXHR() method.
This value will be true for Ajax requests, and false for traditional request.
** Zone Functions
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.
** Zone Limitations
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.
** Coming Soon
* zone parameter for Form components
* Extending a Form with a Zone
* Additional Tapestry.ElementEffect functions, plus documentation
* Real examples ...
[]
Your own Ajax Components
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:
* A 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.
* A {{{../../apidocs/org/apache/tapestry5/json/JSONObject.html}JSONObject}}, which will be sent as the response.
* A {{{../../apidocs/org/apache/tapestry5/StreamResponse.html}StreamResponse}}, which will be sent as the response.
[]