* One in a series of articles on Wicket Interfaces & Methods To Master *
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.
With our requirements and objectives now having been stated, lets move onto the project structure we will need in order to create a reusable Wicket component library.
Create A Library Project
As I previously stated above in Objectives And Requirements, item #3, the JQueryAutocompleteTextField needs to be hosted in its own jar file. Our goal here is to provide a reusable library. Users of this library will have to include it in their classpath in order to compile their own code and they will also need to have the Wicket jar files on their classpath so that the library can resolve its dependencies on Wicket as well.
- 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.
You should now have a project structure that looks this:
The next thing to do is to add Wicket 1.5 to our project. If you don't already have Wicket you can go to http://wicket.apache.org/ and download it. I am using version 1.5.3.
WicketJQueryIntegration also requires google-gson which you can download if you don't already have it. Add it to the project as well.
With our dependencies added to the project and accounted for we need to do one more thing and that is to insure that the libraries we require for compilation aren't packaged in the jar - it is the responsibility of the users of JQueryAutocompleteTextField to provide the dependencies on these in their classpath. If we don't do this, then we are building in a level of dependency on the exact same versions of these libraries and we don't want to do that.
Right click on the project node and select Properties and select Packaging. Uncheck the Copy Dependent Libraries option and click OK.
We now have a project suitable for creating a reusable library that doesn't impose dependencies upon its users and our project structure now looks like this:
Implementing JQueryAutocompleteTextField
We will begin implementation by creating a subclass of AbstractAjaxBehavior. In my previous article we used an implementation of AbstractAjaxBehavior that was tightly coupled to providing support for selecting states. However, because our goal is to create a reusable implementation, one that isn't tightly coupled to any particular use case, our implementation must therefore provide flexibility, allowing the user to implement their own requirements.
Create a Java class in the com.wicket.jquery.integration.autocomplete package. I named mine AbstractJQueryAutocompleAjaxBehavior. In the editor mark the class as abstract and extend the class from AbstractAjaxBehavior.
The constructor needs to take one parameter, a string whose value is a jQuery selector that identifies the target html text field element that will be used to host the jQuery UI Autocomplete component. Our class also needs a field to store this value:
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);
}
In the code above we create a HashMap and add the jQuery selector and callback url to it, the names of which as you can see match those we used in our markers. Additionally, we obtain a PackageTextTemplate resource for the autocomplete.js file which resides in the same package as this class and then we call its asString method passing it the HashMap. asString will interpolate our resource using the HashMap we provided and return the interpolated content of the resource as a String. That solves our slight dilemma quite nicely I think and with very little code.
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.
That wraps up our implementation of JQueryAutocompleteTextField but a few things remain to be done. First we need to build the jar file so that we can use JQueryAutocompleteTextField in our Wicket applications and secondly we need to test it. We will cover both beginning with building the jar file.
Building The Jar File
To build the components jar file right click the project node and select Clean And Build. Netbeans will compile the project and package the component in its own jar file.
Notice how none of the libraries we are using to build the component are in the jar file - this is because when we created the project we instructed Netbeans not to include them. Users of the component will now be able to use what ever versions of these dependencies they see fit to use.
Now all that remains is for us to test our component to make sure that it actually works so lets do that now by creating a small Wicket test application using Netbeans. Make sure you have already installed the Wicket Plugin.
Testing Our Implementation
- 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.
Netbeans will generate a complete Wicket project including the Wicket jars that the Wicket Plugin contributes. Because our component is dependent upon Wicket v1.5 we will have to replace the ones provided by the Wicket Plugin with the 1.5 versions. So remove all the contributed Wicket jars and replace them with those from the 1.5 distribution. Also, add the google-gson jar file to the project as well.
We also need to include our components jar also:
- 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.
We now want to instruct Netbeans to build our components project when we build our test project:
- 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.
Now, whenever we build out test project, Netbeans will check to see if our components project needs to be rebuilt and will do so first before building our test project.
We won't be using Wicket's markup inheritence in our project since it is quite simple so delete the following files from the com.myapp.wicket package:
- BasePage.java
- BasePage.html
- FooterPanel.java
- FooterPanel.html
- HeaderPanel.java
- HeaderPanel.html
Our test project's structure should now look like this:
Our test project will mimic what my previous article did which was to implement an Autocomplete component that allowed the user to select from a list of states that match their input.
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.