Saturday, May 7, 2011

Creating Custom Ajax Behavior on Wicket 1.4

Today we're creating a calendar picker behavior to add to some wicket components. Sample code for this post can be downloaded here.

In order to run the sample code you need a servlet 3 container like Apache Tomcat 7, lets get started.

Wicket Application Configuration.

Since servlet 3, the web.xml file isn't needed anymore. So how we configure the wicket filter? we could create a web.xml file and define it there but since we want to do things in the new way, we can define a subclass of the wicket filter and add the annotation defined on servlet 3 spec.

 @WebFilter(urlPatterns = "/*", initParams=@WebInitParam(name="applicationClassName", value="com.juancavallotti.wicketcustomcomponents.DemoApplication"))
public class CustomWicketFilter extends WicketFilter {
}

Wicket Application and Home Page.

The wicket application and home page are pretty standard so I will not put them here, you can take a look at them directly on the sample code package.

JQuery and JQueryUI.

For this example we've used JQueryUI. As you should know, you can add resources on the java packages, so that's what we did, there are some more elegant ways to do this, specially if you're building your own component library, but for this example we've added the files from the JQueryUI bundle on the classpath and within our home page we loaded the necessary resources:
 add(new StyleSheetReference("jqueryuitheme", HomePage.class, "jquery-ui-1.8.12.custom.css"));
add(new JavaScriptReference("jqueryjs", HomePage.class, "jquery-1.5.1.min.js"));
add(new JavaScriptReference("jqueryuijs", HomePage.class, "jquery-ui-1.8.12.custom.min.js"));
We have previously copied those files into /src/main/resources/{path to package} and wicket resource system will take care to set the right url for us on the html file.

At this point we have what we need to start creating a fun ajax behavior to enable text fields or panels to have a date picker.

Coding a custom AJAX Behavior

The common way to bootstrap JQueryUI components (and generally most of JQuery components) is to call a method with the name of the component on an HTML element with some id, like the following:
 $(document).ready(function() {
$("#${componentId}").datepicker(${options});
});
So we'll start with that, and save it into a file called calendarTemplate.js on the resources package in which we'll create our custom behavior, in my case: /src/main/resources/com/juancavallotti/components.

Now we create one class called JqueryUIDatePickerBehavior, that class will extend AbstractDefaultAjaxBehavior, and it looks like the following:
 package com.juancavallotti.components;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import org.apache.wicket.Component;
import org.apache.wicket.ajax.AbstractDefaultAjaxBehavior;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.model.Model;
import org.apache.wicket.util.template.TextTemplateHeaderContributor;
/**
*
* @author juancavallotti
*/
public class JqueryUIDatePickerBehavior extends AbstractDefaultAjaxBehavior {
private JqueryUIDatePickerSettings settings;
public JqueryUIDatePickerBehavior() {
}
public JqueryUIDatePickerBehavior(JqueryUIDatePickerSettings settings) {
this.settings = settings;
}
@Override
public void onConfigure(Component component) {
component.setOutputMarkupId(true);
String id = component.getMarkupId(true);
Map<String, Object> params = new HashMap<String, Object>();
params.put("componentId", id);
params.put("options", buildSettings());
component.add(TextTemplateHeaderContributor.forJavaScript(JqueryUIDatePickerBehavior.class, "calendarTemplate.js", new Model((Serializable) params)));
}
@Override
protected void respond(AjaxRequestTarget target) {
}
private String buildSettings() {
if (settings == null) {
return "";
}
return settings.toString();
}
}
We override the onConfigure method and instruct the target component for this behavior to output the markup id, then we get that id and populate a HashMap with it (and some configuration that we'll soon explain). Then we call TextTemplateHeaderContributor class and we load our javascript template. The resulting code will be placed on the header section of the page with it's parameters replaced from the values on the hash map.

Finally, we know that the DatePicker component has a lot of options (you can check them out on the documentation page), so It's a good idea to be able to set those options within java code, so we create a class named JqueryUIDatePickerSettings and add some of those options. Please note that this is only example code intended to give you some understanding of how this works, a production implementation should be far more robust than this one.
 public class JqueryUIDatePickerSettings {
private String altField;
private String buttonText;
private String dateFormat;
public JqueryUIDatePickerSettings setTargetComponent(Component component) {
String markupId = component.getMarkupId(true);
altField = "#" + markupId;
return this;
}
public String getButtonText() {
return buttonText;
}
public JqueryUIDatePickerSettings setButtonText(String buttonText) {
this.buttonText = buttonText;
return this;
}
public String getDateFormat() {
return dateFormat;
}
public JqueryUIDatePickerSettings setDateFormat(String strDateFormat) {
this.dateFormat = strDateFormat;
return this;
}
@Override
public String toString() {
return "{"
+ "altField: '" + altField + "',"
+ "buttonText: '" + buttonText + "' , showOn: 'button',"
+ "dateFormat: '"+dateFormat + "'"
+ "}";
}
}
So now we have all the puzzle pieces in place but one: How we add the date picker behavior to some component? Very easy!

Let's take a look at our full home page code and HTML:
 public class HomePage extends WebPage {

private TextField<Date> date;

public HomePage() {

//BOOTSTRAP THE SETTINGS AND A TARGET COMPONENT
date = new TextField("datePickerTarget");
date.setOutputMarkupId(true);
JqueryUIDatePickerSettings settings = new JqueryUIDatePickerSettings();
settings.setTargetComponent(date).setButtonText("Pick Date!").setDateFormat("dd/mm/y");


add(new StyleSheetReference("jqueryuitheme", HomePage.class, "jquery-ui-1.8.12.custom.css"));
add(new JavaScriptReference("jqueryjs", HomePage.class, "jquery-1.5.1.min.js"));
add(new JavaScriptReference("jqueryuijs", HomePage.class, "jquery-ui-1.8.12.custom.min.js"));

//EXAMPLE USE OF THE BEHAVIOR
add(new TextField("miDatePicker").add(new JqueryUIDatePickerBehavior(settings)));
add(new WebMarkupContainer("inlineCalendar").add(new JqueryUIDatePickerBehavior()));
add(date);
}
}
And this is the HTML
   <body>
<h1>jCavallotti JQuery UI custom Components</h1>
<div wicket:id="inlineCalendar"></div>
<div><input type="text" wicket:id="miDatePicker" /></div>
<div><input type="text" wicket:id="datePickerTarget" /></div>
</body>
So we can finally see how things are working:


Conclusion
Building a component library in wicket is really easy and powerful, but we should not forget that there's already a lot of stuff out there ready for us to just use it, specially on wicket stuff maven repository. Nevertheless if what we find doesn't fit our needs we can still build a powerful component library.

1 comment: