Wednesday, June 20, 2007

An Ajax-enabled radio group for Wicket 1.2.x

In Wicket 1.2.x there is an AjaxCheckBox, AjaxSubmitButton, but not an AjaxRadioGroup or AjaxRadioChoice. There are questions on the Wicket-User mailing list, such as this one, but no solutions that are spelled out. I created AjaxRadioGroupPanel as a result. Perhaps this can be done without a panel by generating the markup like RadioChoice does, but this seems to work fine.

Sample usage


private enum SampleEnum
{
SAMPLEVALUE1, SAMPLEVALUE2
};
final List sampleEnums = Arrays
.asList(new SampleEnum[]
{ SampleEnum.SAMPLEVALUE1, SampleEnum.SAMPLEVALUE2 });

private SampleEnum sampleEnumValue = SampleEnum.SAMPLEVALUE1;
...
AjaxRadioGroupPanel p = new AjaxRadioGroupPanel("radiochoicepanel", form, sampleEnums, new PropertyModel(Tester.this, "sampleEnumValue"));
add(p);


AjaxRadioGroupPanel.java


package com.my;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import wicket.Component;
import wicket.Page;
import wicket.ajax.AjaxRequestTarget;
import wicket.ajax.form.AjaxFormSubmitBehavior;
import wicket.behavior.HeaderContributor;
import wicket.markup.html.basic.Label;
import wicket.markup.html.form.Form;
import wicket.markup.html.form.Radio;
import wicket.markup.html.form.RadioGroup;
import wicket.markup.html.list.ListItem;
import wicket.markup.html.list.ListView;
import wicket.markup.html.panel.Panel;
import wicket.model.IModel;
import wicket.model.Model;
import wicket.version.undo.Change;

/**
* A panel containing a radio group that updates its model using Ajax.
*
* @author Julian
*
*/
public class AjaxRadioGroupPanel extends Panel
{
private String suffix = "<br/>\n";
private List<Component> ajaxTargets;

public AjaxRadioGroupPanel(String id, final Form form, List choices,
IModel model)
{
super(id);

final RadioGroup rg = new RadioGroup("radiochoicegroup", model);
rg.setRenderBodyOnly(false);

rg.add(new ListView("radiochoices", choices)
{
@Override
protected void populateItem(ListItem item)
{
final Serializable radioitem = (Serializable) item
.getModelObject();
final Radio rc = new Radio("radiochoice", new Model(radioitem));
// Must use AjaxFormSubmitBehavior for this type of component
rc.add(new AjaxFormSubmitBehavior(form, "onclick")
{
protected void onSubmit(AjaxRequestTarget target)
{
for (Component c : ajaxTargets)
{
target.addComponent(c);
}
}
});
// Add label for radio button
String label = radioitem.toString();
String display = label;
if (localizeDisplayValues())
{
display = getLocalizer().getString(label, this, label);
}
item.add(rc);
item.add(new Label("radiolabel", display));
item.add(new Label("suffix", getSuffix()).setRenderBodyOnly(
true).setEscapeModelStrings(false));
}
});
add(rg);
}

/**
* Borrowed from RadioChoice
*
* @return Separator to use between radio options
*/
public final String getSuffix()
{
return suffix;
}

/**
* Borrowed from RadioChoice
*
* @param suffix
* Separator to use between radio options
*/
public final void setSuffix(String suffix)
{
// Tell the page that this component's suffix was changed
final Page page = findPage();
if (page != null)
{
addStateChange(new SuffixChange(this.suffix));
}

this.suffix = suffix;
}

/**
* Borrowed from RadioChoice suffix change record.
*/
private class SuffixChange extends Change
{
private static final long serialVersionUID = 3344L;

final String prevSuffix;

SuffixChange(String prevSuffix)
{
this.prevSuffix = prevSuffix;
}

public void undo()
{
setSuffix(prevSuffix);
}

public String toString()
{
return "SuffixChange[component: " + getPath() + ", suffix: "
+ prevSuffix + "]";
}
}

/**
* Borrowed from AbstractChoice
*
* Override this method if you want to localize the display values of the
* generated options. By default false is returned so that the display
* values of options are not tested if they have a i18n key.
*
* @return true If you want to localize the display values, default == false
*/
protected boolean localizeDisplayValues()
{
return false;
}

/**
* @param c A component to update when a radio choice is clicked
*/
public void addAjaxTarget(Component c)
{
if (ajaxTargets == null)
{
ajaxTargets = new ArrayList<Component>();
}
ajaxTargets.add(c);
}
}


AjaxRadioGroupPanel.html


<html xmlns:wicket="xmlns:wicket">
<body>
<wicket:panel>
<div class="radiogrouppanel" wicket:id="radiochoicegroup">
<span wicket:id="radiochoices">
<input type="radio" wicket:id="radiochoice"/>
<span wicket:id="radiolabel">Radio Label</span>
<span wicket:id="suffix">Suffix</span>
</span>
</div>
</wicket:panel>
</body>
</html>

An alternative to AjaxFormSubmitBehavior was suggested in this post and it works. Thanks to Alex for the hint:

AjaxRadioGroupPanel.java


...
final RadioGroup rg = new RadioGroup("radiochoicegroup", model);
rg.setRenderBodyOnly(false);
rg.setOutputMarkupId(true);
...
final Radio rc = new Radio("radiochoice", new Model(radioitem));
setupAjaxRadio(form, rg, rc);
// Add label for radio button
String label = radioitem.toString();
...
private void setupAjaxRadio(final Form form, final RadioGroup radioGroup,
final Radio radio)
{
radioGroup.add(radio.add(new AjaxEventBehavior("onchange")
{
protected void onEvent(AjaxRequestTarget target)
{
radioGroup.processInput();
target.addComponent(form);
for (Component c : ajaxTargets)
{
target.addComponent(c);
}
}

protected CharSequence getEventHandler()
{
return getCallbackScript(new AppendingStringBuffer(
"wicketAjaxPost('").append(getCallbackUrl()).append(
"', wicketSerialize(document.getElementById('"
+ radio.getMarkupId() + "'))"), null, null);
}
}));
}

1 comment:

coolmind said...
This comment has been removed by the author.