Sunday, February 5, 2012
Taking The New Wicket Plugin For Netbeans For A Test Ride
Sunday, December 25, 2011
Merry Christmas And A Happy New Year Message... The Wicket Way
Wednesday, December 21, 2011
A Reusable jQuery UI Autocomplete Wicket Component
In my previous article I demonstrated using Wicket's AbstractAjaxBehavior to integrate with the jQuery UI Autocomplete component. In this article I build upon those concepts to create a reusable JQueryAutocompleteTextField. If you haven't read my previous article please do so now. Also, this article assumes you know how to add libraries (jars, libraries, projects) to a project using Netbeans.
Note: you may check out read-only working copies of the source for this article anonymously over HTTP:
- The wicket/jQuery integration library svn checkout - http://wicket-jquery-integration.googlecode.com/svn/trunk/ wicket-jquery-integration-read-only
- The demo application svn checkout - http://wicket-jquery-integration-demo-app.googlecode.com/svn/trunk/ wicket-jquery-integration-demo-app-read-only
Wicket provides numerous reusable components and behaviors that makes page composition simple. Any custom components that we develop should be just as easy to use and be as reusable across projects as well. These factors serve as motivation for encapsulating all the logic I presented in the previous article into a fully self contained reusable component hosted in its own jar file which can be reused in any project just like any one of Wicket's standard components.
I will be using Netbeans v7.0.1 throughout this article. If you aren't using Netbeans along with the Wicket Plugin for your Wicket development then you aren't using the best IDE available for Wicket development period. This powerful duo provides a level of support for Wicket development that no other IDE can offer. I highly recommend that if you haven't done so already to please download and install Netbeans and then install the Wicket Plugin. You won't regret it, I promise you.
This article will be a little more in depth than my previous ones. So go make yourself a pot of coffee and then lets roll up our sleeves and get on with it then...
Objectives And Requirements
First off, any project worth your effort and time is worth at least minimaly defining its objectives and requirements. Otherwise, how would you know if you are successful or not? So, before we get into the nuts and bolts of the implementation, let us minimally identify the objectives and requirements of the JQueryAutocompleteTextField component:
- JQueryAutocompleteTextField must fully encapsulate any resources it requires. In the case of our component, the resource is the Javascript code needed for implementation on the client. Therefore the Javascript should be a packaged resource and adapted and rendered along with the markup for the JQueryAutocompleteTextField. User's shouldn't have to write jQuery script to use JQueryAutocompleteTextField.
- JQueryAutocompleteTextField should be just as simple to create as it is to create an out-of-the-box TextField. It also has to be as generic as an out-of-the-box TextField. In the case of JQueryAutocompleteTextField we will extend Wicket's TextField and add our own generic implementation onto that.
- JQueryAutocompleteTextField has to be reusable across projects so that means it has to be hosted in its own jar file.
- Fire up Netbeans and from the main menu select File | New Project | Java | Java Application and click Next.
- Enter any name you want for the Project Name. I am using WicketJQueryIntegration. If the Use Dedicated Folder for Storing Libraries option is selected, unselect it. Also unselect Create Main Class if it is selected and then click Finish for Netbeans to generate the project.
- Right click on the default src package and select Refactor | Rename and enter any package name you wish to use. I am using com.wicket.jquery.integration.autocomplete.
public abstract class AbstractJQueryAutocompleAjaxBehavior extends AbstractAjaxBehavior {
// The jQuery selector to be used by the jQuery ready handler that registers the autocomplete behavior
final private String jQuerySelector;
/**
* Constructor
* @param jQuerySelector - a string containing the jQuery selector
* for the target html element (<input type='text'... of the jQuery UI
* Autocomplete component
*/
public AbstractJQueryAutocompleAjaxBehavior(String jQuerySelector) {
super();
this.jQuerySelector = jQuerySelector;
}
As was stated in our Objective And Requirements, item #1, we don't want the users of our component to have to write their own jQuery code to wire up jQuery UI's Autocomplete component and therefore our component must render that code to the client page. Here's the boilerplate javascript that we want to use to render to the client page:
$(document).ready(function(){
$('${selector}').autocomplete({
source: function(req, add){
//pass request to server
$.ajax({
url: '${callbackUrl}',
type: 'GET',
cache: false,
data: req,
dataType: 'json',
success: function(json){
var suggestions = [];
//process response
$.each(json, function(i, val){
suggestions.push(val.name);
});
// call autocomplet callback method with results
add(suggestions);
},
error: function(XMLHttpRequest, textStatus, errorThrown){
//alert('error - ' + textStatus);
console.log('error', textStatus, errorThrown);
}
});
}
});
});
Create a new Javascript file in the com.wicket.jquery.integration.autocomplete package and name it autocomplete.js. Then copy the code above into it and save the file.
We are faced with a slight dilemma because we have no way of knowing which html element the user is going to use. The solution, as implemented, is to allow the user to tell us that by providing a jQuery selector in the constructor that identifies it. But wait, how can we alter the Javascript to use this selector? Well, fortunately for us Wicket makes this kind of easy. Wicket supports modifying packaged resources through a process known as interpolation which just means replacing one thing in the resource with another thing. In Wicket's case the things that need to be replaced are identified by ${...} markers. In each marker we provide a name for the marker. In the above Javascript file we provide for two markers, ${selector} which will be replaced with the jQuery selector provided in the constructor and ${callbackUrl} which will be replaced by the callback url to our Wicket Ajax event handler which we will later implement by overriding onRequest.
When we interpolate we provide a HasMap that contains key/value objects. Their keys must match the names we gave each marker and their associated values are what will be used to replace the marker with. When our Javascript resource is contributed to the page it will therefore be rendered with both the jQuery selector and the callback URL. Let's implement that now by overriding the renderHead method of AbstractAjaxBehavior:
/**
* Contributes a jQuery ready handler that registers autocomplete
* behavior for the html element represented by the selector.
*
* The generation of the ready handler uses interpolation, applying
* the jQuery selector and the variable name of the return call
* back url.
*
* @param component
* @param response
*/
@Override
public void renderHead(Component component, IHeaderResponse response) {
super.renderHead(component, response);
Map<String, CharSequence> map = new HashMap<String, CharSequence>(2);
map.put("selector", jQuerySelector);
map.put("callbackUrl", getCallbackUrl());
PackageTextTemplate packageTextTemplate = new PackageTextTemplate(getClass(), "autocomplete.js", "text/javascript");
String resource = packageTextTemplate.asString(map);
response.renderJavaScript(resource, jQuerySelector);
}
Finally we need to actually render the resource to the client page. This is done by calling renderJavaScript, passing it the interpolated resource and the jQuerySelector parameter as a unique id.
Besides rendering the Javascript we must also handle the actual Ajax callback by overriding the onRequest method. Remember, when we stated our objective and requirements we said in item #2 that the component has to be generic. All that means is that we won't assume how the user intends to use the component. Here is the code for our generic implementation of onRequest :
@Override
public void onRequest() {
Logger logger = LoggerFactory.getLogger(this.getClass());
logger.info("ajax request received");
RequestCycle requestCycle = RequestCycle.get();
Request request = requestCycle.getRequest();
IRequestParameters irp = request.getRequestParameters();
StringValue term = irp.getParameterValue("term");
List<?> matches = getMatches(term.toString());
String json = convertListToJson(matches);
requestCycle.scheduleRequestHandlerAfterCurrent(new TextRequestHandler("application/json", "UTF-8", json));
}
In the code above we retrieve the parameter passed to the callback which is the term the user entered into the TextField tied to the jQuery Ui Autocomplete component. We use that term by passing it to an abstract method which we define as follows:
public abstract List<?> getMatches(String term);
Because we declared this method as abstract it will have to be implemented by the user of AbstractJQueryAutocompleAjaxBehavior which I will get to shortly but this is one way we keep this generic, by deferring implementation of this method. We also keep this generic by defining this method's return value using generics and in our case as a List of some object.
Finally, onRequest then calls convertListToJson, a method that converts the List of objects returned from the getMatches method to a Javascript array of JSON objects. That array is then returned to the client which will be passed to the success callback method of the Autocomplete component defined in our Javascript.
Here's the complete code for AbstractJQueryAutocompleAjaxBehavior:
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package com.wicket.jquery.integration.autocomplete;
import com.google.gson.Gson;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.wicket.Component;
import org.apache.wicket.behavior.AbstractAjaxBehavior;
import org.apache.wicket.markup.html.IHeaderResponse;
import org.apache.wicket.request.IRequestParameters;
import org.apache.wicket.request.Request;
import org.apache.wicket.request.cycle.RequestCycle;
import org.apache.wicket.request.handler.TextRequestHandler;
import org.apache.wicket.util.string.StringValue;
import org.apache.wicket.util.template.PackageTextTemplate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*
* @author jeffrey
*/
public abstract class AbstractJQueryAutocompleAjaxBehavior extends AbstractAjaxBehavior {
// The jQuery selector to be used by the jQuery ready handler that registers the autocomplete behavior
final private String jQuerySelector;
/**
* Constructor
* @param jQuerySelector - a string containing the jQuery selector
* for the target html element (<input type='text'... of the jQuery UI
* Autocomplete component
*/
public AbstractJQueryAutocompleAjaxBehavior(String jQuerySelector) {
super();
this.jQuerySelector = jQuerySelector;
}
/**
* Contributes a jQuery ready handler that registers autocomplete
* behavior for the html element represented by the selector.
*
* The generation of the ready handler uses interpolation, applying
* the jQuery selector and the variable name of the return call
* back url.
*
* @param component
* @param response
*/
@Override
public void renderHead(Component component, IHeaderResponse response) {
super.renderHead(component, response);
Map<String, CharSequence> map = new HashMap<String, CharSequence>(2);
map.put("selector", jQuerySelector);
map.put("callbackUrl", getCallbackUrl());
PackageTextTemplate packageTextTemplate = new PackageTextTemplate(getClass(), "autocomplete.js", "text/javascript");
String resource = packageTextTemplate.asString(map);
String uniqueName = Long.toString(Calendar.getInstance().getTimeInMillis());
response.renderJavaScript(resource, uniqueName);
}
@Override
public void onRequest() {
Logger logger = LoggerFactory.getLogger(this.getClass());
logger.info("ajax request received");
RequestCycle requestCycle = RequestCycle.get();
Request request = requestCycle.getRequest();
IRequestParameters irp = request.getRequestParameters();
StringValue term = irp.getParameterValue("term");
List<?> matches = getMatches(term.toString());
String json = convertListToJson(matches);
requestCycle.scheduleRequestHandlerAfterCurrent(new TextRequestHandler("application/json", "UTF-8", json));
}
public abstract List<?> getMatches(String term);
/*
* Convert List to json object.
*
* Dependency on google-gson library which is
* available at http://code.google.com/p/google-gson/
* and which must be on your classpath when using this
* library.
*/
private String convertListToJson(List<?> matches) {
Gson gson = new Gson();
String json = gson.toJson(matches);
return json;
}
}
All that remains for completing our component's implementation is to implement JQueryAutocompleteTextField and this actually is the easiest part to do. Here's the complete code to JQueryAutocompleteTextField:
package com.wicket.jquery.integration.autocomplete;
import java.util.List;
import org.apache.wicket.markup.html.form.TextField;
import org.apache.wicket.model.IModel;
/**
*
* @author jeffrey
*/
public abstract class JQueryAutoCompleteTextField<T extends Object> extends TextField<T> {
private final String jQuerySelector;
public JQueryAutoCompleteTextField(String id, IModel<T> model, Class<T> type, String jQuerySelector) {
super(id, model, type);
this.jQuerySelector = jQuerySelector;
common();
}
public JQueryAutoCompleteTextField(String id, IModel<T> model, String jQuerySelector) {
super(id, model);
this.jQuerySelector = jQuerySelector;
common();
}
public JQueryAutoCompleteTextField(String id, Class<T> type, String jQuerySelector) {
super(id, type);
this.jQuerySelector = jQuerySelector;
common();
}
public JQueryAutoCompleteTextField(String id, String jQuerySelector) {
super(id);
this.jQuerySelector = jQuerySelector;
common();
}
private void common(){
add(new AbstractJQueryAutocompleAjaxBehavior(jQuerySelector) {
@Override
public List<?> getMatches(String term) {
return JQueryAutoCompleteTextField.this.getMatches(term);
}
});
}
public abstract List<?> getMatches(String term);
}
In the code above we provide numerous constructors allowing the user versatility in how they construct the component. Additionally, each constructor calls the method common which adds an instance of AbstractJQueryAutocompleAjaxBehavior to it. Because in AbstractJQueryAutocompleAjaxBehavior we declared getMatches as abstract we are here forced to provide for its actual implementation. All our implementation does is call JQueryAutocompleteTextField's getMatches method which is also declared as abstract. By defining this abstract method in JQueryAutocompleteTextField our users will have to provide their own implementation which will depend upon their individual use-cases and fulfills our requirement that this component provide a generic implementation allowing it be used however the user's use-case might dictate.
- From Netbeans main menu select File | New Project | Java Web | Web Application which will open the New Project wizard.
- In the wizard enter any name you like for the test project. I named mine WicketAndJQueryAjax and make sure that the Set As Main Project option is selected. Click Next.
- Select either GlassFish Server or Apache Tomcat for the server option and click Next.
- From the list of available Frameworks select Wicket and then click Finish.
- Right click on the test project node and select Properties.
- From the Categories pane select Libraries and click the Add Project button.
- In the Add Project window select our components project, WicketJQueryIntegration and click the Add Project Jar Files button.
- Right click on the test project node and select Properties and select Libraries from the Categories pane in the Project Properties window.
- Select the option Build Required Projects and click the OK button.
- BasePage.java
- BasePage.html
- FooterPanel.java
- FooterPanel.html
- HeaderPanel.java
- HeaderPanel.html
Open the HomePage.html file in the editor by double clicking on it. When you do, both it and the HomePage.java file will open. This behavior, one of many, is contributed by the Wicket Plugin.
With the HomePage.html file now opened in the editor, replace all of its content with the following:
<!DOCTYPE html>
<html xmlns:wicket="http://wicket.apache.org">
<head>
<meta charset="UTF-8">
<title>Wicket Example</title>
<link type="text/css" href="css/start/jquery-ui-1.8.16.custom.css" rel="Stylesheet" />
<script type="text/javascript" src="js/jquery.js"></script>
<script type="text/javascript" src="js/jquery-ui-1.8.16.custom.min.js"></script>
</head>
<body>
<h1>Marrying Wicket And jQuery UI Auto Complete Ajax</h1>
<h2>jQuery UI Auto Complete State Lookup</h2>
<form class="jqueryid_form2" wicket:id="form1">
US State Lookup: <input class="jqueryid_state" type="text" wicket:id="state"/><br/>
</form>
</body>
</html>
Notice that in the above there is no reference to a jQuery UI Autocomplete component, just the normal markup one would expect in any Wicket application. Also notice, there is no jQuery ready handler either. The real magic takes place when the component is rendered by Wicket.
Now replace all of the contents of HomePage.java with the following:
/*
* HomePage.java
*
* Created on December 3, 2011, 10:21 AM
*/
package com.myapp.wicket;
import com.wicket.jquery.integration.autocomplete.JQueryAutoCompleteTextField;
import java.util.ArrayList;
import java.util.List;
import org.apache.wicket.markup.html.WebPage;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.form.TextField;
import org.apache.wicket.model.PropertyModel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class HomePage extends WebPage {
private final Form<HomePage> form1;
private final TextField<String> stateTextField;
private String state = "";
/*
* A State class jsonified into an array and returned to the client
*/
static class State {
State() {
}
State(String name) {
this.name = name;
}
String name;
}
private static class StatesDb {
private static final String[] states = new String[]{
"Alabama",
"Alaska",
"Arizona",
"Arkansas",
"California",
"Colorado",
"Connecticut",
"Delaware",
"Florida",
"Georgia",
"Hawaii",
"Idaho",
"Illinois",
"Indiana",
"Iowa",
"Kansas",
"Kentucky",
"Louisiana",
"Maine",
"Maryland",
"Massachusetts",
"Michigan",
"Minnesota",
"Mississippi",
"Missouri",
"Montana",
"Nebraska",
"Nevada",
"New Hampshire",
"New Jersey",
"New Mexico",
"New York",
"North Carolina",
"North Dakota",
"Ohio",
"Oklahoma",
"Oregon",
"Pennsylvania",
"Rhode Island",
"South Carolina",
"South Dakota",
"Tennessee",
"Texas",
"Utah",
"Vermont",
"Virginia",
"Washington",
"West Virginia",
"Wisconsin",
"Wyoming"
};
static List<State> getStatesLike(String target) {
List<State> matches = new ArrayList<State>();
for (String s : states) {
if (s.toLowerCase().startsWith(target.toLowerCase())) {
State state = new State(s);
matches.add(state);
}
}
return matches;
}
}
public HomePage() { /*
* A private static class for states which supports
* querying for states that are like target and
* returning those in a list.
*/
add(form1 = new Form<HomePage>("form1") {
@Override
protected void onSubmit() {
super.onSubmit();
Logger logger = LoggerFactory.getLogger(this.getClass());
logger.info("The user selected: " + state);
}
});
/*
* A TextField with JQueryAutocompleteBehavior
*/
form1.add(stateTextField = new JQueryAutoCompleteTextField<String>("state",
new PropertyModel<String>(this, "state"), "input.jqueryid_state") {
@Override
public List<?> getMatches(String term) {
List<State> statesLike = StatesDb.getStatesLike(term.toString());
return statesLike;
}
});
}
}
Notice in the above that we add a JQueryAutoCompleteTextField to the form and that we also provide our own implementation for its getMatches method allowing us to tailor the component to our specific use case which is to find a list of states that match the value of term.
Run the project and it should render the following in the browser:
Now start slowly typing New York into the text box and as you do will be presented with a list of choice matching what characters you have entered:
Nice, hey? But wait, there's more. Select one of the suggestions from the list and it will propagate the text box. Once propagated, hit Enter to submit the form and now look at the server's log file and you will see log messages that your selection was submitted with the form.
Magicians are never supposed to reveal their tricks to the public but browser pages have no such ethics so now let us peek at the markup that was rendered to the client and relate that back to our component:
I have highlighted the points of interest in red that relate back to our component. As you can see, the jQuery ready handler itself was contributed and our component tailored it to use the selector we provided it. In addition, it also used the callback url. The success function receives the json object we responded with in our component's onRequest method which had called our getMatches method that we implemented in HomePage.java which returned a List of State objects.
Summary
Wicket v1.5 provides awesome flexibility by allowing easy integration with other component libraries. . The same techniques I used here can be applied in many other cases to create other types of components. The only limits are your imagination so imagine! My imagination tells me that there are many other jQuery UI components just waiting for a similar approach. Hmmm...
I hope you have enjoyed following along with me developing an awesome Wicket/jQuery Ui Autocomplete component. Feel free to leave your comments and please remember to come back soon as I am always cooking something new up to share with you.
Saturday, December 17, 2011
Marrying Wicket And jQuery UI Autocomplete Ajax
/*
* HomePage.java
*
* Created on December 3, 2011, 10:21 AM
*/
package com.myapp.wicket;
import com.google.gson.Gson;
import java.lang.String;
import java.util.ArrayList;
import java.util.List;
import org.apache.wicket.Component;
import org.apache.wicket.behavior.AbstractAjaxBehavior;
import org.apache.wicket.markup.html.IHeaderResponse;
import org.apache.wicket.markup.html.WebPage;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.form.TextField;
import org.apache.wicket.request.IRequestParameters;
import org.apache.wicket.request.Request;
import org.apache.wicket.request.cycle.RequestCycle;
import org.apache.wicket.request.handler.TextRequestHandler;
import org.apache.wicket.util.string.StringValue;
public class HomePage extends WebPage {
private final Form<HomePage> form1;
private final AbstractAjaxBehavior aab1;
private final TextField<String> state;
static class State {
State() {}
State(String name) {
this.name = name;
}
String name;
}
public HomePage() {
add(form1 = new Form<HomePage>("form1"));
form1.add(state = new TextField<String>("state"));
/*
* Ajax Behavior provides an event
* listener as well as the request
* handler.
*/
add(aab1 = new AbstractAjaxBehavior() {
@Override
public void renderHead(Component component, IHeaderResponse response) {
super.renderHead(component, response);
response.renderJavaScript("var callbackUrl = '" + aab1.getCallbackUrl() + "';", "callbackurl");
}
// handle the ajax request
@Override
public void onRequest() {
System.out.println("ajax request received");
RequestCycle requestCycle = RequestCycle.get();
Request request = requestCycle.getRequest();
IRequestParameters irp = request.getRequestParameters();
StringValue state = irp.getParameterValue("term");
List<State> statesLike = MockDb.getStatesLike(state.toString());
String json = convertListToJson(statesLike);
requestCycle.scheduleRequestHandlerAfterCurrent(new TextRequestHandler("application/json", "UTF-8", json));
}
});
}
/*
* Convert List to json object
*/
private String convertListToJson(List<?> matches) {
Gson gson = new Gson();
String json = gson.toJson(matches);
return json;
}
/*
* A Mock Database
*/
static class MockDb {
private static final String[] states = new String[]{
"Alabama",
"Alaska",
"Arizona",
"Arkansas",
"California",
"Colorado",
"Connecticut",
"Delaware",
"Florida",
"Georgia",
"Hawaii",
"Idaho",
"Illinois",
"Indiana",
"Iowa",
"Kansas",
"Kentucky",
"Louisiana",
"Maine",
"Maryland",
"Massachusetts",
"Michigan",
"Minnesota",
"Mississippi",
"Missouri",
"Montana",
"Nebraska",
"Nevada",
"New Hampshire",
"New Jersey",
"New Mexico",
"New York",
"North Carolina",
"North Dakota",
"Ohio",
"Oklahoma",
"Oregon",
"Pennsylvania",
"Rhode Island",
"South Carolina",
"South Dakota",
"Tennessee",
"Texas",
"Utah",
"Vermont",
"Virginia",
"Washington",
"West Virginia",
"Wisconsin",
"Wyoming"
};
static List<State> getStatesLike(String target) {
List<State> matches = new ArrayList<State>();
for (String s : states) {
if (s.toLowerCase().startsWith(target.toLowerCase())) {
State state = new State(s);
matches.add(state);
}
}
return matches;
}
};
}
In the code above we create an AbstractAjaxBehavior and add it to the page to provide a request handler for the ajax call. Our AbstractAjaxBehavior class overrides two methods:
- renderHead, in which we grab the behavior's call back url and contribute it via Javascript to the page where it will be used as the url to call by the jQuery UI autocomplete method.
- onRequest, in which we process the ajax request and in which we use the request parameter whose name is 'term' to look up all the states that begin with its value and convert the names of those states to an array of json objects in the form of [{name : "statename"}...]. Finally, we schedule a TextRequestHandler passing that array as a parameter which will be sent back to the client as the result of the ajax call. Note that our response content type is "application/json", "UTF-8".
And that's really all it takes for Wicket v1.5 to support ajax request that must return json. Now, lets take a look at the markup including the Javascript needed to support our integration with jQuery UI and Wicket:
<!DOCTYPE html>
<html xmlns:wicket="http://wicket.apache.org">
<head>
<meta charset="UTF-8">
<title>Wicket Example</title>
<link type="text/css" href="css/start/jquery-ui-1.8.16.custom.css" rel="Stylesheet" />
<script type="text/javascript" src="js/jquery.js"></script>
<script type="text/javascript" src="js/jquery-ui-1.8.16.custom.min.js"></script>
<wicket:head>
</wicket:head>
<script type="text/javascript" >
$(document).ready(function(){
$('input.jqueryid_state').autocomplete({source: function(req, add){
//pass request to server
$.ajax({
url: callbackUrl,
type: 'GET',
cache: false,
data: req,
dataType: 'json',
success: function(json){
var suggestions = [];
//process response
$.each(json, function(i, val){
suggestions.push(val.name);
});
// call autocomplet callback method with results
add(suggestions);
},
error: function(XMLHttpRequest, textStatus, errorThrown){
//alert('error - ' + textStatus);
console.log('error', textStatus, errorThrown);
}
});
}});
});
</script>
</head>
<body>
<h1>Marrying Wicket And jQuery Ajax</h1>
<h2>jQuery UI Auto Complete State Lookup</h2>
<form class="jqueryid_form2" wicket:id="form1">
US State Lookup: <input class="jqueryid_state" type="text" wicket:id="state"/><br/>
<div id="statelist"/>
</form>
</body>
</html>
In the above Javascript code we create a jQuery ready handler which sets up jQuery UI autocomplete. Here we use a json object that contains the property named source and which provides a callback function that autocomplete will call to retrieve the results matching the user's input. In this callback we use jQuery's ajax method to make an ajax call back to the Wicket event listener whose url is stored in the global variable callbackUrl which we obtained from the AbstractAjaxBehavior we created in our Java code.
Finally, the result from the ajax call, which is an array of json objects whose val properties are the names of the states are passed back to autocomplete by calling the add callback method. jQuery Ui autocomplete uses these to present a list of options to the user as can be seen from the following screen shot:
Monday, September 5, 2011
How To Validate And Block Ajax Callbacks
Wicket allows you to easily add Ajax support to Wicket components by adding some concrete subclass of the AbstractAjaxBehavior class to them which ties a Javascript DOM event on the client to an event listener in an AjaxEventBehavior or some subclass of.
For instance, suppose you want to add Ajax support to a Wicket component backing an input text element that supports the DOM onkeyup event. Doing so is easily accomplished using the following markup and Java code fragments:
Java1: <body>2: <h1>Default Wicket Ajax</h1>3: <form wicket:id="defaultajaximplmentationform">4: Enter Something <input type="text" wicket:id="enterSomething"/>5: </form>6: <div>You entered: <span class="message" wicket:id="message"/></div>7: </body>8:
1: public class HomePage extends WebPage {2:3: private Form<HomePage> form;4: private TextField<String> textField;5: private Label message;6: private String enterSomething = "";7:8: public HomePage() {9: form = new Form<HomePage>("defaultajaximplmentationform");10: add(form);11: textField = new TextField<String>("enterSomething", new PropertyModel<String>(this, "enterSomething"));12: textField.add(new AjaxFormSubmitBehavior("onkeyup") {13:14: @Override15: protected void onSubmit(AjaxRequestTarget target) {16: // do something meaningful here like updating the database17: target.addComponent(message);18: }19:20: @Override21: protected void onError(AjaxRequestTarget target) {22: throw new UnsupportedOperationException("Not supported yet.");23: }24: });25: form.add(textField);26: message = new Label("message", new PropertyModel<String>(this, "enterSomething"));27: message.setOutputMarkupId(true);28: add(message);29: }30:31: }32:
Looking at the Java code above we can see that I added an Ajax callback for the Javascript onkeyup event to the textfield component by adding an AjaxFormSubmittingBehavior to it. When rendered the AjaxFormSubmittingBehavior contributes Javascript code to the input element's onkeyup event that submits the form that the input element is a child of. The Javascript that submits the form calls into Wicket's Ajax Javascript library whenever the user presses and releases a key when the input element has the input focus.Here is what the rendered markup looks like:
1:2: <!DOCTYPE html>3: <html xmlns:wicket="http://wicket.apache.org">4: <head>5: <meta charset="UTF-8">6: <title>Wicket Example</title>7: <style type="text/css">8: span.message {9: color: #0000FF;10: }11: </style>12: <script type="text/javascript" src="resources/org.apache.wicket.markup.html.WicketEventReference/wicket-event.js"></script>13: <script type="text/javascript" src="resources/org.apache.wicket.ajax.WicketAjaxReference/wicket-ajax.js"></script>14: <script type="text/javascript" src="resources/org.apache.wicket.ajax.AbstractDefaultAjaxBehavior/wicket-ajax-debug.js"></script>15: <script type="text/javascript" id="wicket-ajax-debug-enable"><!--/*--><![CDATA[/*><!--*/16: wicketAjaxDebugEnable=true;17: /*-->]]>*/</script>18:19: </head>20: <body>21: <h1>Default Wicket Ajax</h1>22: <form wicket:id="defaultajaximplmentationform" id="defaultajaximplmentationform4" method="post" action="?wicket:interface=:1:defaultajaximplmentationform::IFormSubmitListener::"><div style="display:none"><input type="hidden" name="defaultajaximplmentationform4_hf_0" id="defaultajaximplmentationform4_hf_0" /></div>23: Enter Something <input type="text" wicket:id="enterSomething" value="" name="enterSomething" id="enterSomething5" onkeyup="var wcall=wicketSubmitFormById('defaultajaximplmentationform4', '?wicket:interface=:1:defaultajaximplmentationform:enterSomething::IActivePageBehaviorListener:0:&wicket:ignoreIfNotActive=true', null,function() { }.bind(this),function() { }.bind(this), function() {return Wicket.$$(this)&&Wicket.$$('defaultajaximplmentationform4')}.bind(this));;"/>24: </form>25: <div>You entered: <span class="message" wicket:id="message" id="message6"></span></div>26: </body>27: </html>
Line 23 in the rendered markup above shows that the AjaxFormSubmittingBehavior added the onkeyup event to the input element. The Javascript code it added to the event is the following:
var wcall=wicketSubmitFormById('defaultajaximplmentationform7', '?wicket:interface=:2:defaultajaximplmentationform:enterSomething::IActivePageBehaviorListener:0:&wicket:ignoreIfNotActive=true', null,function() { }.bind(this),function() { }.bind(this), function() {return Wicket.$$(this)&&Wicket.$$('defaultajaximplmentationform7')}.bind(this));;
The Javascript added by AjaxFormSubmittingBehavior calls wicketSubmitFormById which is a method that resides in the wicket-event.js, a Javascript file that was also contributed by AjaxFormSubmittingBehavior to the page.
All of Wicket's numerous Ajax components and behaviors work similarly which is fine if you don't need to do anything on the client after the DOM event and before the Ajax call back to the server. But suppose you want to perform validation before the Ajax call back to the server and possibly block the call back to the server if validation fails, how can you do this?
Well, the solution is actually quite easy but poorly documented. Even the holly grail of Wicket books, Wicket In Action, doesn't cover this because it was written before the solution presented here in this article was provided in the Wicket library so many readers of the book are left scratching their heads in confusion.
Prepending Your Own Javascript To Wicket's Generated Javascript
The solution requires that you override the getAjaxCallDecorator method defined in AbstractDefaultAjaxBehavior in your own behaviors to return an implementation of IAjaxCallDecorator that prepends the Javascript that Wicket generates with your own Javascript which can do anything that your use case might require such as validation and preventing the Ajax call to the server should the validation fail.
First, lets take a look at IAjaxCallDecorator:
1: public interface IAjaxCallDecorator extends IClusterable2: {3: /**4: * Name of javascript variable that will be true if ajax call was made, false otherwise. This5: * variable is available in the after script only.6: */7: public static final String WICKET_CALL_RESULT_VAR = "wcall";8:9: /**10: * Decorates the script that performs the ajax call11: *12: * @param script13: * @return decorated script14: */15: CharSequence decorateScript(CharSequence script);16:17: /**18: * Decorates the onSuccess handler script19: *20: * @param script21: * @return decorated onSuccess handler script22: */23: CharSequence decorateOnSuccessScript(CharSequence script);24:25: /**26: * Decorates the onFailure handler script27: *28: * @param script29: * @return decorated onFailure handler script30: */31: CharSequence decorateOnFailureScript(CharSequence script);32:33: }34:
This interface declares 3 methods that must be implemented, all taking a CharSequence script parameter and returning a CharSequence. The CharSequence script parameter passed to these methods is the Javascript that Wicket generated to call it's Ajax Javascript library and that it assigns as the DOM event handler on the client.
Wicket provides a convenience class, AjaxCallDecorator, which is an adapter class which provides default implementations for these 3 methods.
1: public abstract class AjaxCallDecorator implements IAjaxCallDecorator2: {3:4: /**5: *6: */7: private static final long serialVersionUID = 1L;8:9: /**10: * @see org.apache.wicket.ajax.IAjaxCallDecorator#decorateScript(CharSequence)11: */12: public CharSequence decorateScript(CharSequence script)13: {14: return script;15: }16:17: /**18: * @see org.apache.wicket.ajax.IAjaxCallDecorator#decorateOnSuccessScript(CharSequence)19: */20: public CharSequence decorateOnSuccessScript(CharSequence script)21: {22: return script;23: }24:25: /**26: * @see org.apache.wicket.ajax.IAjaxCallDecorator#decorateOnFailureScript(CharSequence)27: */28: public CharSequence decorateOnFailureScript(CharSequence script)29: {30: return script;31: }32:33:34: }35:
To add Javascript that will call our validation routine and prevent the Ajax call should validation fail is just a matter of implementing the AjaxCallDecorator's decorateScript method and prepending script with our own Javascript which I will discuss next but first lets define a use case and build the implementation around that.
Our use case is the following:
Provide a form with an input text field in which the user can enter some text. The validation rules specify that the text the user enters should be at least 3 characters or they shouldn't be allowed to submit the form (via Ajax).
Now here's the implementation:
Markup
The markup we provide is very straight forward. It contains a form with a wicket id of form that has 1 input text element which we give a wicket id of enterSomething. We also provide a button the user can click to submit the form via Ajax which we give a wicket id of submitbutton. When the form is submitted we will echo what the user enters in a label which we have assigned the wicket id of message.
Java1: <!DOCTYPE html>2: <html xmlns:wicket="http://wicket.apache.org">3: <head>4: <meta charset="UTF-8">5: <title>Wicket Example</title>6: <script type="text/javascript" src="javascript/jquery.js"></script>7: <style type="text/css">8: span.message {9: color: #0000FF;10: }11: </style>12: </head>13: <body>14: <h1>Wicket Ajax With Client-Side Validation Using Script Prepending</h1>15: <form wicket:id="form">16: Enter at least 3 characters to call the AJAX callback: <input type="text" wicket:id="enterSomething"/>17: </form>18: <button wicket:id="submitbutton">Submit</button>19: <div>You entered: <span class="message" wicket:id="message"/></div>20: </body>21: </html>22:
On the server side we create the Wicket components corresponding to the markup elements discussed above. The key point here is that for the button I am using Wicket's AjaxSubmitLink component whose implementation adds an AjaxFormSubmitBehavior which will contribute the Javascript code for the click event on the button to submit the form back to the server using Ajax. If you spy the source code for AjaxSubmitLink you'll see that it exposes the AjaxFormSubmitBehavior's getAjaxCallDecorator method which we can override to return our own implementation of IAjaxCallDecorator:
1: public class HomePage extends WebPage {2:3: private Form<HomePage> form;4: private TextField<String> textField;5: private Label messageLabel;6: private String message = "";7: private String enterSomething = "";8:9: public HomePage() {10: form = new Form<HomePage>("defaultajaximplmentationform");11: add(form);12: textField = new TextField<String>("enterSomething", new PropertyModel<String>(this, "enterSomething"));13: textField.setOutputMarkupId(true);14: textField.add(new PreventFormSubmitOnEnterBehavior());15: form.add(textField);16: add(new AjaxSubmitLink("submitbutton", form) {17:18: @Override19: public void renderHead(HtmlHeaderContainer container) {20: super.renderHead(container);21: String javascript = "validate = function(){ var inputElement = $('#" + textField.getMarkupId() + "'); if($(inputElement).val().length < 3){ $(inputElement).css('background-color', '#ff0000'); return false; }else{ $(inputElement).css('background-color', '#ffffff'); return true; } }";22: container.getHeaderResponse().renderJavascript(javascript, "entersomethingvalidation");23: }24:25: @Override26: protected IAjaxCallDecorator getAjaxCallDecorator() {27: return new AjaxCallDecorator() {28:29: @Override30: public CharSequence decorateScript(CharSequence script) {31: PrependingStringBuffer psb = new PrependingStringBuffer(script.toString());32: psb.prepend("if(validate() == false) return false;");33: return psb.toString();34: }35:36: };37:38: }39:40: @Override41: protected void onSubmit(AjaxRequestTarget target, Form<?> form) {42: System.out.println("yada yada");43: // do something meaningful here like updating the database44: message = enterSomething;45: enterSomething = "";46: target.addComponent(textField);47: target.addComponent(messageLabel);48: }49: });50: messageLabel = new Label("message", new PropertyModel<String>(this, "message"));51: messageLabel.setOutputMarkupId(true);52: add(messageLabel);53: }54:55: }56:
Above we create an instance of AjaxCallDecorator and we override its decorateScript method to prepend the script parameter with our own Javascript on line 30. We prepend Javascript on line 32 that calls our own Javascript function validate which we contributed to the page in our overriding of the renderHead method on line 19. To prevent the Ajax call back to the server should our validate function return false the prepended Javascript returns false.
1: @Override2: protected IAjaxCallDecorator getAjaxCallDecorator() {3: return new AjaxCallDecorator() {4:5: @Override6: public CharSequence decorateScript(CharSequence script) {7: PrependingStringBuffer psb = new PrependingStringBuffer(script.toString());8: psb.prepend("if(validate() == false) return false;");9: return psb.toString();10: }11:12: };13:14: }
When run the markup for HomePage is rendered as follows:
1:2: <!DOCTYPE html>3: <html xmlns:wicket="http://wicket.apache.org">4: <head>5: <meta charset="UTF-8">6: <title>Wicket Example</title>7: <script type="text/javascript" src="../javascript/jquery.js"></script>8: <style type="text/css">9: span.message {10: color: #0000FF;11: }12: </style>13: <script type="text/javascript" ><!--/*--><![CDATA[/*><!--*/14: $(document).ready(function(){$('#enterSomething5').live('keypress', function(e){var c = e.which ? e.which : e.keyCode;if (c == 13) {return false;}});});15: /*-->]]>*/</script>16:17: <script type="text/javascript" src="resources/org.apache.wicket.markup.html.WicketEventReference/wicket-event.js"></script>18: <script type="text/javascript" src="resources/org.apache.wicket.ajax.WicketAjaxReference/wicket-ajax.js"></script>19: <script type="text/javascript" src="resources/org.apache.wicket.ajax.AbstractDefaultAjaxBehavior/wicket-ajax-debug.js"></script>20: <script type="text/javascript" id="wicket-ajax-debug-enable"><!--/*--><![CDATA[/*><!--*/21: wicketAjaxDebugEnable=true;22: /*-->]]>*/</script>23:24: <script type="text/javascript" id="entersomethingvalidation"><!--/*--><![CDATA[/*><!--*/25: validate = function(){ var inputElement = $('#enterSomething5'); if($(inputElement).val().length < 3){ $(inputElement).css('background-color', '#ff0000'); return false; }else{ $(inputElement).css('background-color', '#ffffff'); return true; } }26: /*-->]]>*/</script>27:28: </head>29: <body>30: <h1>Wicket Ajax With Client-Side Validation Using Script Prepending</h1>31: <form wicket:id="defaultajaximplmentationform" id="defaultajaximplmentationform6" method="post" action="?wicket:interface=:1:defaultajaximplmentationform::IFormSubmitListener::"><div style="display:none"><input type="hidden" name="defaultajaximplmentationform6_hf_0" id="defaultajaximplmentationform6_hf_0" /></div>32: Enter at least 3 character to call the AJAX callback: <input type="text" wicket:id="enterSomething" value="" name="enterSomething" id="enterSomething5"/>33: </form>34: <button wicket:id="submitbutton" id="submitbutton7" onclick="if(validate() == false) return false;var wcall=wicketSubmitFormById('defaultajaximplmentationform6', '?wicket:interface=:1:submitbutton::IActivePageBehaviorListener:0:&wicket:ignoreIfNotActive=true', 'submitbutton' ,function() { }.bind(this),function() { }.bind(this), function() {return Wicket.$$(this)&&Wicket.$$('defaultajaximplmentationform6')}.bind(this));;; return false;">Submit</button>35: <div>You entered: <span class="message" wicket:id="message" id="message8"></span></div>36: </body>37: </html>
Notice that in the above markup on line 34 that our button's onclick event includes our prepended Javascript. Below is the Javascript we contributed by prepending in bold blue highlight:
onclick="if(validate() == false) return false;var wcall=wicketSubmitFormById('form6', '?wicket:interface=:1:submitbutton::IActivePageBehaviorListener:0:&wicket:ignoreIfNotActive=true', 'submitbutton' ,function() { }.bind(this),function() { }.bind(this), function() {return Wicket.$$(this)&&Wicket.$$('form6')}.bind(this));;; return false;"
And here's what HomePage looks like when rendered in the browser:
If you enter 2 characters or less and then click the submit button you will see the following:
When we clicked submit the button's onclick event was called where we call validate. In this case, because validation failed false was returned and the Ajax call back to the server was blocked by returning false.
When we enter 3 characters and click submit here's what we get:
Since we entered 3 characters validation succeeded and returned true so the Ajax call back to the server wasn't blocked.. On the server we assigned the value of input's model to the message's model and then we cleared out the input's model to an empty string. Then we added the input and message components to the AjaxTarget which will cause them to render in the response by replacing their markup in the DOM.
Summary
As this article has shown, Wicket's enables you to to prepend its Ajax calls to the server with your own Javascript giving you great control over your Ajax processing. What might not have been so obvious is that Wicket doesn't just limit you to prepending and validation - you can also contribute Javascript that will be called in the success handler and the failure handler as well but those are subjects for a future article.








