I thought that there would be someone on the internet who already created a component like that, but I could not find any so I made my own. JSF 2 will introduce a new component creation method. It will be simpler to create components and you do not have to write java code to make them work. But until we have JSF 2 available on all major application servers we will have to program some java for our components to work.
The plan I had for creating it was simple enough just take two HtmlSelectOneMenu components and fill them with default options.
So I ended with something like this:
private void createChildComponents(FacesContext context) {
Application application = context.getApplication();
hourInput = (HtmlSelectOneMenu) application.createComponent(HtmlSelectOneMenu.COMPONENT_TYPE);
minuteInput = (HtmlSelectOneMenu) application.createComponent(HtmlSelectOneMenu.COMPONENT_TYPE);
List<UIComponent> children = getChildren();
children.add(hourInput);
HtmlOutputText sparator = (HtmlOutputText) application.createComponent(HtmlOutputText.COMPONENT_TYPE);
sparator.setValue(":");
children.add(sparator);
children.add(minuteInput);
UISelectItems hourItems = createSelectItems(application, 24, 1);
hourInput.getChildren().add(hourItems);
UISelectItems minuteItems = createSelectItems(application, 60, 5);
minuteInput.getChildren().add(minuteItems);
delegateProperties();
}
private UISelectItems createSelectItems(Application application, int number, int step) throws FacesException {
UISelectItems selectItems = (UISelectItems) application.createComponent(UISelectItems.COMPONENT_TYPE);
List<SelectItem> items = new ArrayList<SelectItem>();
for (int i = 0; i < number; i += step) {
items.add(new SelectItem(StringUtils.leftPad(String.valueOf(i), 2, "0")));
}
selectItems.setValue(items);
return selectItems;
}
Rendering this was not hard but to extract the selected value and store the submitted value back into the value binding, that was not so easy. First I tried to override the validate method but that didn't "sound" right, doing it in the updateModel was much better.
The only weird thing I was still facing was that, sometimes the value from the hour and minute components were converted to Integer but sometimes they where not. So I ended up with this construction to ensure that they were always Integer.
@Override
public void updateModel(FacesContext context) {
super.updateModel(context);
hourInput.validate(context);
minuteInput.validate(context);
ValueExpression valueExpression = getValueExpression("value");
Date value = (Date) valueExpression.getValue(context.getELContext());
if (value != null && hourInput.getValue() != null
&& minuteInput.getValue() != null) {
Calendar calendar = Calendar.getInstance();
calendar.setTime(value);
Integer hour = Integer.valueOf(hourInput.getValue().toString());
calendar.set(Calendar.HOUR_OF_DAY, hour);
Integer minute = Integer.valueOf((minuteInput.getValue().toString()));
calendar.set(Calendar.MINUTE, minute);
valueExpression.setValue(context.getELContext(), calendar.getTime());
}
}
Here I'm using the Java Date API. That of course is extremely sadistic so we could change it to use joda-time.
Now to use it we first register it as a component in components.taglib.xml
<facelet-taglib>
<namespace>http://ctp-consulting.com/components</namespace>
<!--
usage: <ctp:timepicker value="date"/>
-->
<tag>
<tag-name>timepicker</tag-name>
<component>
<component-type>com.ctp.web.components.Timepicker</component-type>
</component>
</tag>
Then make sure you have the components.taglib.xml registered in you're web.xml
<context-param>
<param-name>facelets.LIBRARIES</param-name>
<param-value>/WEB-INF/tags/components.taglib.xml;</param-value>
</context-param>
And here an example of use, fist add the namespace on the top of you're page:
xmlns:ctp="http://ctp-consulting.com/components"
Then in our page you can do something like:
<ctp:timepicker value="#{catering.deliveryTime}"/>
Maybe this is not the best solution, if you have comments or tips, feel free drop them directly on this post. I'm interested in what you have to say!