jsfhttp-redirectprimefacesviewparams

ExternalContext#redirect() with includeViewParams=true


Using a list of currencies in the form of strings as follows.

<p:selectOneMenu id="currency"
                 value="#{currencyRateBean.currency}"
                 onchange="changeCurrency([{name: 'currency', value: this.value}]);">

    <f:selectItems var="row"
                   value="#{currencyBean.currencies}"
                   itemLabel="#{row}"
                   itemValue="#{row}"/>
</p:selectOneMenu>

Along a <p:remoteCommand>.

<p:remoteCommand ignoreAutoUpdate="true"
                 name="changeCurrency"
                 partialSubmit="true"
                 process="@this"
                 update="@none"
                 action="#{currency.currencyAction}"/>

The managed bean setting a currency value being passed through the above <p:remoteCommand> as a parameter to a JavaScript function.

@Named
@RequestScoped
public class Currency {

    @Inject
    @HttpParam
    private String currency;

    @Inject
    private CurrencyRateBean currencyRateBean;

    public Currency() {}

    public String currencyAction() throws MalformedURLException, IOException {

        try (Scanner scanner = new Scanner(new URL("http://www.exchangerate-api.com/INR/" + currency + "/1?k=FQRxs-xT2tk-NExQj").openConnection().getInputStream(), "UTF-8");) {
            currencyRateBean.setCurrencyRate(scanner.nextBigDecimal());
            currencyRateBean.setCurrency(currency);
        } catch (UnknownHostException | ConnectException e) {}

        return FacesContext.getCurrentInstance().getViewRoot().getViewId() + "?faces-redirect=true&includeViewParams=true";
    }
}

The supplied currency value is then set to another session scoped managed bean CurrencyRateBean from within the action method currencyAction() above which finally makes a redirect based on the current value of viewId along with includeViewParams=true which is important.


Now, the story changes, when #{currencyRateBean.currencies} has been changed to have a list of composite objects which has been a list of Strings so far.

The following scenario will not work with includeViewParams=true which is significant.

<p:selectOneMenu value="#{currencyRateBean.currencyHolder}">

    <f:selectItems var="row" value="#{currencyBean.currencies}"
                   itemLabel="#{row.currency}"
                   itemValue="#{row}"/>

    <p:ajax event="change"
            listener="#{currency.currencyAction}"
            partialSubmit="true"
            process="@this"
            update="@none"/>
</p:selectOneMenu>
public void currencyAction()  throws IOException {
    // ...
    FacesContext facesContext = FacesContext.getCurrentInstance();
    String viewId = facesContext.getViewRoot().getViewId();

    ExternalContext externalContext = facesContext.getExternalContext();
    externalContext.redirect(externalContext.getRequestContextPath() + viewId + "?includeViewParams=true");
}

includeViewParams=true has been added for decoration only. It is not going to work.

Since listener in <p:ajax> is incapable of making a redirect based on a navigation case outcome as done by action of <p|h:commandXxx>, ExternalContext#redirect() has to be used anyway.

<p:remoteCommand> can be used on complete of <p:ajax> but this will involve two round trips to the server unnecessarily, first to set the currency value to the associated managed bean and then to make a redirect.

How to make a redirect with includeViewParams=true in the example given?


Solution

  • Like faces-redirect=true, includeViewParams=true works only in navigation outcomes, not in "plain" URLs which you pass to ExternalContext#redirect().

    Use NavigationHandler#handleNavigation().

    FacesContext context = FacesContext.getCurrentInstance();
    String outcome = viewId + "?includeViewParams=true";
    context.getApplication().getNavigationHandler().handleNavigation(context, null, outcome);
    

    Or, with OmniFaces.

    Faces.navigate(viewId + "?includeViewParams=true");
    

    A dubious alternative is to collect all view parameters yourself and convert them to query string so you can use ExternalContext#redirect() anyway. This is easier with OmniFaces.

    Faces.redirect(viewId + "?" + Servlets.toQueryString(Faces.getViewParameterMap()));