jsf viewparam lost after validation error [duplicate]
Asked Answered
L

5

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]

Lytic answered 30/7, 2011 at 20:46 Comment(2)
are you using includeViewParams=true in your submit action?Sweltering
No, but my action handler is not called : the submit fails before in the validation step.Lytic
A
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).

Around answered 31/7, 2011 at 16:42 Comment(4)
For those interested; I create an issue for this at: java.net/jira/browse/JAVASERVERFACES_SPEC_PUBLIC-1029 please vote for it if you wish it to be fixed.Around
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.Lytic
FYI: the patched UIViewParameter is available as <o:viewParam> of OmniFaces. See also showcase.omnifaces.org/components/viewParamNarcho
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.Cotangent
I
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)

Infantilism answered 30/7, 2011 at 22:21 Comment(3)
Thanks a lot, I found an interesting article here link. 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.Lytic
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.Around
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!Infantilism
L
0

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

Liebfraumilch answered 21/8, 2011 at 19:20 Comment(0)
S
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.

Saracen answered 21/12, 2013 at 14:0 Comment(0)
C
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>
Czarevna answered 4/4, 2016 at 2:0 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.