Adding faces message to redirected page using ExternalContext.redirect()
Asked Answered
R

3

14

I am using ExternalContext.redirect(String); method to redirect user to another page:

FacesContext.getCurrentInstance().addMessage(new FacesMessage("Bla bla bla..."));
FacesContext.getCurrentInstance().getExternalContext().getFlash().setKeepMessages(true);
ExternalContext ec = FacesContext.getCurrentInstance().getExternalContext();
ec.redirect(ec.getRequestContextPath() + "/scenario.xhtml");

As Matt Handy mentioned in his answer, I used Flash.setKeepMessages(true); but it does not seem to work with ExternalContext.redirect. (Although it works when I redirect by returning a page name from bean's action method.)

Now how can I add FacesMessage so that it is visible in the redirected (scenario.xhtml) page?

Rhachis answered 15/5, 2012 at 7:5 Comment(0)
F
19

This seems to be a timing problem. This listener method is invoked during the preRenderView event. According to the source code of ELFlash (Mojarra's Flash implementation as returned by ExternalContext#getFlash()) it turns out that it won't set the flash cookie when you're currently sitting in the render response phase and the flash cookie hasn't been set yet for the current request:

Here are the relevant lines from ELFlash:

if (currentPhase.getOrdinal() < PhaseId.RENDER_RESPONSE.getOrdinal()) {
    flashInfo = flashManager.getPreviousRequestFlashInfo();
} else {
    flashInfo = flashManager.getNextRequestFlashInfo(this, true);
    maybeWriteCookie(context, flashManager);
}

The maybeWriteCookie would only set the cookie when the flash cookie needs to be passed through for the second time (i.e. when the redirected page in turn redirects to another page).

This is an unfortunate corner case. This ELFlash logic makes sense, but this isn't what you actually want. Basically you need to add the message during INVOKE_APPLICATION phase instead. There is however no such event as postInvokeAction. With the new JSF 2.2 <f:viewAction> tag it should be possible as it really runs during invoke application phase.

<f:viewAction action="#{bean.onload}" />

As long as you're not on JSF 2.2 yet, you'd need to look for alternate ways. The easiest way would be to create a custom ComponentSystemEvent.

@NamedEvent(shortName="postInvokeAction")
public class PostInvokeActionEvent extends ComponentSystemEvent {

    public PostInvokeActionEvent(UIComponent component) {
        super(component);
    }

}

Now you need somewhere a hook to publish this event. The most sensible place is a PhaseListener listening on after phase of INVOKE_APPLICATION.

public class PostInvokeActionListener implements PhaseListener {

    @Override
    public PhaseId getPhaseId() {
        return PhaseId.INVOKE_APPLICATION;
    }

    @Override
    public void beforePhase(PhaseEvent event) {
        // NOOP.
    }

    @Override
    public void afterPhase(PhaseEvent event) {
        FacesContext context = FacesContext.getCurrentInstance();
        context.getApplication().publishEvent(context, PostInvokeActionEvent.class, context.getViewRoot());
    }

}

If you register it as follows in faces-config.xml

<lifecycle>
    <phase-listener>com.example.PostInvokeActionListener</phase-listener>
</lifecycle>

then you'll be able to use the new event as follows

<f:event type="postInvokeAction" listener="#{bean.onload}" />

Update this is also available in the JSF utility library OmniFaces, so you don't need to homebrew the one and other. See also the InvokeActionEventListener showcase example.

Fastigiate answered 4/6, 2012 at 20:26 Comment(15)
hmmmmm let me read it and understand it.... thanks a lot for this piece of knowledge.Rhachis
mind blowing..... this is abso-freaking-lutely working. I mean thanks a million.....Rhachis
FYI: new preInvokeAction and postInvokeAction events for <f:event> have been added to OmniFaces. It will be in the future 1.1 version.Fastigiate
@Fastigiate I had the same problem, solved by this answer. But this is not working when I return page name with faces-redirect=true from action method, which was working previously.Jactitation
@djaqeel did you try it with faces-redirect=trueJactitation
@jem: Can you please describe the use case in detail? The <f:event> listener method does not support returning navigation case outcomes at all. You need to explicitly invoke ExternalContext#redirect() or NavigationHandler#handleNavigation(). If you're inside a normal action method which is invoked by an UICommand component, then you don't need the <f:event> and you can just return the normal outcome with faces-redirect=true.Fastigiate
@Fastigiate yes I am talking about UICommand component action method. I was return page name from that like view_order.xhtml?faces-redirect=true&p1=1. It was working, but after adding this solution, parameters are not going to view_order page...Jactitation
@jem: I can't reproduce it. Your concrete problem is likely caused elsewhere. Perhaps you were also using redirect() in <f:event> while you shouldn't do that. Perhaps that whole <f:event> is unnecessary for you as you can already just do the job in the action method. The <f:event> is only useful when you're opening the page by a GET request. If you have both on the same page, you may want to add a if (!facesContext.isPostback()) check in the <f:event> method to prevent it from being invoked on postbacks.Fastigiate
@Jactitation It is working fine for me.... no problems here. There might be some other problemRhachis
@Fastigiate I got to the root of problem. I think parameters are passed correctly, but they are not set to the bean when listener method is being called in this case, Is it so?Jactitation
@jem: Apparently the getPhaseId() of your PhaseListener didn't return INVOKE_APPLICATION? They're only set during update model values phase, so you have only access to them after that.Fastigiate
@Fastigiate One final question: For example I have page a.xhtml. It has f:event. When a button is click why does listner method of f:event on the same page is being called? Error is there. Once it reach the destination page there is not any errorJactitation
Because the <f:event> is just designed to do that. If you intend to execute it on GET requests only, then add an if (!facesContext.isPostback()) check to the listener method, as said before.Fastigiate
@Fastigiate Oh Thankyou very much.... I have came from php background, I must have tried is post back earlier... but I dnt know how I missed that... ThanksJactitation
We are talking about a pop-up message no? OMG JSF 2 is a nightmare if this is what it takes to make it work. For those out there in the open-source world, try building 100 JSF applications and then telling everyone they have to upgrade to version 2.x.y and re-test them all to fix the pop-up message. Sorry but this just needed to be said.Bultman
N
16

Use the flash to keep messages over a redirect.

Add these two lines to your code before redirecting:

FacesContext context = FacesContext.getCurrentInstance();
context.getExternalContext().getFlash().setKeepMessages(true);

Note that the there are some issues with Mojarra's flash scope implementation. Keep this in mind if you use it.

Nacelle answered 15/5, 2012 at 7:17 Comment(3)
I am using context.redirect() (in a listener function and not returning page name from action function). This is not working with it.Rhachis
It works only if it's in the same path, as commented in your previous question. Also make sure that you've the most recent Mojarra.Fastigiate
@Fastigiate this is working when I return page name with faces-redirect=true from action method. But when I use context.redirect(), messages are not shown on the redirected page.Rhachis
E
-2

Using Matt Handy's example as a reference, I created the method below that worked very well for me.

public static void Message(String message) {
    FacesMessage fm = new FacesMessage(FacesMessage.SEVERITY_INFO, mensagem, null);
    FacesContext context = FacesContext.getCurrentInstance();
    context.getExternalContext().getFlash().setKeepMessages(true);
    context.addMessage(null, fm);
}
Expression answered 18/1, 2020 at 15:50 Comment(1)
Hi, adding a little more explicit example of something in a different answer is not what SO is about. You'd better edit the existing answer then and make the example more explicit. CheersKirshbaum

© 2022 - 2024 — McMap. All rights reserved.