EventListener, the swiss army knife of ajax functionality

The EventListener annotation is probably going to be the most frequently used new feature (if history from tacos users is any indicator) in Tapestry 4.1. It offers an awful lot, and is based around the functionality now familiar to many in dojo's event API.

See also: EventListener core annotation documentation., Quirksmode

At its core this new annotation allows you to bind client side events to page/component listener methods. "Client Side" events can have a lot of different meanings. It could mean listening to function calls on a Tapestry supported dojo widget, or it could mean listening to changing field values in a Tapestry Form component.

Basic example, listening to pure DOM events

In this example we want to be notified whenever anyone moves their mouse over a particular html element on our page. :

Note: Not to be confused with listening to elements rendered by components, for that use the target attribute.

....
<body>

  <div id="myFavoriteDiv">Big brother is watching you.</div>

</body>
....

The java page class snippet required to bind to this event:

....

@EventListener(elements = "myFavoriteDiv", events = "onmouseover")
public void watchText()
{
 // do something 
}

....

That's it! If you'd like more contextual information, like what was happening with the event that initiated the original client-side event just add a BrowserEvent parameter to your listener and it will be automatically populated.

....

@EventListener(elements = "myFavoriteDiv", events = "onmouseover")
public void watchText(BrowserEvent event)
{
 // do something 
 System.out.println("User clicked on x/y coordinates " 
                    + event.getPageX() + "/" + event.getPageY());
}

....

Complex possibilities, binding widget functions to form submissions

Depending on the number of parameters you specify you can achieve some fairly complicated (under the covers at least) logic with very little work.

Listening to widget functions

In this example we want our listener method to be called when the Autocomplete component on our page has selected an entry.

The relevant html:

....

<form jwcid="myform@Form" clientValidationEnabled="true">
        <fieldset>
           Select a project:
           <span jwcid="projectSelect" />
        </fieldset>
</form>

....

The java page class snippet:

....
@Component(bindings = { "model=projectModel", "value=selectedProject",
        "displayName=message:choose.project", "filterOnChange=true",
        "validators=validators:required"})
public abstract Autocompleter getProjectSelection();

@EventListener(targets = "projectChoose", events = "onValueChanged")
public void projectSelected()
{
        // do something
}
....

Accessing intercepted functions' parameters on the server-side

Note: This feature requires version 4.1.3.

When listening to a client-side javascript function that does not represent a native browser-event, you can access the parameters passed to the function on the server-side. The Parameters are made available as a JSON-Array within the class BrowserEvent.

So, given a javascript function "trigger" defined on component "triggerable" which was called like triggerable.trigger({"theAnswer":42}, ... ), you may get hold of the answer like that:

@EventListener(events="trigger", targets="triggerable")
public void onTriggered( BrowserEvent event )
{
   doSomething( event.getMethodArguments().getJSONObject(0).getInt("theAnswer") );
}

The following Javascript snippet shows how trigger() could be defined:

<div jwcid="triggerable@Any"> ... </div>

<script type="text/javascript">
    var triggerable = document.getElementById('triggerable');
    triggerable.trigger = function(params) {
        alert('calling server with answer ' + params.theAnswer);
    }
</script>

In fact, the function trigger is not even required to exist, which comes in handy if its only purpose would have been being intercepted at the server-side.

Submitting forms when an event happens, and bypass client validation while you're at it

The last example was good for showing how to listen to widget function events, but what are you supposed to do with an event that comes from a IFormComponent that doesn't also submit and update the form value that was changed?

Nothing! If you specify a component target that already implements IFormComponent the event system will automatically wire up the form submission for you.

In the rare instance where you would want to submit some other form instead of the default you can do so by using the submitForm parameter.

....

@EventListener(targets = "projectChoose", events = "onValueChanged",
          submitForm = "myForm")
public void projectSelected()
{
        // do something
}

....

That's it! When your listener is invoked you can be confident that your projectSelect Autocompleter component has also been updated to reflect the currently selected value.

As an added bonus, form validation is turned off by default with the EventListener annotation as the majority use case is likely to be one off individual events where invoking client side validation would be a cumbersome experience for users.

Turning off asynchronous submissions

There are some rare instances where you want to wire up an event to a IFormComponent that you would like to cause a submission but not have it happen asynchronously. For that you can use the async=false attribute.

....

@EventListener(targets = "projectChoose", events = "onValueChanged", async = false)
public void projectSelected()
{
        // do something
}

....

A note about event names

One common misunderstanding with EventListener is the exact syntax to use when listening to events. The rules aren't very complicated, but there are two important differences:

  • Native Events : These are events generated by actions people take in the web browser. Such events might be Mouse,Keyboard,Focus,or any combination within. All are covered in the handy reference at Quircksmode.org. These events should almost always be specified in all lowercase. Event though it is perfectly legal to do something like window.onScroll=function doMyBidding(evt), this is not the same thing that happens when you call window.addEventListener('onscroll',function(e)); - which is more or less what Dojo is doing. Just keep all of your event names in lowercase and you'll be ok.
  • Function Names : Because the Dojo event connection system is so powerful it allows you to bind to more than just native javascript events, like function calls on javascript functions. The example above referenced the selectOption function for the Autocompleter widget. It is valid/correct to use proper capital case when referencing actual javascript functions on the client side.