1

I have a problem with the correct usage of the methods getValue(), getLocalValue() and/or getSubmittedValue()

I tried to write a simple Composite Component to let the user enter a time (hours and minutes). In the model I want to store only one Integer value (hours*60+minutes).

Basically I followed this article from BalusC.

Now I want to submit the form where I am using this component and persist the value to my database, but I am getting the previous value instead.


There are no validation errors, no converter errors etc. Thus, as far as I understood, in the invoke-Phase, when the action-Method is triggered, the getSubmittedValue() should return null, the new value should be accessable via getValue() and isLocalValueSet() should return true. (see also here, where a similar question is asked)

Am I correct so far?

But what happens is that isLocalValueSet() returns true but getValue() (and also getLocalValue()) gives me the old value and getSubmittedValue() is returning the new (correct) value. On the second submit, getValue() returns the value, just entered before the first button click.

So, what am I missing / doing wrong here?


My Code so far: Backing Component:

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

    private UIInput hours;
    private UIInput minutes;

    @Override
    public String getFamily() {
        return UINamingContainer.COMPONENT_FAMILY;
    }

    @Override
    public void encodeBegin(FacesContext context) throws IOException {
        Integer i = (Integer) this.getValue();             
        if(i != null) {
            hours.setValue(i/60);
            minutes.setValue(i%60);
        }
        super.encodeBegin(context);
    }

    @Override
    public Object getSubmittedValue() {
        Integer [] value = new Integer [2];        
        value[0] = hours.isLocalValueSet() ?
          (Integer) hours.getValue() : (Integer) hours.getSubmittedValue();
        value[1] = minutes.isLocalValueSet() ?
          (Integer) minutes.getValue() : (Integer) minutes.getSubmittedValue();
        return value;
    }

    @Override
    protected Object getConvertedValue(FacesContext contect, Object submittedValue) {
        Integer [] value = (Integer []) submittedValue;
        int hh = value[0] == null ? 0 : value[0];
        int mm = value[1] == null ? 0 : value[1];
        return hh*60+mm;
    }
    // Getter & Setter 
}

The Composite Component:

<cc:interface componentType="inputTime">
    <cc:attribute name="value" type="java.lang.Integer" required="true" />
    <cc:attribute name="ajax" default="#{false}" type="java.lang.Boolean" />
    <cc:attribute name="listener" method-signature="void actionListener()" />
</cc:interface>

<cc:implementation>
    <span id="#{cc.clientId}" style="white-space: nowrap"> 
        <p:inputText id="hours" binding="#{cc.hours}" converter="javax.faces.Integer"  >
        </p:inputText>
        <p:inputText id="minutes" binding="#{cc.minutes}" converter="javax.faces.Integer">
        </p:inputText>
    </span>
</cc:implementation>

Usage in View:

<h:form>
    <stg:inputTime value="#{bean.entity.value}"/>
    <p:commandButton action="#{bean.listener}" />
</h:form>

I am running this specific project on:

  • Mojarra 2.1.29
  • PrimeFaces 5.1
  • GlassFish 3.1.2 Build 5

Edit 1:

According to BalusC comment, I edited the Backing Component like this:

@Override
public Object getSubmittedValue() {
    StringBuilder sb = new StringBuilder("");       
    sb.append(hours.isLocalValueSet() ? 
        hours.getValue() : hours.getSubmittedValue());
    sb.append("-");
    sb.append(minutes.isLocalValueSet() ? 
        minutes.getValue() : minutes.getSubmittedValue());
    return sb.toString();
}

@Override
protected Object getConvertedValue(FacesContext contect, Object submittedValue) {
    Scanner sc = new Scanner((String) submittedValue).useDelimiter("-");
    int hh = sc.nextInt();
    int mm = sc.nextInt();
    return hh*60+mm;
}

But my problem remains the same: - in my getSubmittedValue method isLocalValueSet is evaluated to true and getValue (as well as getLocalValue) is returning the 'old' value.

(For clarification, I am not dealing with null values here so far, but I just wanted to keep it simple in this example..)


Edit 2: I just upgraded from PrimeFaces 3.5 to PrimeFaces 5.1. Problem is still present.

Community
  • 1
  • 1
stg
  • 2,757
  • 2
  • 28
  • 55
  • This can't possibly work as `getSubmittedValue()` **always** returns `String` and thus would only cause `ClassCastException`. Perhaps you're swallowing exceptions somewhere? – BalusC Feb 16 '15 at 07:46
  • I refactored my Backing Component as you suggested. I think I just never ran into an exception, because `getSubmittedValue` from the `UIInput` that I am using, was never called. However, my problem remains the same. Should not `getValue` returns the new recently submitted value which the user has just entered? But why is it containing the 'old' value here? Using `getSubmittedValue` here should not be the proper way here, should it? – stg Feb 16 '15 at 08:32

1 Answers1

3

That article was edited after an user reported that it didn't work in MyFaces. The original getSubmittedValue() reads like:

@Override
public Object getSubmittedValue() {
    return day.getSubmittedValue()
        + "-" + month.getSubmittedValue()
        + "-" + year.getSubmittedValue();
}

Then a MyFaces user reported this failed in MyFaces because it first processes the input's children and then the input itself, while Mojarra did it exactly the other way round. This inconsistency will be aligned out for JSF 2.3 (which will likely follow MyFaces approach).

The bugfix was as below:

@Override
public Object getSubmittedValue() {
    return (day.isLocalValueSet() ? day.getValue() : day.getSubmittedValue())
        + "-" + (month.isLocalValueSet() ? month.getValue() : month.getSubmittedValue())
        + "-" + (year.isLocalValueSet() ? year.getValue() : year.getSubmittedValue());
}

However, it overlooked the fact that isLocalValueSet is stored in JSF state and it would in Mojarra thus still fail on multiple subsequent submits and only return the initially submitted value.

The below should fix it again, and make it more ugly:

@Override
public Object getSubmittedValue() {
    return (day.getSubmittedValue() == null && day.isLocalValueSet() ? day.getValue() : day.getSubmittedValue())
        + "-" + (month.getSubmittedValue() == null && month.isLocalValueSet() ? month.getValue() : month.getSubmittedValue())
        + "-" + (year.getSubmittedValue() == null && year.isLocalValueSet() ? year.getValue() : year.getSubmittedValue());
}

(the blog article has in the meanwhile been fixed)

In any case, if you don't intend to distribute this composite and would always run on Mojarra, then you could also just go for the initial approach.

@Override
public Object getSubmittedValue() {
    return day.getSubmittedValue()
        + "-" + month.getSubmittedValue()
        + "-" + year.getSubmittedValue();
}
BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
  • tyvm! :) But did I get you right now, that for JSF 2.3 the 'initial approach' will fail again? – stg Feb 16 '15 at 10:41
  • Yup. You'd then follow the MyFaces approach. See also this EG discussion https://java.net/projects/javaserverfaces-spec-public/lists/jsr372-experts/archive/2015-01/message/3 – BalusC Feb 16 '15 at 10:52
  • Thanks for clarification! And btw also thanks for the nice article. It was very helpful to me getting started developing my own components. :) – stg Feb 16 '15 at 10:57