3
<ui:composition template="/WEB-INF/templates/base.xhtml"
                xmlns="http://www.w3.org/1999/xhtml"
                xmlns:ui="http://java.sun.com/jsf/facelets"
                xmlns:h="http://java.sun.com/jsf/html"
                xmlns:f="http://java.sun.com/jsf/core"
                xmlns:p="http://primefaces.org/ui">    
    <ui:define name="metadata">
        <f:metadata>
            <f:event type="preRenderView" listener="#{resetPassword.loadUser}" />
            <f:viewParam name="user" value="#{resetPassword.user}" />
        </f:metadata>
    </ui:define>

    <ui:define name="content">
        <h:form rendered="#{resetPassword.user != null}">
            <h:panelGrid class="form_panel" columns="2" width="596px">
                <h:outputLabel class="form_label" for="password" value="#{base['resetPassword.password']}" />
                <h:inputSecret class="form_inputText" id="password" value="#{resetPassword.password}">
                    <f:ajax event="keyup" execute="password" listener="#{resetPassword.validatePassword}" render="scorePanel complexity" />
                </h:inputSecret>

                (...) // Other labels and inputfields, no ajax
            </h:panelGrid>
        </h:form>
    </ui:define>
</ui:composition> 

I have read about a lot of problems with preRenderView in combination with ajax-calls. The main problem I have come across is the postback issue.Though, in my case, ajax won't fire at all with the preRenderView. Without the whole f:event-tag, my ajax-call does work correct.

resetPassword.loadUser() will set resetPassword.user, which is NOT null. I need to use the preRenderView instead of @PostConstruct in order to fill the f:viewParam. This param is needed when the form is submitted.

Why does my ajax-event break when <f:event type="preRenderView" /> is defined?

Note: <ui:insert name="metadata" /> in the template is located inside <h:head>.

Update

As BalusC commented, the issue is not visible in this part of the code.

public void loadUser(ComponentSystemEvent event) {
    if(!FacesContext.getCurrentInstance().isPostback()) {
        user = (hash != null) ? userService.getByIdAndPwdResetHash(userId, hash) : null;
    }
}

This code might return null but doesn't (as my form is being loaded).

public void validatePassword(AjaxBehaviorEvent event) {
    System.out.println("Ajax works"); // Just for testing purposes so far
}

I don't know what to add further, as this pretty much is all relevant code.

Menno
  • 12,175
  • 14
  • 56
  • 88
  • The problem is not visible in the information provided so far, or you must have set the `user` property in the `loadUser()` method to `null` (which doesn't make sense in first place). Can you please clarify "won't fire" in more detail? Is the HTTP request not been sent at all? Or is it properly been sent, but is the method not invoked? Further I wonder if you've ever gone across the step of learning about JSF converters and validators. I suggest to carefully go through http://balusc.blogspot.com/2011/09/communication-in-jsf-20.html#ProcessingGETRequestParameters and the subsequent chapters. – BalusC Nov 26 '12 at 18:24
  • So, your're only initializing the bean property on the initial request. Is your bean view scoped? A request scoped one definitely needs initialization during postbacks as the `user` property is basically cleared out in the brand new instance. – BalusC Nov 26 '12 at 18:37
  • Hmm, I'm using CDI. This means I would need to use `@ConversationScoped` as soon as I'm using any postbacks? – Menno Nov 26 '12 at 18:39
  • By the way, aren't you somewhere mixing `userId` with `user`? The view param sets `user`, but you're relying on an `userId` to initialize `user`. – BalusC Nov 26 '12 at 18:40
  • No, the userId and the other mentioned field hash are GET-parameters. These fields should result in a user (from the service). – Menno Nov 26 '12 at 18:41
  • Yes okay, but you've a `` not a ``. – BalusC Nov 26 '12 at 18:50

1 Answers1

4

This construct will fail when the #{resetPassword} is request scoped and the user property is not initialized during bean's (post)construction.

JSF will during apply request values phase namely re-evaluate the rendered attribute of an input component (this also includes all of its parent components) before processing the submitted value, as part of safeguard against tampered requests. In your case, the parent form is not rendered and thus all input components won't be processed.

You have basically 2 options:

  1. Make the bean @ViewScoped (or the CDI equivalent @ConversationScoped). This way it lives as long as you're interacting with the same view (resp. conversation).

  2. Perform the initialization of user in (post)constructor. The pre render view is too late. As your bean is request scoped already, just use @ManagedProperty (or its homegrown CDI equivalent; Google has results on "@HttpParam").

See also:


Unrelated to the concrete question, performing validation in an action(listener) method isn't the right approach. Instead of <f:ajax listener>, use a normal Validator which you reference by input component's validator attribute or a nested <f:validator>.

Community
  • 1
  • 1
BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
  • Ok, my mistake indeed was that the @RequestScoped bean will be reconstructed upon an ajax-call. Concerning the validation, I am in fact using a final validation. This ajax call renders a panel in which a 'score' for the password will be given. E.g.: http://www.passwordmeter.com/ – Menno Nov 26 '12 at 18:51
  • Oh, such one, I'd rather rename the method to `calculatePasswordStrength` or so which is so much more self-documenting. You're calculating the password strength, not validating the password's correctness, right? – BalusC Nov 26 '12 at 18:53
  • True, validation will execute upon submitting the form, the way it should, as you correctly mentioned. – Menno Nov 26 '12 at 18:55