You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 2 Next »

Background

I'd like to see improved Ajax support in Click, especially around the area of reusable Ajax controls. Currently it is not possible to create Ajax aware controls.

There are a number of ways we can improve Ajax support, so I'd like to capture some proposals for further discussion. It might be worth integrating more than one proposal in the final solution.

I'll use jQuery where client-side JavaScript is required to convey a concept.

There are two sides to Ajax support, the client side HTML markup and JavaScript code to initiate the Ajax request, and server side support to handle the request and return a partial response.

I won't go into great detail of the client-side support necessary. Instead I'll focus on the server side support.

Client Side

Below is a simple client-side example I'll use to convey the proposals for server-side support.

my-page.htm
<a id="mylink" href="my-page.htm">Click Me</a>

<div id="target">Update Me</div>

Javascript:

my-page.js
// Bind a click handler to the link with id 'mylink'
$('#mylink').bind('click', function(event) {

  // Make an Ajax call to my-page.htm passing the parameters 'mylink', to identify which
  // control was clicked and 'event', to identify which event was fired.
  // If the request succeeds, the 'success' method is called
  $.get("my-page.htm", { mylink: 1, event: event.type }, success);

  function success(data) {
    // Update the target div with the Ajax response.
    $('#target').html(data);
  }
});

This example is fairly simple and self-explanatory.

For client side support, I suggest we keep JavaScript / Ajax functions in Velocity templates instead of wrapping it in a Java API. By hosting the JavaScript it in a template we can immediately see changes to the template, no compilation or redeploy step is required.

Next I'll cover the proposals to handle the Ajax request and response on the server-side.

1. Ajax aware Page methods

The simplest way to improve Ajax support is to allow Page methods to be called directly from the browser. The Page method could return a Partial response that contains the data to return to the browser.

MyPage.java
public MyPage extends Page {

  public Partial loadLabel() {
    // A partial object provides a way to return a partial response to the browser. Any content added to the Partial object,
    // is streamed back to the client
    Partial partial = new Partial();
    partial.add(new Label("hello"));
    return partial;
  }
}

The example client-side Ajax call will need slight modification, since a Page method is invoked instead of a Control:

my-page.js
  $.get("my-page.htm", { mylink: 1, event: event.type, page-method: loadLabel }, success);

In order to support invocation of Page methods, Click should be enhanced so that if the 'page-method' request parameter is present, that request parameter value will be the Page method to invoke. The signature of the method must be: "public Partial methodName()". Except for creating the Page object, no other page events (onInit, onRender etc) are executed.

Pros:

  • simple to implement
  • easy for users to understand and use

Cons:

  • caters only for GET requests, not POSTS, since page events are not executed
  • does not cater for ajax aware controls
  • interaction with other components on the Page will be fairly limited, since no initialization or binding occurs

2. Ajax aware Controls

A more sophisticated approach would be to allow controls to register for Ajax requests. A special AjaxListener can be set as a Control listener, that will be invoked if the incoming request targets the control:

MyPage.java
public MyPage extends Page {

  public onInit() {

    ActionLink link = new ActionLink("link");
    link.setActionListener(new AjaxListener("click") {

      public Partial onAjaxAction(Control resource, Event event) {
        Partial partial = new Partial();
        partial.add(new Label("hello"));
        return partial;
      }
    });
  }
}

This approach will allow creating reusable ajax aware controls, but it could lead to an explosion of new controls, as each control needing ajax behavior will need to be extended and enhanced. This problem could be alleviated by creating Helper objects that can "inject" the necessary JavaScript code to make Ajax calls from the browser.

MyPage.java
public MyPage extends Page {
  public void onInit() {
    Form form = new Form("form");
    TextField field = new TextField("field");
    form.add(field);  // <- First we attach the field to its parent form to ensure the field.getId()
                      // will return its fully qualified ID: "form-field", not just "field".

    AjaxHelper helper = new AjaxHelper();
    helper.ajaxify(field); // <- this call will inject necessary Javascript code to make Ajax calls
  }
}

The Helper's ajaxify() call will leverage the control method #getHeadElements to add JavaScript libraries, JavaScript template code etc. The ajaxify() method could also be used to register the Control to the AjaxRegistry, instead of building this logic into the Control's onInit method.

Pros:

  • Relatively simple to understand and use
  • Both POST and GET requests are supported. For example an Form could be created which performs an Ajax post. As the onInit event is executed, the Form and Fields will be created and ready to be bound to incoming request parameters. Thus a Submit button can register an Ajax listener and save the submitted Form data to the database, before responding with a Partial response, such as a success message, or error message.
  • Can create prepackaged Ajax aware controls

Cons:

  • Can lead to difficult to find bugs if there is mismatch between the client side Ajax ID attribute and the server side control ID attribute. Dynamic stateless pages could also lead to difficulties if an Ajax control is added inside another control's listener and subsequent requests won't have the Ajax control present in the server-side component tree.
  • Implementation is more complex than for Page Methods
  • The stateless nature of Click makes Ajax applications inherently tricky, because if a non-Ajax request is made to the server, all Ajax related state will be undone. This can be alleviated by using Stateful pages, but since requests to stateful pages are serialized, this creates another, albeit small, problem in that parallel Ajax requests to a stateful page won't work. In other words if the browser makes two Ajax requests to a stateful Page where the first request is very resource intensive and takes 10 seconds to execute, the second Ajax request will have to wait until the first request finishes. Although this might not be a common use case, it is worth taking into consideration.

Outstanding Issues:

  • A control can only have a single listener defined. How will a control handle multiple incoming events? For example a field might handle two Ajax events, "onblur" for validation purposes, and "onkeypress" for auto-completion. The Event object passed into the listener could be inspected, but this leads to if-else logic. This brings up the idea of having multiple listeners per control.
    • Pro: Each listener could be registered for a specific event, and will only be triggered for Ajax requests of that event type.
    • Con: Having multiple listeners per control could increase the conceptual complexity and could lead to an increase in maintenance. For example, if the client-side code neglects to pass the event parameter, no listener will be invoked. The user will now have to deal with whether the control ID or event request parameter or both are missing.

3. Behaviors

Behaviors is a concept borrowed from Wicket and JSF. A behavior is attached to a Control and will be notified by the Control for specific events such as onInit, onProcess, onRender, getHeadElements. A behavior can also act as a listener and receive events, just like a Control does.

In terms of Ajax, a Behavior could be added to a Control and will inject the necessary JavaScript and Ajax code needed by the control. Further, the Behavior will act as a listener and will receive the incoming request. So unlike Proposal 2 above, the Control will not be invoked for Ajax requests.

To make things more conrete, lets look at an example:

MyPage.java
public class MyPage extends Page {
  public void onInit() {

  }
}
  • No labels