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?

3 Answers3

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.

BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
  • hmmmmm let me read it and understand it.... thanks a lot for this piece of knowledge. –  Jun 05 '12 at 06:37
  • mind blowing..... this is abso-freaking-lutely working. I mean thanks a million..... –  Jun 07 '12 at 10:54
  • 2
    FYI: new `preInvokeAction` and `postInvokeAction` events for `` have been added to [OmniFaces](http://code.google.com/p/omnifaces/). It will be in the future 1.1 version. – BalusC Jun 07 '12 at 14:19
  • @BalusC 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. – jem Jun 07 '12 at 14:40
  • @djaqeel did you try it with faces-redirect=true – jem Jun 07 '12 at 14:41
  • @jem: Can you please describe the use case in detail? The `` 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 `` and you can just return the normal outcome with `faces-redirect=true`. – BalusC Jun 07 '12 at 14:43
  • @BalusC 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... – jem Jun 07 '12 at 15:04
  • @jem: I can't reproduce it. Your concrete problem is likely caused elsewhere. Perhaps you were *also* using `redirect()` in `` while you shouldn't do that. Perhaps that whole `` is unnecessary for you as you can already just do the job in the action method. The `` 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 `` method to prevent it from being invoked on postbacks. – BalusC Jun 07 '12 at 15:11
  • @jem It is working fine for me.... no problems here. There might be some other problem –  Jun 07 '12 at 16:00
  • @BalusC 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? – jem Jun 07 '12 at 16:24
  • @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. – BalusC Jun 07 '12 at 16:26
  • @BalusC 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 error – jem Jun 07 '12 at 16:57
  • Because the `` 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. – BalusC Jun 07 '12 at 17:01
  • @BalusC 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... Thanks – jem Jun 08 '12 at 07:16
  • 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. – Darrell Teague Mar 14 '14 at 14:49
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.

Matt Handy
  • 29,855
  • 2
  • 89
  • 112
  • I am using context.redirect() (in a listener function and not returning page name from action function). This is not working with it. –  May 15 '12 at 07:36
  • 3
    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. – BalusC May 15 '12 at 12:39
  • 2
    @BalusC 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. –  May 17 '12 at 12:00
-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);
}
  • 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. Cheers – Kukeltje Jan 18 '20 at 17:21