Home > Open Source > Date DropDownChoice Component Apache Wicket

Date DropDownChoice Component Apache Wicket

Print Friendly Print Get a PDF version of this webpage PDF

Apache Wicket is yet another Java Web Development framework. But the beauty of Wicket is it provides clear separation of concerns. It doesn’t mix markup and java and saves you from adapting to additional expression language. Wicket templates are simple HTML files with only additional wicket attribute wicket:id. This makes it easy for the Java developers to work on the prototypes created by web designers. If you know core Java, its absolutely no problem adapting to Wicket programming model. Wicket comes with a nice quickstart guide and plenty of examples for using different components.

I just finished a project using Wicket. There are plenty of components available which we can just reuse. But in this project, we had a requirement to display date in Date, Month, Year drop down format instead of using simple text field with datepicker. I checked the wicket-datetime library, but sigh…the component was not available. I googled a bit and found the nice implementation by Mystic Coders. A small TODO thing in this piece of code is that the date drop down always show 31 days irrespective of the month and year. So I did some minor enhancement to allow population of date drop down based on Month and Year(Leap year consideration).

In the DateChooser.java, first of all change the getDays() function to return days based on month and year.
Secondly, we need to add the AjaxFormComponentUpdatingBehavior to month and year DDC. Make sure you set the output markup id for dayDDC to true.

package nl.jteam.common.wicket.component;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;

import org.apache.wicket.WicketRuntimeException;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior;
import org.apache.wicket.markup.html.form.DropDownChoice;
import org.apache.wicket.markup.html.form.FormComponent;
import org.apache.wicket.markup.html.form.IChoiceRenderer;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.Model;

/**
 * DateChooser
 * <p/>
 * Created by: Andrew Lombardi Copyright 2006 Mystic Coders, LLC
 * <p/>
 * With helpful contributions from: <a
 * href="http://www.systemmobile.com">mr_smith</a> <a
 * href="http://www.almaw.com">AlMaw</a> and ivaynberg
 * <p>
 * Aparna: Overridden convertInput() method and added logic to get days based on
 * month and year.
 * </p>
 *
 * @author Aparna Chaudhary
 */
public class DateChooser extends FormComponent<Date> {

/** The day ddc. */
private DropDownChoice<Integer> dayDDC;

/** The month ddc. */
private DropDownChoice<Integer> monthDDC;

/** The year ddc. */
private DropDownChoice<Integer> yearDDC;

/**
* Instantiates a new date chooser.
*
* @param id the id
* @param model the model
*/
public DateChooser(final String id, IModel<Date> model) {
super(id);

if (model == null) {
model = new Model<Date>(new Date());
} else if (model.getObject() == null) {
model.setObject(new Date());
} else if (!(model.getObject() instanceof Date)) {
throw new WicketRuntimeException("DateChooser [" + getPath() + "] contains an invalid model object, must be an object of type java.util.Date");
}

setModel(model);

monthDDC = new DropDownChoice<Integer>("month", new DateModel(model, Calendar.MONTH), getMonths(), new IChoiceRenderer<Integer>() {

public Object getDisplayValue(Integer object) {
SimpleDateFormat format = new SimpleDateFormat("MMM");

Calendar cal = Calendar.getInstance();
cal.set(Calendar.MONTH, object.intValue());

return format.format(cal.getTime());
}

public String getIdValue(Integer integer, int index) {
return String.valueOf(index);
}
});
monthDDC.add(new AjaxFormComponentUpdatingBehavior("onchange") {
protected void onUpdate(AjaxRequestTarget target) {
// change the days dropdown when month changes
dayDDC.setChoices(getDays());
target.addComponent(dayDDC);
}
});
add(monthDDC);

yearDDC = new DropDownChoice<Integer>("year", new DateModel(model, Calendar.YEAR), getYears());
add(yearDDC);
yearDDC.add(new AjaxFormComponentUpdatingBehavior("onchange") {
protected void onUpdate(AjaxRequestTarget target) {
// change the days dropdown when year changes
dayDDC.setChoices(getDays());
target.addComponent(dayDDC);
}
});

dayDDC = new DropDownChoice<Integer>("day", new DateModel(model, Calendar.DAY_OF_MONTH), getDays());
dayDDC.setOutputMarkupId(true);
add(dayDDC);
}

/**
* The Class DateModel.
*/
private class DateModel extends Model<Integer> {

/** The date model. */
private final IModel<Date> dateModel;

/** The calendar field. */
private final int calendarField;

/**
* Instantiates a new date model.
*
* @param dateModel the date model
* @param calendarField the calendar field
*/
public DateModel(IModel<Date> dateModel, int calendarField) {
this.dateModel = dateModel;
this.calendarField = calendarField;
}

/*
* (non-Javadoc)
* @see org.apache.wicket.model.Model#detach()
*/
@Override
        public void detach() {
dateModel.detach();
}

/*
* (non-Javadoc)
* @see org.apache.wicket.model.Model#getObject()
*/
@Override
        public Integer getObject() {
if (dateModel.getObject() == null) {
return null;
}

Calendar cal = Calendar.getInstance();
cal.setTime(dateModel.getObject());

return cal.get(calendarField);
}

/*
* (non-Javadoc)
* @see org.apache.wicket.model.Model#setObject(java.io.Serializable)
*/
@Override
        public void setObject(Integer object) {
Date date = dateModel.getObject();
if (date == null) {
date = new Date();
}
Calendar cal = Calendar.getInstance();
cal.setTime(date);
cal.set(calendarField, object);

dateModel.setObject(cal.getTime());
}
}

/**
* Gets the months.
*
* @return the months
*/
private List<Integer> getMonths() {
List<Integer> months = new ArrayList<Integer>(12);

for (int i = 0; i < 12; i++) {
months.add(i);
}

return months;
}

/**
* Gets the days.
*
* @return the days
*/
protected List<Integer> getDays() {
List<Integer> days = new ArrayList<Integer>(31);
int totalDays = 31;
if (yearDDC.getModelObject() != null && monthDDC.getModelObject() != null) {
Calendar cal = new GregorianCalendar(yearDDC.getModelObject(), monthDDC.getModelObject(), 1);
totalDays = cal.getActualMaximum(Calendar.DAY_OF_MONTH);
}
for (int i = 1; i <= totalDays; i++) {
days.add(i);
}
return days;
}

/**
* Gets the years.
*
* @return the years
*/
protected List<Integer> getYears() {
List<Integer> years = new ArrayList<Integer>(10);

Calendar cal = Calendar.getInstance();

for (int i = cal.get(Calendar.YEAR) - 2; i < cal.get(Calendar.YEAR) + 8; i++) {
years.add(i);
}

return years;
}

/*
* (non-Javadoc)
* @see org.apache.wicket.markup.html.form.FormComponent#convertInput()
*/
@Override
protected void convertInput() {
Calendar cal = Calendar.getInstance();
cal.set(yearDDC.getConvertedInput(), monthDDC.getConvertedInput(), dayDDC.getConvertedInput(), 0, 0, 0);
Date selectedDate = cal.getTime();
setModel(new Model<Date>(selectedDate));
}

}

The above component is tested with Wicket-1.4-rc5.

  1. victor
    March 2nd, 2010 at 11:02 | #1

    This updates is very helpful for me. Original DateChooser component was written in 2006 year and doesn’t work as expected.

  2. Hovi
    August 25th, 2010 at 11:17 | #2

    Hello,
    thanks for sollution, it is nice upgrade from original version of datepicker, however there is little bug. If you change month or year, day resets to current day. It is caused, because day component doesn’t have ajaxupdatingbehavior onchange. However if I add it, set day to 31th and then change month to month that doesn’t have 31th, days is still at 31. This is caused by behavior of GregorianCalendar in DateModel#setModel, where if day limit of the month is reached, month is changed instead of lowering to maximum of the month.

    There are 2 workarounds. One is just to update all components so it is visible that month has changed, which isn’t really user friendly. Another workaround is checking max day limit in DateModel#setModel and set it on max if current value is above maximum.

    Here is sourcecode with includng changes (only AjaxFormComponentUpdatingBehavior, updateChoices, DateModel#setModel are changed):

    public class DateChooser extends FormComponent {

    /** The day ddc. */
    private DropDownChoice dayDDC;

    /** The month ddc. */
    private DropDownChoice monthDDC;

    /** The year ddc. */
    private DropDownChoice yearDDC;

    /**
    * Instantiates a new date chooser.
    *
    * @param id
    * the id
    * @param model
    * the model
    */
    public DateChooser(final String id, IModel model) {
    super(id);

    if (model == null) {
    model = new Model(new Date());
    } else if (model.getObject() == null) {
    model.setObject(new Date());
    }

    setModel(model);

    monthDDC = new DropDownChoice(”month”, new DateModel(model, Calendar.MONTH), getMonths(), new IChoiceRenderer() {

    public Object getDisplayValue(Integer object) {
    SimpleDateFormat format = new SimpleDateFormat(”MMM”);

    Calendar cal = Calendar.getInstance();
    cal.set(Calendar.MONTH, object.intValue());

    return format.format(cal.getTime());
    }

    public String getIdValue(Integer integer, int index) {
    return String.valueOf(index);
    }
    });
    monthDDC.add(new AjaxFormComponentUpdatingBehavior(”onchange”) {
    @Override
    protected void onUpdate(AjaxRequestTarget target) {
    // change the days dropdown when month changes
    updateChoices(target);
    }
    });
    add(monthDDC);

    yearDDC = new DropDownChoice(”year”, new DateModel(model, Calendar.YEAR), getYears());
    add(yearDDC);
    yearDDC.add(new AjaxFormComponentUpdatingBehavior(”onchange”) {
    @Override
    protected void onUpdate(AjaxRequestTarget target) {
    // change the days dropdown when year changes
    updateChoices(target);
    }
    });

    dayDDC = new DropDownChoice(”day”, new DateModel(model, Calendar.DAY_OF_MONTH), getDays());
    dayDDC.add(new AjaxFormComponentUpdatingBehavior(”onchange”) {

    @Override
    protected void onUpdate(AjaxRequestTarget target) {
    }
    });

    dayDDC.setOutputMarkupId(true);
    add(dayDDC);
    }

    protected void updateChoices(AjaxRequestTarget target) {
    dayDDC.setChoices(getDays());
    target.addComponent(dayDDC);
    target.addComponent(yearDDC);
    target.addComponent(monthDDC);
    }

    /**
    * The Class DateModel.
    */
    private class DateModel extends Model {

    /** The date model. */
    private final IModel dateModel;

    /** The calendar field. */
    private final int calendarField;

    /**
    * Instantiates a new date model.
    *
    * @param dateModel
    * the date model
    * @param calendarField
    * the calendar field
    */
    public DateModel(IModel dateModel, int calendarField) {
    this.dateModel = dateModel;
    this.calendarField = calendarField;
    }

    /*
    * (non-Javadoc)
    *
    * @see org.apache.wicket.model.Model#detach()
    */
    @Override
    public void detach() {
    dateModel.detach();
    }

    /*
    * (non-Javadoc)
    *
    * @see org.apache.wicket.model.Model#getObject()
    */
    @Override
    public Integer getObject() {
    if (dateModel.getObject() == null) {
    return null;
    }

    Calendar cal = Calendar.getInstance();
    cal.setTime(dateModel.getObject());

    return cal.get(calendarField);
    }

    /*
    * (non-Javadoc)
    *
    * @see org.apache.wicket.model.Model#setObject(java.io.Serializable)
    */
    @Override
    public void setObject(Integer object) {
    Date date = dateModel.getObject();
    if (date == null) {
    date = new Date();
    }
    Calendar cal = Calendar.getInstance();
    cal.setTime(date);

    // if current day of the month is higher than allowed in new
    // month/yer, set it to max allowed day in month
    if (calendarField == Calendar.YEAR || calendarField == Calendar.MONTH) {
    int totalDays = 31;
    int year = yearDDC.getModelObject().intValue();
    int month = monthDDC.getModelObject().intValue();
    if (calendarField == Calendar.YEAR) {
    year = object.intValue();
    } else {
    month = object.intValue();
    }
    Calendar cal2 = new GregorianCalendar(year, month, 1);
    totalDays = cal2.getActualMaximum(Calendar.DAY_OF_MONTH);
    if (dayDDC.getModelObject().intValue() > totalDays) {
    dayDDC.setModelObject(Integer.valueOf(totalDays));
    cal.set(Calendar.DAY_OF_MONTH, totalDays);
    }
    }

    cal.set(calendarField, object);

    dateModel.setObject(cal.getTime());
    }
    }

    /**
    * Gets the months.
    *
    * @return the months
    */
    private List getMonths() {
    List months = new ArrayList(12);

    for (int i = 0; i < 12; i++) {
    months.add(i);
    }

    return months;
    }

    /**
    * Gets the days.
    *
    * @return the days
    */
    protected List getDays() {
    List days = new ArrayList(31);
    int totalDays = 31;
    if (yearDDC.getModelObject() != null && monthDDC.getModelObject() != null) {
    Calendar cal = new GregorianCalendar(yearDDC.getModelObject(), monthDDC.getModelObject(), 1);
    totalDays = cal.getActualMaximum(Calendar.DAY_OF_MONTH);
    }
    for (int i = 1; i <= totalDays; i++) {
    days.add(i);
    }
    return days;
    }

    /**
    * Gets the years.
    *
    * @return the years
    */
    protected List getYears() {
    List years = new ArrayList(10);

    Calendar cal = Calendar.getInstance();

    for (int i = cal.get(Calendar.YEAR) – 2; i < cal.get(Calendar.YEAR) + 8; i++) {
    years.add(i);
    }

    return years;
    }

    /*
    * (non-Javadoc)
    *
    * @see org.apache.wicket.markup.html.form.FormComponent#convertInput()
    */
    @Override
    protected void convertInput() {
    Calendar cal = Calendar.getInstance();
    cal.set(yearDDC.getConvertedInput(), monthDDC.getConvertedInput(), dayDDC.getConvertedInput(), 0, 0, 0);
    Date selectedDate = cal.getTime();
    setModel(new Model(selectedDate));
    }

    }

  1. No trackbacks yet.
Get Adobe Flash playerPlugin by wpburn.com wordpress themes