13

I am registering a change event listener for every input in the HTML using jQuery as below:

<h:head>
    <script type="text/javascript">
        //<![CDATA[
        $(document).ready(function(){
            $(':input').each(function() {
                $(this).change(function() {
                    console.log('From docReady: ' + this.id);
                });
            });
        });

        function changeHandler() {
            console.log("from changeHandler");
        }
        //]]>
    </script>
</h:head>
<h:body>
    <h:form id="topForm">
        <p:commandButton id="myButton" value="update"
                         action="#{testBean.submit}"
                         partialSubmit="true" process=":myForm:panel"
                         update=":myForm:panel" />
    </h:form>
    <h:form id="myForm">
        <p:panel id="panel">
            <p:inputTextarea id="myTextarea"
                             value="#{testBean.val}"
                             onchange="changeHandler();"/>
        </p:panel>
    </h:form>
</h:body>

Both change events are being triggered if the user changes the content of myTextarea. However after pressing the update button, which partially updates the myTextarea, only the changeHandler is triggering afterwards. The event bound in $(document).ready() is not triggering anymore.

Is this PrimeFaces related and/or an expected behavior? If yes then how can I ensure to trigger the second event without rerunning document ready script again.

BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
Uluk Biy
  • 48,655
  • 13
  • 146
  • 153

1 Answers1

31

As to the cause of your problem, the ajax request will update the HTML DOM tree with new HTML elements from the ajax response. Those new HTML elements do —obviously— not have the jQuery event handler function attached. However, the $(document).ready() isn't re-executed on ajax requests. You need to manually re-execute it.

This can be achieved in various ways. The simplest way is to use $(document).on(event, selector, function) instead of $(selector).on(event, function). This is tied to the document and the given functionRef is always invoked when the given eventName is triggered on an element matching the given selector. So you never need to explicitly re-execute the function by JSF means.

$(document).on("change", ":input", function() {
    console.log("From change event on any input: " + this.id);
});

The alternative way is to explicitly re-execute the function yourself on complete of ajax request. This would be the only way when you're actually interested in immediately execute the function during the ready/load event (e.g. to directly apply some plugin specific behavior/look'n'feel, such as date pickers). First, you need to refactor the $(document).ready() job into a reusable function as follows:

$(document).ready(function(){
    applyChangeHandler();
});

function applyChangeHandler() {
    $(":input").on("change", function() {
        console.log("From applyChangeHandler: " + this.id);
    });
}

(note that I removed and simplified your completely unnecessary $.each() approach)

Then, choose one of the following ways to re-execute it on complete of ajax request:

  1. Use the oncomplete attribute of the PrimeFaces command button:

    oncomplete="applyChangeHandler()"
    
  2. Use <h:outputScript target="body"> instead of $(document).ready(),

    <h:outputScript id="applyChangeHandler" target="body">
        applyChangeHandler();
    </h:outputScript>
    

    and reference it in update attribute:

    update=":applyChangeHandler"
    
  3. Use <p:outputPanel autoUpdate="true"> to auto update it on every ajax request:

    <p:outputPanel autoUpdate="true">
        <h:outputScript id="applyChangeHandler">
            applyChangeHandler();
        </h:outputScript>
    </p:outputPanel>
    
  4. Use OmniFaces <o:onloadScript> instead of $(document).ready(), <h:outputScript> and all on em.

    <o:onloadScript>applyChangeHandler();</o:onloadScript>
    
BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
  • Or you could use [event delegation](http://api.jquery.com/on/#direct-and-delegated-events), which is the technique designed specifically to solve this kind of problem. – Anthony Grist Jan 18 '13 at 14:18
  • @Anthony: That works only on ajax requests which are executed via "plain jQuery" and on HTML DOM elements which are attached via "plain jQuery" because it has its own internal event listeners therefor. – BalusC Jan 18 '13 at 14:19
  • All of my experience tells me neither of those statements is correct. – Anthony Grist Jan 18 '13 at 14:31
  • @BalusC. Thanks for posting. It seems the 3rd choice is suitable for my use case. I am planning to put that code block in the facelet template. And for this 3rd way, I removed $(document).ready(function(){} otherwise the handler is registered twice on initial page load, then put the autoUpdatable p:outputPanel to the end of the body manually. If I eventually find myself implementing the functionalities that Omnifaces already offers, definitely will give it a try. :) – Uluk Biy Jan 18 '13 at 18:03
  • @BalusC. Is there any way to just execute the script on newly added elements only after ajax response? Otherwise the handler is bound to the element again and again. – Uluk Biy Jan 22 '13 at 17:55
  • You could use a more specific selector or use `$.off()` first and then `$.on()`. – BalusC Jan 22 '13 at 18:15
  • Note that I improved the answer as per new jQuery's `$(document).on()`. – BalusC Oct 08 '13 at 12:19
  • @BalusC I bet my upvote brought you here to update it. I liked 3rd way described before, you shouldnt have deleted it :P – hendrix Oct 08 '13 at 12:31
  • @smitalm: fair point, I undeleted it and instead put it as "update". – BalusC Oct 08 '13 at 12:33
  • @BalusC it's the best answer I've seen in that topic, Actually one upvote is not enough for you :) – Dorgham May 25 '14 at 13:16