3

I have a JSF web application in JBoss 7.1 and Richfaces 4.1. I tried to configure a custom error page in web.xml but came across the problem, that this does not work for AJAX requests. As a solution I tried to use Omnifaces FullAjaxExceptionHandler which displays the error page fine.

However I wanted to add a form that allows the user to enter additional information and send this, along with the exception, as an email to me. The problem is that on the error page, the submit buttons does not work. When I click on it the error page just reloaded. On the next click everything works as expected.

The same problem occurs with h:commandlinks in a small menu that is in the template of the error page.

I'm quite new to JSF so I don't really know why this happens and how it can be fixed. Or is there a better way to accomplish this?

BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
Markus
  • 79
  • 1
  • 9

1 Answers1

1

This is related to JSF spec issue 790 which boils down to that ajax-updating some component which in turn contains one or more <h:form> components doesn't properly update the new view state identifier of those forms.

This issue is fixed in the upcoming JSF 2.2, but until then you've to do with the following JavaScript based workaround.

jsf.ajax.addOnEvent(function(data) {
    if (data.status == "success") {
        var viewState = getViewState(data.responseXML);

        if (viewState) {
            for (var i = 0; i < document.forms.length; i++) {
                var form = document.forms[i];

                if (!hasViewState(form)) {
                    createViewState(form, viewState);
                }
            }
        }
    }
});

function getViewState(responseXML) {
    var updates = responseXML.getElementsByTagName("update");

    for (var i = 0; i < updates.length; i++) {
        var update = updates[i];

        if (update.getAttribute("id") == "javax.faces.ViewState") {
            return update.firstChild.nodeValue;
        }
    }

    return null;
}

function hasViewState(form) {
    for (var i = 0; i < form.elements.length; i++) {
        if (form.elements[i].name == "javax.faces.ViewState") {
            return true;
        }
    }

    return false;
}

function createViewState(form, viewState) {
    var hidden;

    try {
        hidden = document.createElement("<input name='javax.faces.ViewState'>"); // IE6-8.
    } catch(e) {
        hidden = document.createElement("input");
        hidden.setAttribute("name", "javax.faces.ViewState");
    }

    hidden.setAttribute("type", "hidden");
    hidden.setAttribute("value", viewState);
    hidden.setAttribute("autocomplete", "off");
    form.appendChild(hidden);
}

Just include it as <h:outputScript name="some.js" target="head"> inside the <h:body> of the error page. If you can't guarantee that the page uses JSF <f:ajax>, then you might want to add an additional if (typeof jsf !== 'undefined') check before jsf.ajax.addOnEvent() call.

The JSF component library PrimeFaces has already solved this issue in its core ajax engine, so if you happen to use it already, you might want to replace all <f:ajax> links/buttons by the PrimeFaces ones.

See also:

Community
  • 1
  • 1
BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
  • Thanks for the fast answer. Strangely the error page does not contain a javax.faces.ViewState element when rendered through the exception handler. So document.getElementsByName("javax.faces.ViewState") does not find anything. The element is present on the previous page (that has thrown the exception) and if I open the error page manually. I'm using Richfaces a4j:commandbutton if that makes any difference. – Markus Nov 12 '12 at 16:39
  • Oh right, that was a bit silly in case an `@all` render is used instead of a partial render, I fixed the example in the answer. – BalusC Nov 12 '12 at 16:54
  • Great this works. So data.responseXML contains the ajax response from the exception handler. Ok, thanks again. This really helped a lot. – Markus Nov 12 '12 at 17:09
  • This does not seem to work for Firefox as getElementById() only works if the attribute is specified as ID in a DTD. I've now used something getElementsByName("update") and loop through them to check for the right id. – Markus Nov 15 '12 at 17:06