Simple Wicket ChoiceRenderer for any kind of Object
There are times in which rendering a Choice inside of a DropDown or RadioChoice component is just a matter of exposing some values stored inside the object associated to the choice item.
Maybe we can have no localization requirements or, more commonly, maybe our data has no localization issues at all.. think, for example, at a simple use case like the following:
public class Company implements java.io.Serializable {
private Integer id; //primary key
private String name; //company name
public Company(Integer id, String name ) {
this.id = id; this.name=name;
}
//other properties, plus getters & setters....
}
Now, you want to show the list of available Companies inside of a DropDownChoice:
//RendererExample.html
<select wicket:id="companySelection" />
//RendererExample.java
public class RendererExample extends WebPage {
//list of available choices (just a hard-coded example)
private List<Company> companies = Arrays.asList(
new Company(1001,"APPLE"),
new Company(1002, "MICROSOFT") );
//will hold selected value
private Company selectedCompany;
public RendererExample() {
add( new DropDownChoice( "companySelection",
new PropertyModel( this, "selectedCompany" ),
companies );
}
}
Without giving any Renderer to the DropDownChoice, we will end up with a list of Object dumps, because Wicket will use the Company ‘toString’ method to render the label of each option and its position inside the list to render the ‘value’ part of the option:
Now we have two routes to take:
1) implement the “toString()” method on the Company object to return what we want to display
2) use a Renderer
I naturally prefer the second option, primarily because I don’t like to mix display logic inside a data object;
Here we can immediately see a common pattern.. if we want to use as id/label two properties of our data object, why don’t create a a common ChoiceRenderer that we can reuse over and over on different data object ?
Here’s the basic implementation:
import org.apache.commons.beanutils.BeanUtils;
import org.apache.wicket.markup.html.form.IChoiceRenderer;
/**
* Simple property-based ChoiceRenderer.
* Use the given property names as the source to obtain the
* respective ID and LABEL strings used to render the Choice
* to the Hosting component.
*
* Example:
*
* //somewhere, in SomePersistentData.java file...
* public class SomePersistentData implements Serializable {
* private Integer id;
* private String description;
* //plus, getters and setters....
* }
*
* //extract Objects from DB
* List values = myService.findDataElements();
*
* add( new DropDownChoice( "myWicketId",
* new PropertyModel(myDataObject, "propertyName" ),
* values,
* new PropertyChoiceRenderer( "id", "description" ) ))
*
* @author Davide Alberto Molin (davide.molin@gmail.com)
*/
public class PropertyChoiceRenderer<T> implements IChoiceRenderer {
//name of the property used to extract the ID for the choice option
private String idProperty;
//name of the property used to extract the LABEL for the choice option
private String valueProperty;
public PropertyChoiceRenderer( String idProperty, String valueProperty )
{
this.idProperty = idProperty;
this.valueProperty = valueProperty;
}
public Object getDisplayValue(T object)
{
return getPropertyValue( object, valueProperty );
}
public String getIdValue(T object, int index )
{
return getPropertyValue( object, idProperty ).toString();
}
private Object getPropertyValue( T object, String property )
{
try
{
return BeanUtils.getProperty( object, property );
}
catch( Exception err )
{
//in case of exception, fall back to simple toString...
return object.toString();
}
}
}
That’s all. Put it in your code and you’ll have a reusable yet simple ChoiceRenderer:
add( new DropDownChoice( "companySelection",
new PropertyModel( this, "selectedCompany" ),
companies,
new PropertyChoiceRenderer( "id", "name" ) );
}
And that’s what you get:
Obviously, this is a basic implementation, open to modifications; You can get rid of the generic T in the code using “Object” on the IChoiceRendere import if you see no reason to have it; Again, if you want to retain the index of the item as the option value, just add another constructor that takes only the property used for the label and modify the getIdValue() in order to use the index value if the idProperty is null..


There is obviously a lot to learn about this. There were some pretty good points.
Hi,
I have a problem with getting the value of a drop down choice. I can’t get the value selected after the submit button was clicked. Below is my code…
please help me..
public class FileUploadPage extends BasePage
{
private static FileUploadField file;
private TextField text;
private static DropDownChoice dueYear;
private static DropDownChoice dueMonth;
/**
* Constructor
*/
public FileUploadPage()
{
// create a feedback panel
final Component feedback = new FeedbackPanel(“feedback”).setOutputMarkupPlaceholderTag(true);
add(feedback);
// create the form
Form form = new Form(“uploadForm”)
{
/**
* @see org.apache.wicket.markup.html.form.Form#onSubmit()
*/
@Override
protected void onSubmit()
{
// display uploaded info
//System.out.println(dueMonth.get);
info(“Text: ” + text.getModelObject());
info(“Due Month: ” + dueMonth.getModelValue());
info(“Due Month: ” + dueMonth.getInput());
info(“Due Month: ” + dueMonth.getModelObject());
FileUpload upload = file.getFileUpload();
if (upload == null)
{
info(“No file uploaded”);
}
else
{
info(“File-Name: ” + upload.getClientFileName() + ” File-Size: ” +
Bytes.bytes(upload.getSize()).toString());
}
}
};
form.setMaxSize(Bytes.megabytes(1));
add(form);
// create a textfield to demo non-file content
form.add(text = new TextField(“text”, new Model()));
IModel dueYearList = new LoadableDetachableModel() {
protected Object load()
{
return MainApplication.instance().getDueMonthDao().getAllDueYear();
}
};
dueYear = new DropDownChoice(“dueYear”, new PropertyModel(this, “dueYear”), dueYearList, DueYearRenderer.INSTANCE);
//dueYear = new DropDownChoice(“dueYear”, dueYearList, DueYearRenderer.INSTANCE);
dueYear.setRequired(true);
add(dueYear);
IModel dueMonthList = new LoadableDetachableModel() {
protected Object load()
{
return MainApplication.instance().getDueMonthDao().getAllDueMonth();
}
};
//dueMonth = new DropDownChoice(“dueMonth”, new PropertyModel(this,”dueMonth”), dueMonthList, DueMonthRenderer.INSTANCE);
dueMonth = new DropDownChoice(“dueMonth”, dueMonthList, DueMonthRenderer.INSTANCE);
dueMonth.setRequired(true);
add(dueMonth);
// create the file upload field
//form.add(file = new FileUploadField(“file”, new PropertyModel(this, “file”)));
form.add(file = new FileUploadField(“file”, new Model(“file”)));
}
/**
* @return the dueMonth
*/
public DropDownChoice getDueMonth() {
return dueMonth;
}
/**
* @param dueMonth the dueMonth to set
*/
public void setDueMonth(DropDownChoice dueMonth) {
this.dueMonth = dueMonth;
}
}
Hi Ardin
The first problem I see is that the DropDownChoices are not part of the form; they have to be if you want their values to be transmitted to the server when the form is submitted.
Your component graph has to be in sync with your HTML graph. If your select tag is “inside” your form (and it has to be), so has to be your DropDownChoice component in respect to your Form component.
Another problem I see with your code is that you don’t have a backing data model;
The ideal approach with Wicket demands that you first identify the object that has to ‘receive’ and ‘provide’ the data you are manipulating with your form; Then, you set up your interface components, binding them to your data object via Wicket Model classes.
Some components allow you to not use a backing model (but I would not advocate for it); DropDownChoices are not in this list.. they require you to set a Model when you instance a new DropDownChoice component;
The Model is, essentially, the link between your value object and the corresponding UI component; It’s a sort of a Proxy that mediates between your actual value object and the graphic component; it updates your data object when the form is submitted and read values from it when the component has to be rendered/refreshed.
So, when you setup your DropDownChoice, Wicket must know where your value object is: you have to give it a Model class wrapping your actual data object, like in this example:
//object containing the actual selection (if any)
MyDataObject data = new MyDataObject();
//Wicket will read the selected choice from the "propertyOfMyDataObject" property of "data"
//and will update "data" when the form will be submitted
DropDownChoice ddc = new DropDownChoice( "wicketId", new PropertyModel( data, "propertyOfMyDataObject" ), ..... )
In your case, you use “this” as the value object.. this is perfectly legal but the property you are referring to must be of the same object type of the objects contained into the list given to the DropDownChoice.. if you use the same UI component (as you’re doing now) as the destination/source for data you are short-circuiting it
A DropDownChoice is built passing it a list of values of a certain kind; when you make a selection and submit the form, the UI Component will update the “property” of your data object with the value contained into the selection, that is an instance of the entire object of the initial collection corresponding to the selected choice.
If you feed your DropDownChoice with a list of “DueMonth” objects, it will expect to be bound to a DueMonth property object:
public class FileUploadPage extends BasePage {
//this is my data object
//I usually suffix Data objects with "xxxData"
private DueMonth dueMonthData;
//this is the UI component, using my data
//object as the backing storage
private DropDownChoice dueMonthChoice;
....
public FileUploadPage() {
//build the form...
Form form = new Form( "uploadForm" ) {
@Override
protected void onSubmit()
{
// display uploaded info
// my data Object will be automatically up2date
//after the submit...
info("Due Month Object: " + dueMonthData );
}
}
//add the form to the page
add( form );
//dueMonthList is a List<DueMonth> instance...
//build the choice and add it to the form
DropDownChoice ddc = new DropDownChoice( "dueMonth",
new PropertyModel(this,"dueMonthData"),
dueMonthList, DueMonthRenderer.INSTANCE );
//add the choice to the form
form.add( ddc );
}
}
Let me know if this can be of any help…
Hi Davide,
i deeply appreciate your response. i have edited my code to your advice…
however, the lines:
info(“Due Year: ” + dueYearData.getDescription());
info(“Due Month: ” + dueMonthData.getDueMonth());
does not give the year and date i have selected before submitting the page.
below is my latest code:
public class FileUploadPage extends BasePage
{
private static FileUploadField file;
private TextField text;
private static DropDownChoice dueYear;
private static DropDownChoice dueMonth;
private static DueMonth dueMonthData;
private static DueYear dueYearData;
/**
* Constructor
*/
public FileUploadPage()
{
// create a feedback panel
final Component feedback = new FeedbackPanel(“feedback”).setOutputMarkupPlaceholderTag(true);
add(feedback);
// create the form
Form form = new Form(“uploadForm”)
{
/**
* @see org.apache.wicket.markup.html.form.Form#onSubmit()
*/
@Override
protected void onSubmit()
{
// display uploaded info
info(“Text: ” + text.getModelObject());
info(“Due Year: ” + dueYearData.getDescription());
info(“Due Month: ” + dueMonthData.getDueMonth());
FileUpload upload = file.getFileUpload();
if (upload == null)
{
info(“No file uploaded”);
}
else
{
info(“File-Name: ” + upload.getClientFileName() + ” File-Size: ” +
Bytes.bytes(upload.getSize()).toString());
//setResponsePage(new ConfirmationPage());
}
}
};
form.setMaxSize(Bytes.megabytes(1));
add(form);
// create a textfield to demo non-file content
form.add(text = new TextField(“text”, new Model()));
IModel dueYearList = new LoadableDetachableModel() {
protected Object load()
{
return MainApplication.instance().getDueMonthDao().getAllDueYear();
}
};
dueYearData = new DueYear(“2010″, “2010″);
dueYear = new DropDownChoice(“dueYear”, new PropertyModel(this, “dueYearData”), dueYearList, DueYearRenderer.INSTANCE);
dueYear.setRequired(true);
add(dueYear);
IModel dueMonthList = new LoadableDetachableModel() {
protected Object load()
{
return MainApplication.instance().getDueMonthDao().getAllDueMonth();
}
};
dueMonthData = new DueMonth(“01″, “JANUARY”);
dueMonth = new DropDownChoice(“dueMonth”, new PropertyModel(this,”dueMonthData”), dueMonthList, DueMonthRenderer.INSTANCE);
dueMonth.setRequired(true);
add(dueMonth);
// create the file upload field
//form.add(file = new FileUploadField(“file”, new PropertyModel(this, “file”)));
form.add(file = new FileUploadField(“file”, new Model(“file”)));
}
}
Hi Davide,
i have already figured out the error. i have included the ddc to the form…
if i am short-circuiting, can you guide me on the proper way…
cheers,
Ardin
Hi Ardin,
I apologize for the delay
Good job figuring out the error..
With “short-circuiting” I was meaning the following:
A Wicket Model is a wrapper around the actual ‘container’ of your data (that is, your data object, usually); Theory tells us that a Model has to be bound to an object and that object becomes the source of data for your component and ultimately, is set to the value you input inside your UI component.
That said, when you were declaring a field such as the following:
file = new FileUploadField( “file”, new PropertyModel( this, “file” ) );
you were actually short-circuiting.. in fact you were telling Wicket:
“dear Wicket, use the “file” property of my class (an instance of FileUploadModel) as the backing object for the FileUploadModel component”! can you see it ? you were actually creating a sort of a “loop”, telling Wicket to feed the component with itself
Now you’re doing things in the correct way for the DropDownChoices (you feed the models with two instances of your actual data objects); FileUpload still has some inconsistencies;
You can create a FileUploadField in 2 distinct ways:
1) file = new FileUploadField( “file” );
If you does not give him a backing object, it will be created internally by the component (this is specific to the FileUploadComponent! it’s not a common Wicket behavior)
2) file = new FileUploadField( “file”, “myFileUploadProperty” );
where you have: private FileUpload myFileUploadProperty; in your class.
Personally I usually opt for solution nr.1; This is probably the only case when I do not have a data object at hand when I instance the component.
In the overall your actual approach is headed in the right direction; just leave out the model from the FileUploadComponent and it all will be good.
Just one question: is there a particular reason why you are declaring your fields and your components as static ? keep in mind that doing this you are opening your code to potential concurrency issues, because your data and your components will exist in only one instance, shared among all the instances of that page..
Cheers
Davide
Hi Davide,
Thanks again.
Actually, i have copied this code from the internet and modified to suit my need. The sample code declares the components as static, so i didnt bother to modify since i dont know the effect of being static. Now that i know, i have revised the code.
My problem now is everytime the Upload button is clicked and an error occured, the FileUploadField fields were initialized. How can this be prevented so that the user will not always click on the Browse button.
Below is the revised code:
public class FileUploadPage extends BasePage
{
private FileUploadField araFile;
private FileUploadField erfFile;
private TextField amount;
private DropDownChoice dueYear;
private DropDownChoice dueMonth;
private DueMonth dueMonthData;
private DueYear dueYearData;
private PageParameters pageParams;
/**
* Constructor
*/
public FileUploadPage()
{
// create a feedback panel
final Component feedback = new FeedbackPanel(“feedback”).setOutputMarkupPlaceholderTag(true);
add(feedback);
// create the form
Form form = new Form(“uploadForm”)
{
@Override
protected void onSubmit()
{
// display uploaded info
FileUpload uploadErf = erfFile.getFileUpload();
FileUpload uploadAra = araFile.getFileUpload();
if (uploadErf == null)
{
info(“No ERF File to upload!!!”);
}
else
{
if (uploadErf.getClientFileName().endsWith(“.csv”))
{
if (uploadAra != null && !uploadAra.getClientFileName().endsWith(“.xls”))
{
info(“Invalid Ara File!”);
}
else
{
LoginSession session = (LoginSession) this.getSession();
Erf erf = new Erf();
erf.setRemittingAgency(“1000008012″);
erf.setDueMonth(dueYearData.getDueYear() + dueMonthData.getDueMonth());
erf.setAmount(Double.valueOf(amount.getValue()));
erf.setErfFile(uploadErf.getClientFileName());
erf.setErfFileSize(uploadErf.getBytes());
if (uploadAra != null)
{
erf.setAraFile(uploadAra.getClientFileName());
erf.setAraFileSize(uploadAra.getBytes());
}
else
{
erf.setAraFile(“”);
erf.setAraFileSize(null);
}
erf.setAaoUserId(session.getAao().getUserId());
try
{
MainApplication.instance().getErfDao().insertErf(erf);
pageParams = new PageParameters();
pageParams.put(“confirmationNbr”, erf.getConfirmationNbr());
setResponsePage(new ConfirmationPage(pageParams));
}
catch (Exception e)
{
info(e.getMessage());
}
}
}
else
{
this.info(“Invalid ERF file!”);
}
}
}
};
form.setMaxSize(Bytes.megabytes(1));
add(form);
// create a textfield to demo non-file content
amount = new TextField(“amount”, new Model());
amount.setRequired(true);
form.add(amount);
IModel dueYearList = new LoadableDetachableModel() {
protected Object load()
{
return MainApplication.instance().getDueMonthDao().getAllDueYear();
}
};
dueYearData = new DueYear(“2010″, “2010″);
dueYear = new DropDownChoice(“dueYear”, new PropertyModel(this, “dueYearData”), dueYearList, DueYearRenderer.INSTANCE);
dueYear.setRequired(true);
form.add(dueYear);
IModel dueMonthList = new LoadableDetachableModel() {
protected Object load()
{
return MainApplication.instance().getDueMonthDao().getAllDueMonth();
}
};
dueMonthData = new DueMonth(“01″, “JANUARY”);
dueMonth = new DropDownChoice(“dueMonth”, new PropertyModel(this, “dueMonthData”), dueMonthList, DueMonthRenderer.INSTANCE);
dueMonth.setRequired(true);
form.add(dueMonth);
// create the file upload field
form.add(erfFile = new FileUploadField(“erfFile”, new Model(“file”)));
form.add(araFile = new FileUploadField(“araFile”, new Model(“file”)));
}
}
Hi Ardin,
I don’t think there’s a solution to your problem;
When a file upload fails (there can be many reasons: file too large, invalid content type..) the MultipartServletWebRequest can’t be created and thus the form field values can not be read from the HttpServletRequest.. this result in the cleared fields.
This kind of problem was reported back in 2007 in various Wicket forums and is recorded as issue WICKET-406 in the developers Wicket mailing list;
The fact that it’s still present probably means that there are no practical solutions at hand at the moment.
Hi Davide,
Can you help me with my problem with my code below. the line
onViewIndividualPayor((IndividualPayor) getModelObject()); always return an error because the getModelObject() return null. I have previously used this approached in another page with no error. How come it has error on this page.
public class ListIndividualPayorsPage extends BasePage
{
private static final IModel TITLE = new ResourceModel(“listIndividualPayors.title”);
public ListIndividualPayorsPage(PageParameters params)
{
setPageTitle(TITLE);
final String searchString = params.getString(“searchString”);
IModel listModel = new LoadableDetachableModel()
{
protected Object load() {
return MainApplication.instance().getIndividualPayorDao().find(searchString);
}
};
add(new IndividualPayorListView(“individualPayorList”, listModel)
{
//protected void populateItem(ListItem item, final IndividualPayor ip)
protected void populateItem(ListItem item, IndividualPayor ip)
{
/*
* This is where we populate every row of the INDIVIDUAL PAYOR table
* with its components
*/
item.add(new Label(“bpNo”, ip.getBpNo()));
item.add(new Label(“lastName”, ip.getLastName()));
item.add(new Label(“firstName”, ip.getFirstName()));
item.add(new Label(“middleName”, ip.getMiddleName()));
Link viewLink = new Link(“view”, item.getModel()) {
public void onClick() {
onViewIndividualPayor((IndividualPayor) getModelObject());
}
};
item.add(new Link(“edit”, item.getModel())
{
public void onClick()
{
/*
* Translate wicket model into the underlying object so
* that listener code does not need to deal with the
* model
*/
onEditIndividualPayor((IndividualPayor) getModelObject());
}
});
item.add(viewLink);
//item.add(editLink);
}
});
}
protected void onViewIndividualPayor(IndividualPayor ip)
{
ip.setRemarks(“view”);
setResponsePage(new EditIndividualPayorPage(this,ip));
}
protected void onEditIndividualPayor(IndividualPayor ip)
{
ip.setRemarks(“edit”);
setResponsePage(new EditIndividualPayorPage(this,ip));
}
}
Hi Ardin, give me 24 hours to get back to my home (I’m abroad in this moment) and I’ll see what I can do to help you
regards Davide
Just a quick reply, before delving more deeply into your code… It’s not clear to me what the IndividualPayorListView class is made of.. it’s probably an extension of ListView but something does not seems right to me.. the Wicket ListView class has no “populateItem” method with two arguments! the only available populateItem method has only one argument (a ListItem instance); the model object contained inside the ListItem instance is the data model representing your row data.. can you let me view your implementation for the IndividualPayorListView class ?
Hi Davide,
Thanks for your reply. your advice helps a lot.
Regards,
Ardin
You’ve done it once again. Superb article!
Many thanx Celina!
I’m glad you liked it!
Hi Davide,
Thanks for the quick reply.
Here the ndividualPayorListView class:
public abstract class IndividualPayorListView extends ListView{
/** Creates a new instance of UserListView */
public IndividualPayorListView(String id, IModel model) {
super(id,model);
}
protected IModel getListItemModel(IModel listViewModel, int index)
{
/*
* Instead of using default model we wrap each employee object with a
* detachable model to save on session state and keep it up to date
*/
//List list = (List) listViewModel.getObject(this);
List list = (List) listViewModel.getObject();
IndividualPayor ip = (IndividualPayor) list.get(index);
return new IndividualPayorModel(ip);
}
protected ListItem newItem(int index)
{
/*
* Override default ListItem construction to create ListItems that add
* even/odd class attributes
*/
return new OddEvenListItem(index, getListItemModel(getModel(), index));
}
protected void populateItem(ListItem item)
{
/*
* Forward to a more typesafe and easier to use populateItem call
*/
IndividualPayor ip = (IndividualPayor) item.getModelObject();
populateItem(item, ip);
}
/**
* Convinience callback to populate rows of the listview.
*
* @param item
* ListItem to be populated
* @param emp
* Employee object this ListItem represents
*
* @see ListView#populateItem(ListItem)
*/
protected abstract void populateItem(ListItem item, IndividualPayor ip);
}
Hi Ardin,
It seems that the problem lies in your custom implementation of the ListView class… I tried implementing your example using a normal ListView class and the code works perfectly as expected.. Probably you are missing something important reimplementing your ListItem node (I see that you’re using a custom OddEvenListItem class..); If your necessity is to add even/odd presentation attributes, maybe you should consider using a simple AttributerAppender, avoiding to override the ListItem implementation class.. Please, check your implementation of ListView and ListItem.. the problem is probably lying there..
Hi Davide,
You’re an angel. i used the regular implementation of the ListView class. i experimented on how to use the AttributeAppender to avoid custom implementation of the ListView class and it works well now.
i figured to add in the item object:
–
if (item.getIndex() % 2 == 0){
item.add(new AttributeAppender(“class”, new Model(“odd”), “odd”));
}
else {item.add(new AttributeAppender(“class”, new Model(“even”), “odd”));}
–
it is much simpler now, the two addl class (1) custom ListView class and the (2) OddEvenListItem class were eliminated.
Your great. thank you very much.
Till next time,
Ardin
That’s great Ardin, I’m happy you solved you issue

Good job figuring out how to use AttributeAppender (cool, isn’t it ?)
Happy coding!
Davide
Yes Davide, it’s cool and i’m starting to like wicket. Hope you will help me again when i encountered one.