Ajax & _javascript_Page edited by Bob Harner
Comment:
Added "Alternatives to Prototype" section
Changes (3)
Full ContentTapestry includes sophisticated _javascript_ and Ajax support, based on the Prototype and Scriptaculous libraries. These libraries are all packaged with Tapestry itself ... no extra download is required. Tapestry has many basic and useful components available within the application itself, and it's easy to create new ones. Ajax support takes the form of new components and, occasionally, component mixins. Tapestry will automatically link in prototype.js, scriptaculous.js, effects.js and the Tapestry library, tapestry.js. You can add additional libraries as needed. Prototype and Scriptaculous Versions
Changes to ScriptaculousTapestry uses a modified version of the main Scriptaculous library, scriptaculous.js, with the library's default autoloading behavior turned off. This lets Tapestry and Tapestry components control which Scriptaculus scripts are loaded, rather than having all of them loaded unnecessarily. 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 RenderSupport object. RenderSupport includes 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 standard Tapestry library (which largely consists of support for form input validation) are included automatically. If you 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. Even though the dragdrop.js library is stored inside a JAR file, Tapestry ensures that it can be accessed from the client web browser. A Tapestry URL within the virtual folder "/assets" is created; the file will be given a version number (the application version number if not specified more specifically) and will be sent to the browser with a far-future expires header (to encourage the browser to cache the file aggresively). 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 RenderSupportRenderSupport 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. IncludeJavaScriptLibrary AnnotationThe @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_. Combining ScriptsIn production mode, Tapestry automatically combines _javascript_ libraries. A single request (for a virtual asset) will retrieve the combined content of all referenced _javascript_ library files. This is a very useful feature, as it reduces the number of requests needed to present a page to the user. As elsewhere, if the client browser supports gzip compression, the combined _javascript_ will be compressed. Ajax Components and MixinsAutocomplete MixinThe 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: <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). ZonesZones 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 can actually render as any element you chose, but for discussion's sake, we'll assume the <div> default). It also adds some _javascript_ to the page to "wire up" a Tapestry.ZoneManager object to control updating the <div> element. A Zone can be updated via an ActionLink or EventLink component, or by a Form. All of these components support 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 divIn many situations, a Zone is a kind of "wrapper" or "container" for dynamic content; one that provides a look and feel ... a bit of wrapping markup to create a border. In that situation, the Zone <div> may contain an update <div>. An update <div> is specifically a <div> element 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 TypesIn 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 is typically 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>. An event handler may return a Link and the client will be redirected to that link. Returning a page name (as a String), or a page class, or a page instance will also send a redirect to the indicated page. Multiple Zone UpdatesAn event handler may cause multiple zones to be updated on the client side. To accomplish this, return a MultiZoneUpdate object configured with 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 a block or component, or a Renderable or RenderCommand ... or an object, such as String, that can be coerced to either of these. Typically, you will inject a Block or Component and return that: @Inject private Form registrationForm; @Inject Block registrationHelp; Object onActionFromRegister() { return new MultiZoneUpdate("userInput", registrationForm).add("helpPanel", registrationHelp); } This implies that there are two zones, "userInput" and "helpPanel", somewhere in the rendered page, waiting to receive the updated content. Graceful DegradationUsers 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. Zone FunctionsA 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 performs a yellow fade to highlight that the content of the Zone has changed. Zone LimitationsUnlike 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. Coming Soon
Your own Ajax ComponentsA 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:
Client-side LoggingTapestry uses a modified version of the Blackbird _javascript_ console. The Tapestry object includes three functions: debug, warn and error. Each of these functions take a message and an optional pattern; if the pattern is provided, the message is interpolated on the pattern. The final message is displayed in the Blackbird console, which will make itself visible automatically. In production mode, debug messages will be filtered out (they will not be visible until the user presses F2 to display the console, and then clicks the grayed out icon for debug messages). In development mode, debug messages are not filtered out. Example usage: Tapestry.debug("Field id is #{id}, value is #{value}", field); Tapestry.error("Server is not available."); Handling Slow Page LoadsIf your page loads slowly (typically, because of scripts loaded from external sites), you may see a race condition where the user can click on a link before an event handler for that link has been wired up. The client-side function Tapestry.waitForPage() can be used in an element's onclick handler to force a wait for the page to fully load. In this race condition, the screen will dim and a message will appear advising the user to wait a moment; once the page is fully loaded, this modal dialog will be removed. The correct usage is:
<a href="" class="code-quote">"..." _onclick_="_javascript_:Tapestry.waitForPage(event);"> ... </a>
The constant MarkupConstants.WAIT_FOR_PAGE contains this snippet. The Standard Tapestry LibraryTapestry's client-side support, the standard Tapestry library, is the file tapestry.js, which has dependencies on Prototype and on Scriptaculous Effects. tapestry.js, along with its dependencies, are automatically added to the page when your code adds any other _javascript_ or _javascript_ library. Tapestry NamespaceTapestry defines a number of object and classes inside the Tapestry namespace. It also adds a handful of methods to the Form class, and to Form elements. These are mostly related to input validation and determining element visibility. The Tapestry ObjectThe standard library adds a new function, $T(). This function is used much like Prototype's $(), except that instead of returning a DOM object, it returns a hash (an initially empty _javascript_ object) that is associated with the DOM object. This hash is known as the Tapestry object. You may pass in an object id (as a string) or an object reference. The Tapestry Object is created on first invocation. Note: you'll see it as a property name _tapestry on the DOM object (which may be useful when debugging). When Tapestry adds information to a DOM object, it does so in the Tapestry object. This helps avoid name conflicts, and groups all Tapestry-added properties into one place which is much easier to debug. For example, you might store a value for an element in one place: $T(myid).fadeDuration = .5; Then use it somewhere else:
new Effect.Fade($(myId), { duration: $T(myid).fadeDuration });
Change Notification Preferences
View Online
|
View Changes
|
- [CONF] Apache Tapestry > Ajax & JavaScript confluence
- [CONF] Apache Tapestry > Ajax & JavaScript confluence
- [CONF] Apache Tapestry > Ajax & JavaScript confluence
- [CONF] Apache Tapestry > Ajax & JavaScript confluence
- [CONF] Apache Tapestry > Ajax & JavaScript confluence
- [CONF] Apache Tapestry > Ajax & JavaScript confluence