Asked  7 Months ago    Answers:  5   Viewed   358 times

We have an ajax navigation menu which updates a dynamic include. The include files have each their own forms.

<h:form>
    <h:commandButton value="Add" action="#{navigator.setUrl('AddUser')}">
        <f:ajax render=":propertiesArea" />
    </h:commandButton>
</h:form>
<h:panelGroup id="propertiesArea" layout="block">
    <ui:include src="#{navigator.selectedLevel.url}" />
</h:panelGroup>

It works correctly, but any command button in the include file doesn't work on first click. It works only on second click and forth.

I found this question commandButton/commandLink/ajax action/listener method not invoked or input value not updated and my problem is described in point 9. I understand that I need to explicitly include the ID of the <h:form> in the include in the <f:ajax render> to solve it.

<f:ajax render=":propertiesArea :propertiesArea:someFormId" />

In my case, however, the form ID is not known beforehand. Also this form will not be available in the context initally.

Is there any solution to the above scenario?

 Answers

44

You can use the following script to fix the Mojarra 2.0/2.1/2.2 bug (note: this doesn't manifest in MyFaces). This script will create the javax.faces.ViewState hidden field for forms which did not retrieve any view state after ajax update.

jsf.ajax.addOnEvent(function(data) {
    if (data.status == "success") {
        fixViewState(data.responseXML);
    }
});

function fixViewState(responseXML) {
    var viewState = getViewState(responseXML);

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

            if (form.method == "post") {
                if (!hasViewState(form)) {
                    createViewState(form, viewState);
                }
            }
            else { // PrimeFaces also adds them to GET forms!
                removeViewState(form);
            }
        }
    }
}

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

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

        if (update.getAttribute("id").match(/^([w]+:)?javax.faces.ViewState(:[0-9]+)?$/)) {
            return update.textContent || update.innerText;
        }
    }

    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);
}

function removeViewState(form) {
    for (var i = 0; i < form.elements.length; i++) {
        var element = form.elements[i];
        if (element.name == "javax.faces.ViewState") {
            element.parentNode.removeChild(element);
        }
    }
}

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 in question uses JSF <f:ajax>, which would trigger auto-inclusion of jsf.js, then you might want to add an additional if (typeof jsf !== 'undefined') check before jsf.ajax.addOnEvent() call, or to explicitly include it by

<h:outputScript library="javax.faces" name="jsf.js" target="head" />

Note that jsf.ajax.addOnEvent only covers standard JSF <f:ajax> and not e.g. PrimeFaces <p:ajax> or <p:commandXxx> as they use under the covers jQuery for the job. To cover PrimeFaces ajax requests as well, add the following:

$(document).ajaxComplete(function(event, xhr, options) {
    if (typeof xhr.responseXML != 'undefined') { // It's undefined when plain $.ajax(), $.get(), etc is used instead of PrimeFaces ajax.
        fixViewState(xhr.responseXML);
    }
}

Update if you're using JSF utility library OmniFaces, it's good to know that the above has since 1.7 become part of OmniFaces. It's just a matter of declaring the following script in the <h:body>. See also the showcase.

<h:body>
    <h:outputScript library="omnifaces" name="fixviewstate.js" target="head" />
    ...
</h:body>
Tuesday, June 1, 2021
 
juanrpozo
answered 7 Months ago
28

Introduction

Whenever an UICommand component (<h:commandXxx>, <p:commandXxx>, etc) fails to invoke the associated action method, or an UIInput component (<h:inputXxx>, <p:inputXxxx>, etc) fails to process the submitted values and/or update the model values, and you aren't seeing any googlable exceptions and/or warnings in the server log, also not when you configure an ajax exception handler as per Exception handling in JSF ajax requests, nor when you set below context parameter in web.xml,

<context-param>
    <param-name>javax.faces.PROJECT_STAGE</param-name>
    <param-value>Development</param-value>
</context-param>

and you are also not seeing any googlable errors and/or warnings in browser's JavaScript console (press F12 in Chrome/Firefox23+/IE9+ to open the web developer toolset and then open the Console tab), then work through below list of possible causes.

Possible causes

  1. UICommand and UIInput components must be placed inside an UIForm component, e.g. <h:form> (and thus not plain HTML <form>), otherwise nothing can be sent to the server. UICommand components must also not have type="button" attribute, otherwise it will be a dead button which is only useful for JavaScript onclick. See also How to send form input values and invoke a method in JSF bean and <h:commandButton> does not initiate a postback.

  2. You cannot nest multiple UIForm components in each other. This is illegal in HTML. The browser behavior is unspecified. Watch out with include files! You can use UIForm components in parallel, but they won't process each other during submit. You should also watch out with "God Form" antipattern; make sure that you don't unintentionally process/validate all other (invisible) inputs in the very same form (e.g. having a hidden dialog with required inputs in the very same form). See also How to use <h:form> in JSF page? Single form? Multiple forms? Nested forms?.

  3. No UIInput value validation/conversion error should have occurred. You can use <h:messages> to show any messages which are not shown by any input-specific <h:message> components. Don't forget to include the id of <h:messages> in the <f:ajax render>, if any, so that it will be updated as well on ajax requests. See also h:messages does not display messages when p:commandButton is pressed.

  4. If UICommand or UIInput components are placed inside an iterating component like <h:dataTable>, <ui:repeat>, etc, then you need to ensure that exactly the same value of the iterating component is been preserved during the apply request values phase of the form submit request. JSF will reiterate over it to find the clicked link/button and submitted input values. Putting the bean in the view scope and/or making sure that you load the data model in @PostConstruct of the bean (and thus not in a getter method!) should fix it. See also How and when should I load the model from database for h:dataTable.

  5. If UICommand or UIInput components are included by a dynamic source such as <ui:include src="#{bean.include}">, then you need to ensure that exactly the same #{bean.include} value is preserved during the view build time of the form submit request. JSF will reexecute it during building the component tree. Putting the bean in the view scope and/or making sure that you load the data model in @PostConstruct of the bean (and thus not in a getter method!) should fix it. See also How to ajax-refresh dynamic include content by navigation menu? (JSF SPA).

  6. The rendered attribute of the component and all of its parents and the test attribute of any parent <c:if>/<c:when> should not evaluate to false during the apply request values phase of the form submit request. JSF will recheck it as part of safeguard against tampered/hacked requests. Storing the variables responsible for the condition in a @ViewScoped bean or making sure that you're properly preinitializing the condition in @PostConstruct of a @RequestScoped bean should fix it. The same applies to the disabled and readonly attributes of the component, which should not evaluate to true during apply request values phase. See also JSF CommandButton action not invoked, Form submit in conditionally rendered component is not processed, h:commandButton is not working once I wrap it in a <h:panelGroup rendered> and Force JSF to process, validate and update readonly/disabled input components anyway

  7. The onclick attribute of the UICommand component and the onsubmit attribute of the UIForm component should not return false or cause a JavaScript error. There should in case of <h:commandLink> or <f:ajax> also be no JS errors visible in the browser's JS console. Usually googling the exact error message will already give you the answer. See also Manually adding / loading jQuery with PrimeFaces results in Uncaught TypeErrors.

  8. If you're using Ajax via JSF 2.x <f:ajax> or e.g. PrimeFaces <p:commandXxx>, make sure that you have a <h:head> in the master template instead of the <head>. Otherwise JSF won't be able to auto-include the necessary JavaScript files which contains the Ajax functions. This would result in a JavaScript error like "mojarra is not defined" or "PrimeFaces is not defined" in browser's JS console. See also h:commandLink actionlistener is not invoked when used with f:ajax and ui:repeat.

  9. If you're using Ajax, and the submitted values end up being null, then make sure that the UIInput and UICommand components of interest are covered by the <f:ajax execute> or e.g. <p:commandXxx process>, otherwise they won't be executed/processed. See also Submitted form values not updated in model when adding <f:ajax> to <h:commandButton> and Understanding PrimeFaces process/update and JSF f:ajax execute/render attributes.

  10. If the submitted values still end up being null, and you're using CDI to manage beans, then make sure that you import the scope annotation from the correct package, else CDI will default to @Dependent which effectively recreates the bean on every single evaluation of the EL expression. See also @SessionScoped bean looses scope and gets recreated all the time, fields become null and What is the default Managed Bean Scope in a JSF 2 application?

  11. If a parent of the <h:form> with the UICommand button is beforehand been rendered/updated by an ajax request coming from another form in the same page, then the first action will always fail in JSF 2.2 or older. The second and subsequent actions will work. This is caused by a bug in view state handling which is reported as JSF spec issue 790 and currently fixed in JSF 2.3. For older JSF versions, you need to explicitly specify the ID of the <h:form> in the render of the <f:ajax>. See also h:commandButton/h:commandLink does not work on first click, works only on second click.

  12. If the <h:form> has enctype="multipart/form-data" set in order to support file uploading, then you need to make sure that you're using at least JSF 2.2, or that the servlet filter who is responsible for parsing multipart/form-data requests is properly configured, otherwise the FacesServlet will end up getting no request parameters at all and thus not be able to apply the request values. How to configure such a filter depends on the file upload component being used. For Tomahawk <t:inputFileUpload>, check this answer and for PrimeFaces <p:fileUpload>, check this answer. Or, if you're actually not uploading a file at all, then remove the attribute altogether.

  13. Make sure that the ActionEvent argument of actionListener is an javax.faces.event.ActionEvent and thus not java.awt.event.ActionEvent, which is what most IDEs suggest as 1st autocomplete option. Having no argument is wrong as well if you use actionListener="#{bean.method}". If you don't want an argument in your method, use actionListener="#{bean.method()}". Or perhaps you actually want to use action instead of actionListener. See also Differences between action and actionListener.

  14. Make sure that no PhaseListener or any EventListener in the request-response chain has changed the JSF lifecycle to skip the invoke action phase by for example calling FacesContext#renderResponse() or FacesContext#responseComplete().

  15. Make sure that no Filter or Servlet in the same request-response chain has blocked the request fo the FacesServlet somehow. For example, login/security filters such as Spring Security. Particularly in ajax requests that would by default end up with no UI feedback at all. See also Spring Security 4 and PrimeFaces 5 AJAX request handling.

  16. If you are using a PrimeFaces <p:dialog> or a <p:overlayPanel>, then make sure that they have their own <h:form>. Because, these components are by default by JavaScript relocated to end of HTML <body>. So, if they were originally sitting inside a <form>, then they would now not anymore sit in a <form>. See also p:commandbutton action doesn't work inside p:dialog

  17. Bug in the framework. For example, RichFaces has a "conversion error" when using a rich:calendar UI element with a defaultLabel attribute (or, in some cases, a rich:placeholder sub-element). This bug prevents the bean method from being invoked when no value is set for the calendar date. Tracing framework bugs can be accomplished by starting with a simple working example and building the page back up until the bug is discovered.

Debugging hints

In case you still stucks, it's time to debug. In the client side, press F12 in webbrowser to open the web developer toolset. Click the Console tab so see the JavaScript conosle. It should be free of any JavaScript errors. Below screenshot is an example from Chrome which demonstrates the case of submitting an <f:ajax> enabled button while not having <h:head> declared (as described in point 7 above).

js console

Click the Network tab to see the HTTP traffic monitor. Submit the form and investigate if the request headers and form data and the response body are as per expectations. Below screenshot is an example from Chrome which demonstrates a successful ajax submit of a simple form with a single <h:inputText> and a single <h:commandButton> with <f:ajax execute="@form" render="@form">.

network monitor

(warning: when you post screenshots from HTTP request headers like above from a production environment, then make sure you scramble/obfuscate any session cookies in the screenshot to avoid session hijacking attacks!)

In the server side, make sure that server is started in debug mode. Put a debug breakpoint in a method of the JSF component of interest which you expect to be called during processing the form submit. E.g. in case of UICommand component, that would be UICommand#queueEvent() and in case of UIInput component, that would be UIInput#validate(). Just step through the code execution and inspect if the flow and variables are as per expectations. Below screenshot is an example from Eclipse's debugger.

debug server

Tuesday, June 1, 2021
 
viper
answered 7 Months ago
82

The action can point two things:

  1. A method expression action="#{bean.methodname}" where the method look like this:

    @ManagedBean
    @RequestScoped
    public class Bean {
        public String methodname() {
            // Do some business task here.
            return "response";
        }
    }
    

    After executing the method the action will effectively end up containing the return value of the method, like so: action="response".

    You can also control the outcome "dynamically" the usual Java way:

    public String methodname() {
        if (someCondition) {
            return "somepage";
        } else {
            return "anotherpage";
        }
    }
    

    Depending on the condition outcome, the action will end up like action="somepage" or action="anotherpage"

  2. Another XHTML page in the same folder as the current XHTML page. You just have to specify the filename: action="response".

Either way, it will go to the XHTML page which is composed by outcome + ".xhtml" where outcome is the action value (e.g. response.xhtml, somepage.xhtml or anotherpage.xhtml) which is supposed to be in the same folder as the XHTML file containing the h:commandButton.

You don't need to configure anything in faces-config.xml for this. Previously, during JSF 1.x ages you would need to define <navigation-case> for this.

See also:

  • We don't need no stinkin' faces-config.xml! - blog by lead Mojarra developer.
Friday, July 30, 2021
 
Hat
answered 5 Months ago
Hat
84

This is a longshot, but are you running some sort of tracking script? Like webtrends or coremetrics (or even some of your own script, that's globally looking for all clicks)? I ran into a similar problem a while ago, where the initial-click was being captured by coremetrics. Just a thought.

Wednesday, September 29, 2021
 
BlueNile
answered 2 Months ago
96

I have fixed it, and I detail the problem if any user will find himself in the same condition.

The problem was that in the manifest.json I had entered a start url for Google Analytics:

 "start_url": "/meteosurf/?utm_source=mobile&utm_medium=device&utm_campaign=standalone",

When offline, the application didn't know what to do, so it displayed the "No Connection" screen.

In order to fix it, I have added an error function in the service worker fetch funtion, to redirect the app to index.html. This way:

self.addEventListener('fetch', function(event) {
  console.log('Fetch event for ', event.request.url);
  event.respondWith(
    caches.match(event.request).then(function(response) {
      if (response) {
        console.log('Found ', event.request.url, ' in cache');
        return response;
      }
      console.log('Network request for ', event.request.url);
      return fetch(event.request)

    }).catch(function(error) {

      return caches.match('index.html');

    })
  );
});
Tuesday, October 19, 2021
 
Neil Stockton
answered 2 Months ago
Only authorized users can answer the question. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :
 
Share