8

I'm facing the following issue: in one page, I list all users of my application and have an "edit" button for each one, which is a "GET" link with ?id=<userid>.

The edit page has a <f:viewParam name="id" value="#{editUserBean.id}"/> in metadata.
If I made some input mistakes and submit (I use CDI Weld Bean validation), the page is displayed again, but I've lost the ?id=... in the URL and so lose the user id of the user I'm editing.

I've looked at a similar problem described in JSF validation error, lost value, but the solution with inputhidden (or worse, with tomahawk, which looks overkill) requires lot of uggly code.

I've tried adding a "Conversation" with CDI, and it is working, but it looks like too much overkill to me again.

Does there exists a simple solution in JSF to preserve view parameters in case of validation errors?

[My environment: Tomcat7 + MyFaces 2.1.0 + Hibernate Validator 4.2.0 + CDI(Weld) 1.1.2]

Community
  • 1
  • 1
DenisGL
  • 1,190
  • 1
  • 16
  • 31

5 Answers5

8

Interesting case. For everyone, the following minimal code reproduces this:

Facelet:

<html xmlns="http://www.w3.org/1999/xhtml"
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:f="http://java.sun.com/jsf/core"
>

    <f:metadata>
        <f:viewParam id="id" name="id" value="#{viewParamBean.id}"/>
    </f:metadata>

    <h:body>

        <h:messages />

        #{viewParamBean.id} <br/>

        <h:form>
            <h:inputText value="#{viewParamBean.text}" >
                <f:validateLength minimum="2"/>
            </h:inputText>

            <h:commandButton value="test" action="#{viewParamBean.actionMethod}"/>
        </h:form>

    </h:body>
</html>

Bean:

@ManagedBean
@RequestScoped
public class ViewParamBean {

    private long id;    
    private String text;

    public void actionMethod() {

    }

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getText() {
        return text;
    }

    public void setText(String text) {
        this.text = text;
    }    
}

If you call the Facelet with viewparam.xhtml?id=12 it will display the 12 onscreen. If you then input something valid, e.g. aaaaa, the id will disappear from the URL, but keeps being displayed on screen (owning to the stateful nature of ui components).

However... as OP mentioned, as soon as any validator error occurs (e.g. entering a), the id will be permanently lost. Entering valid input afterwards will not bring it back. It almost seems like a bug, but I tried both Mojarra 2.1 and Myfaces 2.1 and both have the same behavior.

Update:

After some inspection, the problem seems to be in this method of `UIViewParameter' (Mojarra):

public void encodeAll(FacesContext context) throws IOException {
    if (context == null) {
        throw new NullPointerException();
    }

    // if there is a value expression, update view parameter w/ latest value after render
    // QUESTION is it okay that a null string value may be suppressing the view parameter value?
    // ANSWER: I'm not sure.
    setSubmittedValue(getStringValue(context));
}

And then more specifically this method:

public String getStringValue(FacesContext context) {
    String result = null;
    if (hasValueExpression()) {
        result = getStringValueFromModel(context);
    } else {
        result = (null != rawValue) ? rawValue : (String) getValue();
    }
    return result;
}

Because hasValueExpression() is true, it will try to get the value from the model (the backing bean). But since this bean was request scoped it will not have any value for this request, since validation has just failed and thus no value has ever been set. In effect, the stateful value of UIViewParameter is overwritten by whatever the backing bean returns as a default (typically null, but it depends on your bean of course).

One workaround is to make your bean @ViewScoped, which is often a better scope anyway (I assume you use the parameter to get a user from a Service, and it's perhaps unnecessary to do that over and over again at every postback).

Another alternative is to create your own version of UIViewParameter that doesn't try to get the value from the model if validation has failed (as basically all other UIInput components do).

Arjan Tijms
  • 37,782
  • 12
  • 108
  • 140
  • 1
    For those interested; I create an issue for this at: http://java.net/jira/browse/JAVASERVERFACES_SPEC_PUBLIC-1029 please vote for it if you wish it to be fixed. – Arjan Tijms Aug 02 '11 at 21:25
  • I've noticed another strange behavior resulting of this issue (with Mojarra (2.1.2), but not with MyFaces (2.1.0)) : if the view parameter is set as `required="true"`, an Ajax Request will fail in the validation Phase. – DenisGL Aug 05 '11 at 14:00
  • 4
    FYI: the patched `UIViewParameter` is available as `` of OmniFaces. See also http://showcase.omnifaces.org/components/viewParam – BalusC Feb 11 '13 at 12:42
  • 1
    Thanks a lot for your investigation! I lost quite some time trying to figure out what was wrong with my form, until I found this question. Changing the bean scope did the trick, but I'm gonna update my OmniFaces Jar as well. – Med Apr 19 '13 at 08:43
1

You don't actually loose the view parameter. f:viewParam is stateful, so even if it's not in the URL, it's still there. Just put a break point or system.out in the setter bound to view param.

(if you google on viewParam stateless stateful you'll find some more info)

Mike Braun
  • 3,729
  • 17
  • 15
  • Thanks a lot, I found an interesting article here [link](http://jdevelopment.nl/stateless-stateful-jsf-view-parameters/). However, I still have the problem: if a submit my form without any error, the setter bound to the view param is called, but If i make a mistake in my input and submit again the value is lost. – DenisGL Jul 31 '11 at 16:17
  • 1
    The UIViewParameter is indeed stateful as I describe in that article, but due to problematic code in the `encode` method of that component, it overwrites it with the default value that's in the backing bean. See my updated answer. – Arjan Tijms Jul 31 '11 at 17:40
  • Okay, it's normally stateful but due to some design oversight the value is indeed lost whenever there are validation errors. Guess I learned something new! – Mike Braun Jul 31 '11 at 19:38
0
 <f:metadata>
        <f:viewParam id="id" name="id" value="#{baen.id}"/>
    </f:metadata>

Or when you the first time get parameter from url, save it in session map and continue use from that map, and after save/or update the form clean map.

Armen Arzumanyan
  • 1,939
  • 3
  • 30
  • 56
0

This is tricky, but you can try to restore view parameters with History API:

<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://xmlns.jcp.org/jsf/html"
      xmlns:f="http://xmlns.jcp.org/jsf/core"
      xmlns:ui="http://xmlns.jcp.org/jsf/facelets">
    <f:metadata >
        <f:viewParam name="param1" value="#{backingBean.viewParam1}" />
        <f:viewParam name="param2" value="#{backingBean.viewParam2}" />
        <f:viewAction action="#{view.viewMap.put('queryString', request.queryString)}" />
    </f:metadata>
    <h:head>
        <title>Facelet Title</title>
    </h:head>
    <h:body>
        <ui:fragment rendered="#{facesContext.postback}" >  
            <script type="text/javascript">
                var url = '?#{view.viewMap.get('queryString')}';
                history.replaceState({}, document.title, url);
            </script>
        </ui:fragment>
        <h:form>
            <h:inputText id="name" value="#{backingBean.name}" />
            <h:message for="name" style="color: red" />
            <br />
            <h:commandButton value="go" action="#{backingBean.go}" />
        </h:form>
        <h:messages globalOnly="true" />
    </h:body>
</html>
0

I've the same in my Application. I switched to @ViewAccessScoped which allows way more elegant implementations.

Dar Whi
  • 822
  • 5
  • 14