8

I would like to set up a Date field in my page like this

|hours| h |minutes|

where hours and minutes are in separated inputText.

The bean have this date

import java.util.Date;
...
private Date myDate;
...

and the page is

<h:form>
    ...
    <h:inputText id="myDateHours" maxlength="2" value="#{myBean.myDate}"
        <f:convertDateTime pattern="HH" />
    </h:inputText>

    <h:outputText value=" h " />

    <h:inputText id="myDateMinutes" maxlength="2" value="#{myBean.myDate}"
        <f:convertDateTime pattern="mm" />
    </h:inputText>
    ...
</h:form>

But the problem is that when I submit the form only the last element is saved. For instance if I type the hours and then the minutes, the hours are overwritten and the result is

| 00 | h | minutes |

I tried to set

<h:inputText id="myDateHours" value="#{myBean.myDate.hours}></h:inputText>

<h:inputText id="myDateMinutes" value="#{myBean.myDate.minutes}></h:inputText>

but I get a

Cannot convert 01/01/70 01:00 of type class java.util.Date to int

I don't want to replace my date field with two int field (hours and minutes...) Do you have an idea?

Thanks

BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
Loric
  • 1,678
  • 8
  • 28
  • 48

2 Answers2

14

This particular case is not possible if you want to use a single model value.

This is however a perfect candidate for a composite component. It allows you to bind a single model value to a group of closely related existing components and perform the processing/conversion in the backing component, fully decoupled from the view and backing bean. One of the examples can be found in this article: composite component with multiple input fields. This example can in for your specific case be altered as follows:

/resources/components/inputTime.xhtml:

<ui:component
    xmlns="http://www.w3.org/1999/xhtml"
    xmlns:f="http://java.sun.com/jsf/core"
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:ui="http://java.sun.com/jsf/facelets"
    xmlns:cc="http://java.sun.com/jsf/composite"
>
    <cc:interface componentType="inputTime">
        <cc:attribute name="value" type="java.util.Date" shortDescription="The selected time. Defaults to now." />
    </cc:interface>
    <cc:implementation>
        <span id="#{cc.clientId}" style="white-space:nowrap">
            <h:inputText id="hour" binding="#{cc.hour}" maxlength="2" converter="javax.faces.Integer" />h
            <h:inputText id="minute" binding="#{cc.minute}" maxlength="2" converter="javax.faces.Integer" />
        </span>
    </cc:implementation>
</ui:component>

com.example.InputTime

@FacesComponent("inputTime")
public class InputTime extends UIInput implements NamingContainer {

    private UIInput hour;
    private UIInput minute;

    /**
     * As required by <cc:interface>.
     */
    @Override
    public String getFamily() {
        return UINamingContainer.COMPONENT_FAMILY;
    }

    /**
     * Set initial hour and minute based on model.
     */
    @Override
    public void encodeBegin(FacesContext context) throws IOException {
        Calendar calendar = Calendar.getInstance();
        Date date = (Date) getValue();

        if (date != null) {
            calendar.setTime(date);
        }

        hour.setValue(calendar.get(Calendar.HOUR_OF_DAY));
        minute.setValue(calendar.get(Calendar.MINUTE));
        super.encodeBegin(context);
    }

    /**
     * Returns the submitted value in HH-mm format.
     */
    @Override
    public Object getSubmittedValue() {
        return hour.getSubmittedValue() + "-" + minute.getSubmittedValue();
    }

    /**
     * Converts the submitted value to concrete {@link Date} instance.
     */
    @Override
    protected Object getConvertedValue(FacesContext context, Object submittedValue) {
        try {
            return new SimpleDateFormat("HH-mm").parse((String) submittedValue);
        }
        catch (ParseException e) {
            throw new ConverterException(e);
        }
    }

    public UIInput getHour() {
        return hour;
    }

    public void setHour(UIInput hour) {
        this.hour = hour;
    }

    public UIInput getMinute() {
        return minute;
    }

    public void setMinute(UIInput minute) {
        this.minute = minute;
    }

}

Usage:

<html ... xmlns:my="http://java.sun.com/jsf/composite/components">
...
<my:inputTime value="#{bean.date}" />

See also:

Community
  • 1
  • 1
BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
  • That's a great solution! Thank you so much! How can I do if I want to change the converter message for each input? – Loric Jun 24 '13 at 14:08
  • Throw `ConverterException` with specific `FacesMessage`. Note: you can't target a specific input, both inputs are then considered invalid as it's after all a single component. – BalusC Jun 24 '13 at 14:10
  • cool! and other question, is it possible to use a blur ajax event on inputs (inside/outside the component?) to render an h message? – Loric Jun 24 '13 at 14:15
  • Yes. If inside, just nest `` in each of those inputs. If outside, just declare the inputs as `` and retarget `f:ajax` on them. – BalusC Jun 24 '13 at 14:17
  • Yes, but if my h message is outside the composant I can't reference the id. It would be better if I can add the ajax tag in my page like this ... But the event blur is not defined for the composant – Loric Jun 24 '13 at 14:24
  • Sorry, that should have been `` with ``. Note `execute="inputTimeId"` which should reference ``, the (default) `@this` unfortunately doesn't work in this construct. – BalusC Jun 24 '13 at 15:07
  • Seems to be the perfect solution but can't find the tag.. `no tag was defined for name: behaviorHolder"` – Loric Jun 25 '13 at 06:38
  • Sorry, I mixed up things again, it's ``: http://docs.oracle.com/javaee/6/javaserverfaces/2.1/docs/vdldocs/facelets/composite/clientBehavior.html – BalusC Jun 25 '13 at 10:39
  • Thank you so much, everything is running fine now ;) So helpful – Loric Jun 25 '13 at 12:16
2

You need two separate setter methods in the bean, and then do the merge in the server.

<h:inputText id="myDateHours" value="#{myBean.hours}></h:inputText>
<h:inputText id="myDateMinutes" value="#{myBean.minutes}></h:inputText>

Both must get date values, so then you can operate with a JAVA Calendar setting both fields, in the action invoked by your form.

Calendar calendar = new GregorianCalendar();
calendar.setTimeInMillis(0);
calendar.set(Calendar.HOUR, getHours().getHours());
calendar.set(Calendar.MINUTE, getMinutes().getMinutes());

Be aware of time zones if required.

MaQy
  • 478
  • 3
  • 10