Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JavaScript/jQuery event listeners do not work after JSF component is updated via Ajax

I'm executing the following jQuery function on <p:dataTable> filter (whose id is id) that allows users to enter only digits in the filter component.

$(document).ready(function() {
    $("#form\\:dataTable\\:id\\:filter").keydown(function(event) {
        //Allow: tab, escape, and enter
        if(event.keyCode===9||event.keyCode===27||event.keyCode===13||
            //Allow: Ctrl+A, Ctrl+C
            (event.keyCode===65&&event.ctrlKey===true)||(event.keyCode===67&&event.ctrlKey===true)||
            //Allow: home, end, left, right
            (event.keyCode>=35&&event.keyCode<=39)){
            //let it happen, don't do anything
            event.preventCapture();
            return;
        }//backspace, delete
        else if(event.keyCode===46||event.keyCode===8)
        {
            return;
        }
        else{//Ensure that it is a number and stop the keypress
            if (event.shiftKey||(event.keyCode<48||event.keyCode>57)&&(event.keyCode< 96||event.keyCode>105)){
                //event.preventDefault();
                event.preventCapture();
            }
        }
    });
});

This function is placed under context/resources/default/js/digit_only_textfield.js. Therefore, it can be used on XHTML pages like,

<h:outputScript library="default" name="js/digit_only_textfield.js"/>

The XHTML page looks like the following.

<h:outputScript library="default" name="js/digit_only_textfield.js"/>

<h:form id="form" prependId="true">

    <!--PrimeFaces extension <pe:blockUI>-->         

    <p:remoteCommand name="updateTable" update="dataTable"/>

    <p:panel id="panel">
        <h:panelGrid id="panelGrid" columns="3" cellpadding="5">

            <!--Some UIInput components-->

            <p:commandButton id="btnSubmit" 
                             update="panel" 
                             onstart="PF('blockUIWidget').block();" 
                             oncomplete="if(!args.validationFailed) {updateTable();}PF('blockUIWidget').unblock();" 
                             actionListener="#{bean.insert}" 
                             value="Save"/>
        </h:panelGrid>
    </p:panel>

    <p:dataTable id="dataTable" 
                 var="row" 
                 value="#{bean}"
                 filterEvent="keydown"

                 ...
                 ...  >

                 ...
                 ...
    <p:dataTable>
<h:form>

This jQuery works fine for the filter whose id is is but when this <p:dataTable> is updated by pressing the given <p:commandButton>, it stops functioning.

How to make this function work after <p:dataTable> is updated by AJAX?


A new problem domain introduced:

This question and corresponding replies on the PrimeFaces Community Forum still do not lead to a workaround/solution to the following problem domain.

if a wrong key is hit (i.e a non-digit key, except backspace, delete etc) then, the data table is updated unnecessarily that causes some costly queries to be fired upon the database which is completely unnecessary and the data table must be prevented from being updated.

like image 274
Tiny Avatar asked Nov 26 '13 21:11

Tiny


1 Answers

The flow is as follows:

  • Browser retrieves HTML output.
  • Browser populates HTML DOM tree based on HTML markup.
  • When finished, browser triggers HTML DOM ready event.
  • jQuery's $(document).ready() function handlers are all invoked.
  • Yours finds an element in DOM by ID and attaches a keydown listener to it.
  • During user interaction, an ajax request is fired and the HTML DOM tree is updated with new HTML elements delivered by the ajax response.
  • Among others, exactly that element having the keydown listener is removed from HTML DOM tree and replaced by a fresh new element without any keydown listener. The document ready event is not fired during ajax requests. Your ready handler is never re-invoked. The keydown listener is never re-attached. To the enduser, it then indeed seemingly "stops functioning".

The solution on this particular case should now be obvious: explicitly re-attach the keydown listener on complete of ajax call. Most straightforward would be to extract the job of attaching the keydown listener into a reusable function and fire it as follows:

function applyKeydownOnTableFilter() { 
    // ...
}

$(document).ready(applyKeydownOnTableFilter);

So that you can just do a:

<p:commandButton ... oncomplete="applyKeydownOnTableFilter()" />

But this is quite tedious to repeat for every single ajax command/listener and not very maintenance friendly. Better is to approach it differently: use jQuery's $.on() instead. Replace

$(document).ready(function() {
    $("#form\\:dataTable\\:id\\:filter").keydown(function(event) {
        // ...
    });
});

by

$(document).on("keydown", "#form\\:dataTable\\:id\\:filter", function(event) {
    // ...
});

This way the keydown listener isn't actually attached to the element of interest. Instead, thanks to the event bubbling feature of JavaScript, the keydown event will ultimately reach the $(document) — which is always present and usually not changed during JSF ajax requests. Once reached, the $(document).on() is triggered, which will then determine the source of the event and check it if matches the given selector and if so, then invoke the function. This all without the need to attach the keydown listener to the physical element and thus not sensitive to whether the element is removed/replaced in the HTML DOM tree.

See also:

  • JSF/PrimeFaces ajax updates breaks jQuery event listener function bindings

By the way, do you also see how much similarities there are between HTML DOM tree and JSF component tree?

like image 143
BalusC Avatar answered Oct 20 '22 17:10

BalusC