So far I’ve converted a couple of Struts actions & tiles to Wicket panels and I have to say, I’m starting to love Wicket more and more. Okay, on the Java side of things it leads to somewhat of a code explosion but I’d rather have more Java code than keeping JSP’s, Javascript, XML config files, TLD’s, ActionForms and Actions all in sync – manually. And compared to Struts or any other action based framework you can get real independent components without any hassle.
Wicket’s documentation is still lacking but with the help of the examples, the Wiki and the excellent mailinglist this shouldn’t be a long-term problem.
Anyway enough feathers and flowers, in my Struts app I had a tile with a Calendar and a tile with two tables displaying some data of the selected month. The Calendar tile fired an Ajax request when a previous or next month was clicked and the two tables updated accordingly.
In Wicket terms this means having a Calendar panel (CalendarPanel) and another Panel (TimesheetPanel) displaying the data for that month. To get the basics on how Ajax works with Wicket there are enough Ajax examples to get you started so let’s skip the very very basics.
First of all two links are added to CalendarPanel:
[code lang="java"]
AjaxLink previousMonthLink = new ChangeMonthLink("previousMonthLink", -1);
previousMonthLink.add(new Image("previousMonthImg", new ResourceReference(CalendarPanel.class, "arrow_left.gif")));
add(previousMonthLink);
AjaxLink nextMonthLink = new ChangeMonthLink("nextMonthLink", 1);
nextMonthLink.add(new Image("nextMonthImg", new ResourceReference(CalendarPanel.class, "arrow_right.gif")));
add(nextMonthLink);
[/code]
And the markup:
[code lang="html"]


[/code]
To control AjaxLink’s behaviour there’s an ChangeMonthLink inner class extending AjaxLink:
[code lang="java"]
private class ChangeMonthLink extends AjaxLink
{
private int monthChange;
public ChangeMonthLink(String id, int monthChange)
{
super(id);
this.monthChange = monthChange;
}
@Override
public void onClick(AjaxRequestTarget target)
{
EhourWebSession session = (EhourWebSession)this.getSession();
Calendar month = session.getNavCalendar();
month.add(Calendar.MONTH, monthChange);
month.set(Calendar.DAY_OF_MONTH, 1);
session.setNavCalendar(month);
((BasePage)getPage()).ajaxRequestReceived(target,
CommonStaticData.AJAX_CALENDARPANEL_MONTH_CHANGE);
}
@Override
protected IAjaxCallDecorator getAjaxCallDecorator()
{
return new LoadingSpinnerDecorator();
}
}
[/code]
The onClick method is invoked when the XmlHttpRequest lands back on the server. Since any parameters you passed when constructing are still available there’s no need to parse the request (or ActionForm for that matter).
Within the onClick method of the Calendar panel only some date manipulation is done. Deciding which Panel should be updated in the client’s browser is delegated to the Page containing the Calendar. If you want re-usable components you don’t want to make references from one component to the other when they’re on the same level in the Page hierarchy. Components could re-appear in another context when referenced panels aren’t available.
All my pages extend from BasePage so it’s a safe cast to BasePage. However the extending Page doesn’t know which Panel fired the Ajax request hence the need to supply an event type, AJAX_CALENDARPANEL_MONTH_CHANGE.
With the Calendar panel setup the only code left to do is making the Page refresh the relevant panels:
[code lang="java"]
public class OverviewPage extends BasePage
{
private CalendarPanel calendarPanel;
private WebMarkupContainer contentContainer;
public OverviewPage(PageParameters params)
{
..
calendarPanel = new CalendarPanel("sidePanel", user);
calendarPanel.setOutputMarkupId(true);
add(calendarPanel);
contentContainer = new TimesheetPanel("contentContainer", user, session.getNavCalendar());
contentContainer.setOutputMarkupId(true);
}
@Override
protected void ajaxRequestReceived(AjaxRequestTarget target, int eventType)
{
CalendarPanel newCalendarPanel = new CalendarPanel("sidePanel", user);
newCalendarPanel.setOutputMarkupId(true);
calendarPanel.replaceWith(newCalendarPanel);
calendarPanel = newCalendarPanel;
target.addComponent(newCalendarPanel);
TimesheetPanel panel = new TimesheetPanel("contentContainer", user, session.getNavCalendar());
panel.setOutputMarkupId(true);
contentContainer.replaceWith(panel);
contentContainer = panel;
target.addComponent(panel);
}
}
[/code]
As you can see replacing a Panel is easy. The calendarPanel was already added in in the constructor. In the ajaxRequestReceived method it’s replaced with a new one. To have it refresh call addComponent on the AjaxRequestTarget with the new panel as an argument and you’re all set! It’s that easy, no need to do anything in the TimesheetPanel as there’s no difference between constructing it from a Page or reconstructing it for an Ajax request.
Just make sure the same ID (sidePanel & contentContainer) is used for the replacement panel as was used in the original construction since you’re replacing a component in the Page’s hierarchy.
Two things are left. You have to call setOutputMarkupId(true) on components you want to have ajaxified. By setting this to true Wicket generates a unique ID to locate the Panel in the HTML DOM.
The other thing is the AjaxCallDecorator as seen in the AjaxLink code fragment. With an AjaxCallDecorator you can decorate the Javascript onClick event before and after the request was send. I use it to display a loading message while the request is being processed:
[code lang="java"]
public class LoadingSpinnerDecorator implements IAjaxCallDecorator
{
public CharSequence decorateOnFailureScript(CharSequence script)
{
return "document.getElementById('LoadingSpinner').style.visibility = 'hidden';" + script;
}
public CharSequence decorateOnSuccessScript(CharSequence script)
{
return "document.getElementById('LoadingSpinner').style.visibility = 'hidden';" + script;
}
public CharSequence decorateScript(CharSequence script)
{
return "document.getElementById('LoadingSpinner').style.visibility = 'visible';" + script;
}
}
[/code]
That’s it! If I compare this to my Javascript/Action/ActionForm/Tiles/JSP/JSTL/XML combo you might understand why my love for Wicket is growing
Entries (RSS)
The indicator can maybe even simpler… look at IAjaxIndicatorAware
your component/page or behavior could implement that and return “LoadingSpinner”
another trick that matej showed me is that you can just do this all in JS if you already have a JS file:
Wicket.Ajax.registerPreCallHandler(onStartAjax);
Wicket.Ajax.registerPostCallHandler(onStopAjax);
Wicket.Ajax.registerFailureHandler(onStopAjax);
then just hide and show your indicator in onStartAjax() and onStopAjax() and every single AJAX request on your site will magically spin that little wheel.
Maybe we should make a wicket interface to this functionality so you can just name your ajax indicator’s id for your whole app in one shot?
That Wicket.Ajax looks interesting ! So there is a global Wicket object in Javascript? Didn’t know that, is there doc/javadoc somewhere available?
Ah found the wicket-ajax.js that’s included. Interesting, thx !
Thanks for the article about Replacing Panels with Wicket and Ajax, it was real help.
Any ideas on how i would go about approaching REMOVING Panels with Wicket and Ajax? I am kinda stuck on this as my idea is to replace with an empty Panel. Not sure if there is a cleaner or better way
Thanks,
Blake
For removing panels I did the same trick as you did, replacing it with an empty Component/WebMarkupContainer, and I think that’s the only possibility. The Panel is already part of the Wicket hierarchy and for what I know you can’t modify the hierarchy if you hardcoded the id in the html.
Well, another possibility would be to just hide it. setVisibility(false) (could be different method, don’t have the api at hand)
Hmm, I have tried going that route, yet it does not seem to produce favorable results. My current workaround is to setResponsePage() back to itself so the page will refresh and the panel will be removed.
My problem occurs when click on an AjaySubmitLink, the current Panel that the AjaxSubmitLink was clicked on does not disappear, therefore when trying to replace the current Panel with a new empty Panel, the page does not respond with the replaced panel.
Greetings!
I have a questions this technique to refresh a panel, can be used to any component? Because im trying to refresh a panel that just have a wicket Image, and the Image is not refreshed:
CODE:
public void onClick(AjaxRequestTarget ajaxRequestTarget) {
Image newMainPhoto = new MyImage(“mainPhoto”, fotoByteArray, MAIN_PHOTO_SIZE);
MainPhotoPanel newMainPhotoPanel= new MainPhotoPanel (“mainPhotoPanel”, newMainPhoto);
newMainPhotoPanel.setOutputMarkupId(true);
mainPhotoPanel.replaceWith(newMainPhotoPanel);
mainPhotoPanel= newMainPhotoPanel;
ajaxRequestTarget.addComponent(mainPhotoPanel);
}
Thanx alot for the help
The nice thing is javascript compatibility issues are resolved by wicket. That saves a lot of time.
[...] Replacing Panels with Wicket and AJAX (et beaucoup d’autres) chez “eHour development blog” [...]
For Google, rather then using an empty WebmarkupContainer you can just use org.apache.wicket.markup.html.panel.EmptyPanel.
Works like a champ.