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!