22

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.

BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
karlgrz
  • 14,485
  • 12
  • 47
  • 58

4 Answers4

32

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 converter to parse the string into a Date so it can be validated.

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. The submit method seems like an action listener for a button, which puts it at the end of the life cycle; actions and action listeners are triggered 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.

Vsevolod Golovanov
  • 4,068
  • 3
  • 31
  • 65
Dr. Nichols
  • 336
  • 3
  • 4
  • The McDowell's answer is more to the point. Business logic should be performed in the Invoke Application phase. You shouldn't ever need submitted or local value, shouldn't have to perform some redundant manual conversion and validation. – Vsevolod Golovanov Feb 27 '13 at 08:32
  • 1
    Components are just processed (converted/validated) in the order they appear in the component tree. There's no vague magic. – BalusC Feb 27 '13 at 19:24
  • Regarding your remarks on conditional validation: It is in fact possible to write a custom validator, and supply it with a list of "other" fields (=other than the field on which the validator works) that should be taken into account - e.g. fields in a group should be either all filled out, or all left blank, e.g. passport number + passport country + expiry date. (Google "jsf group validator"). Because some fields may be before and some after this field in processing order, it could use `isLocalValueSet()`, `getValue()` and `getSubmittedValue()` to get the relevant value to use in the logic. – frIT Nov 06 '15 at 09:53
6

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
  • 107,573
  • 31
  • 204
  • 267
  • Thank you for the insight, @McDowell. You've provided me with a lot of ammunition to investigate my code. Kudos. – karlgrz Nov 04 '08 at 13:42
  • 1
    After a bit of investigating I realized the problem was in my JSP markup. I was not binding the value of the control to a bean property, I was just setting the component binding & the datasource. After I bound the value everything worked as expected and I eliminated all the getSubmittedValue(). – karlgrz Nov 04 '08 at 14:13
  • Yeah and how exactly are we going to access this model in a custom validator? No thanks. – Andrew Oct 10 '17 at 21:15
0

I work on xpages which are based on JSF so.. it could be the same...

Anyway, getSubmittedValue(); always returns what you see in firebug/chrome develepers network tab. That is value within sent packet. I have it shown (chrome) in headers tab, in form data section, named $$xspsubmitvalue.

On the other hand, getValue() is component specific. <-- not 100% sure here.

Ermo
  • 91
  • 1
  • 7
0

TL;DR answer:

UIViewRoot viewRoot = context.getViewRoot();
UIInput input = (UIInput)viewRoot.findComponent(":form:inputID");

String inputValueString;

if (input.isLocalValueSet()) {
  inputValueString = (String)input.getValue(); //validated and converted already
} else {
  inputValueString = (String)input.getSubmittedValue(); //raw input
}

or at least that's what the other answers are saying to do...

Just use .getSubmittedValue() and deal with the consequences of having to convert raw input (if necessary, if that raw input needs conversion). .getValue() is broken in this regard, even with the code above. It delays the submitted value if you use it and that's unacceptable.

Andrew
  • 5,839
  • 1
  • 51
  • 72