blob: 1d1dda07912aeb341fad5a4286f989e14ce6bfa7 [file] [log] [blame]
With Wicket it's quite easy to build a callback URL that executes a specific method on server side. This method must be defined in a functional interface (i.e. an an interface that defines just one method) that inherits from built-in @org.apache.wicket.IRequestListener@ and it must be a void method with no parameters in input:
{code}
public interface IMyListener extends IRequestListener
{
/**
* Called when the relative callback URL is requested.
*/
void myCallbackMethod();
}
{code}
To control how the method will be invoked we must use class @org.apache.wicket.RequestListenerInterface@. In Wicket is a common practice to instantiate this class as a public static field inside the relative callback interface:
{code}
public interface IMyListener extends IRequestListener
{
/**RequestListenerInterface instance*/
public static final RequestListenerInterface INTERFACE = new
RequestListenerInterface(IMyListener.class);
/**
* Called when the relative callback URL is requested.
*/
void myCallbackMethod();
}
{code}
By default @RequestListenerInterface@ will respond rendering the current page after the callback method has been executed (if we have a non-AJAX request). To change this behavior we can use setter method @setRenderPageAfterInvocation(boolean)@.
Now that our callback interface is complete we can generate a callback URL with @Component@'s method @urlFor(RequestListenerInterface, PageParameters)@ or with method @urlFor (Behavior, RequestListenerInterface, PageParameters)@ if we are using a callback interface with a behavior (see the following example).
Project CallbackURLExample contains a behavior (class @OnChangeSingleChoiceBehavior@) that implements a callback interface to update the model of an @AbstractSingleSelectChoice@ component when user changes the selected option (it provides the same functionality of method @wantOnSelectionChangedNotifications@).
Instead of a custom callback interface, @OnChangeSingleChoiceBehavior@ implements built-in interface @org.apache.wicket.behavior.IBehaviorListener@ which is designed to generate a callback URL for behaviors. The callback method defined in this interface is @onRequest()@ and the following is the implementation provided by @OnSelectionChangedNotifications@:
{code}
@Override
public void onRequest() {
Request request = RequestCycle.get().getRequest();
IRequestParameters requestParameters = request.getRequestParameters();
StringValue choiceId = requestParameters.getParameterValue("choiceId");
//boundComponent is the component that the behavior it is bound to.
boundComponent.setDefaultModelObject( convertChoiceIdToChoice(choiceId.toString()));
}
{code}
When invoked via URL, the behavior expects to find a request parameter (choiceId) containing the id of the selected choice. This value is used to obtain the corresponding choice object that must be used to set the model of the component that the behavior is bound to (boundComponent). Method @convertChoiceIdToChoice@ is in charge of retrieving the choice object given its id and it has been copied from class @AbstractSingleSelectChoice@.
Another interesting part of @OnChangeSingleChoiceBehavior@ is its method @onComponentTag@ where some JavaScript “magic” is used to move user's browser to the callback URL when event “change” occurs on bound component:
{code}
@Override
public void onComponentTag(Component component, ComponentTag tag) {
super.onComponentTag(component, tag);
CharSequence callBackURL = getCallbackUrl();
String separatorChar = (callBackURL.toString().indexOf('?') > -1 ? "&" : "?");
String finalScript = "var isSelect = $(this).is('select');\n" +
"var component;\n" +
"if(isSelect)\n" +
" component = $(this);\n" +
"else \n" +
" component = $(this).find('input:radio:checked');\n" +
"window.location.href='" + callBackURL + separatorChar +
"choiceId=' + " + "component.val()";
tag.put("onchange", finalScript);
}
{code}
The goal of @onComponentTag@ is to build an onchange handler that forces user's browser to move to the callback URL (modifing standard property window.location.href). Please note that we have appended the expected parameter (choiceId) to the URL retrieving its value with a JQuery selector suited for the current type of component (a drop-down menu or a radio group). Since we are using JQuery in our JavaScript code, the behavior comes also with method @renderHead@ that adds the bundled JQuery library to the current page.
Method @getCallbackUrl()@ is used to generate the callback URL for our custom behavior and it has been copied from built-in class @AbstractAjaxBehavior@:
{code}
public CharSequence getCallbackUrl(){
if (boundComponent == null){
throw new IllegalArgumentException(
"Behavior must be bound to a component to create the URL");
}
final RequestListenerInterface rli;
rli = IBehaviorListener.INTERFACE;
return boundComponent.urlFor(this, rli, new PageParameters());
}
{code}
Static field @IBehaviorListener.INTERFACE@ is the implementation of @RequestListenerInterface@ defined inside callback interface @IBehaviorListener@.
The home page of project @CallbackURLExample@ contains a @DropDownChoice@ and a @RadioChoice@ which use our custom behavior. There are also two labels to display the content of the models of the two components:
!CallbackURLExample-screenshot.png!
{note}
Implementing interface @IBehaviorListener@ makes a behavior stateful because its callback URL is specific for a given instance of component.
{note}
As final note it's interesting to see how Wicket internally uses callback URLs for its standard link component. Class @org.apache.wicket.markup.html.link.Link@ implements interface @org.apache.wicket.markup.html.link.ILinkListener@ which in turn extends @IRequestListener@:
{code}
public interface ILinkListener extends IRequestListener
{
/** Listener interface */
public static final RequestListenerInterface INTERFACE = new RequestListenerInterface(
ILinkListener.class);
/**
* Called when a link is clicked.
*/
void onLinkClicked();
}
{code}
The implementation of method @onLinkClicked@ simply delegates event handling to our custom version of @onClick@:
{code}
@Override
public final void onLinkClicked()
{
// Invoke subclass handler
onClick();
}
{code}
h3. Wicket events infrastructure
Starting from version 1.5 Wicket offers an event-based infrastructure for inter-component communication. The infrastructure is based on two simple interfaces (both in package @org.apache.wicket.event@) : @IEventSource@ and @IEventSink@.
The first interface must be implemented by those entities that want to broadcast en event while the second interface must be implemented by those entities that want to receive a broadcast event.
The following entities already implement both these two interfaces (i.e. they can be either sender or receiver): @Component@, @Session@, @RequestCycle@ and @Application@.
@IEventSource@ exposes a single method named send which takes in input three parameters:
* *sink*: an implementation of @IEventSink@ that will be the receiver of the event.
* *broadcast*: a @Broadcast@ enum which defines the broadcast method used to dispatch the event to the sink and to other entities such as sink children, sink containers, session object, application object and the current request cycle. It has four possible values:
{table}
*Value* | *Description*
BREADTH | The event is sent first to the specified sink and then to all its children components following a breadth-first order.
DEPTH | The event is sent to the specified sink only after it has been dispatched to all its children components following a depth-first order.
BUBBLE | The event is sent first to the specified sink and then to its parent containers.
EXACT | The event is sent only to the specified sink.
{table}
* *payload*: a generic object representing the data sent with the event.
Each broadcast mode has its own traversal order for @Session@, @RequestCycle@ and @Application@. See JavaDoc of class @Broadcast@ for further details about this order.
Interface @IEventSink@ exposes callback method @onEvent(IEvent<?> event)@ which is triggered when a sink receives an event. The interface @IEvent@ represents the received event and provides getter methods to retrieve the event broadcast type, the source of the event and its payload. Typically the received event is used checking the type of its payload object:
{code}
@Override
public void onEvent(IEvent event) {
//if the type of payload is MyPayloadClass perform some actions
if(event.getPayload() instanceof MyPayloadClass) {
//execute some business code.
}else{
//other business code
}
}
{code}
Project @InterComponetsEventsExample@ provides a concrete example of sending an event to a component (named 'container in the middle') using all the available broadcast methods:
!InterComponentsEventsExample-screenshot.png!