views:

2172

answers:

2

Hey all,

I've been developing a few JSF applications lately and am disturbed with the inconsistency in the web component APIs.

I've noticed that there is extremely unpredictable behavior when calling .getValue() or .getSubmittedValue() on a JSF component object in server side code. Sometimes when I call .getValue() on a drop down list box, I've noticed that I get the value as it was BEFORE I selected my value (so the value from the last page refresh), of which .getSubmittedValue() gets me the correct value, as such:

UIInput name = new UIInput(); // This is the control I have in a bean.

public void submit(ActionEvent ae)
{
    someMethod(name.getValue().toString());          // Retrieves the "old" value
    someMethod(name.getSubmittedValue().toString()); // Retrieves the correct value 
}

Also, I've noticed that calling .getSubmittedValue() on a form field sometimes results in a null pointer exception because that value has not been instantiated in the component object, in which case when I call .getValue() in that circumstance I get the correct value, for example:

HtmlInputText name = new HtmlInputText(); // This is the control I have in a bean.

public void submit(ActionEvent ae)
{
    someMethod(name.getValue().toString());          // Retrieves the correct value
    someMethod(name.getSubmittedValue().toString()); // Throws NullPointerException 
}

Is this just a "quirk" of the JSF framework, or am I just using the API COMPLETELY incorrectly?? Any insight into these two methods would be greatly appreciated. Cheers.

+3  A: 

To quote the documentation on EditableValueHolder.getSubmittedValue:

Return the submittedValue value of this component. This method should only be used by the encodeBegin() and/or encodeEnd() methods of this component, or its corresponding Renderer.

Generally, you would not even be calling getValue. Instead, the component's value attribute should be bound to your model (a bean, maybe). Your business logic would interact with the model, not the component.

If the submitted value is not being set as the value, then I'd guess that some validation is failing. The only problem with that is that your event is being fired. Two guesses for the problem here:

  • You have a stale reference to the component object.
  • You've set the immediate attribute on a UICommand which means that the event is fired in a phase where the component will be in an inappropriate state.

It isn't possible to be certain with the information provided.

McDowell
Thank you for the insight, @McDowell. You've provided me with a lot of ammunition to investigate my code. Kudos.
KG
KG
+3  A: 

Since this is the #1 result in Google for searching on getValue vs. getSubmittedValue I'd just like to add that the difference between these is critical in validation (i.e. when writing a custom validator)

To quote the API documentation for getSubmittedValue():

This is non-null only between decode and validate phases, or when validation for the component has not succeeded. Once conversion and validation has succeeded, the (converted) value is stored in the local "value" property of this component, and the submitted value is reset to null.

Source: http://myfaces.apache.org/core11/myfaces-api/apidocs/javax/faces/component/UIInput.html#getSubmittedValue()

This means that if the validation/conversion has taken place for the binding you are trying to access, you should call getValue() otherwise you'll have to call getSubmittedValue() and deal with parsing it yourself. The order in which these occur seems to be dictated by the order they appear in the UI, but I don't think that's guaranteed. Even if it is, you shouldn't count on that as changing field in your UI shouldn't break your code.

You can detect if the validation/conversion has been done by just looking at what isLocalValueSet() returns. If it returns true, then the valdation/conversion has been done, so you should call getValue(). Otherwise you'll need to call getSubmittedValue() and that'll give you the raw input the user entered and you'll likely want to parse it into something more meaningful.

For example, a calendar object would return a Date object when getValue() was called, but a String object when getSubmittedValue() was called. It's up to your validator to parse the string into a Date so it can be compared.

It'd be great if the JSF spec had a method which would do this for us, but AFAIK it doesn't. If certain dates need to be before other dates, and some are only required in certain circumstances, one will need to write several validators to handle this. So it can easily become an issue. This is similar to the fact that you can't do any kind of validation on a blank field, which means you can't make that field conditionally required. If validation was run on all fields, even blank ones, a custom validator could be written to throw an exception if it should be required and is not. There are some things with JSF which are just a pain; unless/until they're fixed, we just have to deal with them.

To speak to the specifics of the issue in the original post: the difference here is where you're at in the life cycle. Submit seems like the action for a button, which puts it at the end of the life cycle; application listeners are called in the "Invoke Application" phase which comes prior to the render response, but after validation. If you're going to program in JSF, you should learn and understand the life cycle. It's worth the time.

Dr. Nichols
Excellent answer! Great insight, thanks @Dr. Nichols
KG