Search code examples
javascriptjqueryevent-handlingtimeoutjquery-events

How to run a function once when binding to multiple events that all trigger in Javascript?


I have a search input that listens to keyup and change to trigger an update of a listview via Ajax.

Looks like this:

input.on('keyup change', function(e) {
    if (timer) {
        window.clearTimeout(timer);
    }
    timer = window.setTimeout( function() {
        timer = null;
        val = input.val();
        el = input.closest('ul');
        // run a function - triggers Ajax
        widget[func](dyn, el, lib_template, locale, val, "update");
    }, interval );
});

All working nice, except the handling of the timeout and binding, which causes double Ajax requests to be placed instead of a single one (when the keyup has passed, the change event triggers the same Ajax request again).

I can "fix" this by adding another timeout:

var runner = false;

input.on('keyup change', function(e) {
    if ( runner === false ){
        runner = true;
        if (timer) {
            window.clearTimeout(timer);
        }
        timer = window.setTimeout( function() {
            timer = null;
            val = input.val();
            el = input.closest('ul');
            widget[func](dyn, el, lib_template, locale, val, "update");
            // ssh....
            window.setTimeout( function(){ runner = false; },2500);
        }, interval );
    }
});

But this is not nice at all...

Question:
How can I make sure with two binding that both fire, that the function I need only runs once?

EDIT:
The Ajax call is triggered here:

widget[func](dyn, el, lib_template, locale, val, "update");

which calls this function to build a dynamic listview

buildListView : function( dyn,el,lib_template,locale,val,what ){
    ...
    // this calls my AJax Config "getUsers"
    $.parseJSON( dynoData[ dyn.method ](cbk, val, dyn.display) );

 });

 // config AJAX
 getUsers: function(cbk, val, recs){
  var form = "",
  pullRetailers = ( val === undefined ? "" : val ),
  service = "../services/some.cfc",
  method = "by",
  returnformat = "json",
  targetUrl = "",
  formdata = "...manually_serialized...,
  successHandler = function(objResponse, cbk) {
     cbk( objResponse );
  };
  // finally pass to the generic JSON handler
  ajaxFormSubmit( form, service, formdata, targetUrl, successHandler, "yes", "", returnformat, cbk );
}

// generic AJAX
var ajaxFormSubmit = 
    function ( form, service, formdata, targetUrl, successHandler, dataHandler, errorHandler, returnformat, type ){
    ...

    $.ajax({
        async: false,
        type: type == "" ? "get" : type,
        url: service,
        data: formdata,
        contentType: 'application/x-www-form-urlencoded',
        dataType: returnformat,
        success: function( objResponse ){
            if (objResponse.SUCCESS == true || typeof objResponse === "string" ){
                dataHandler == "yes" ? successHandler( objResponse, override ) : successHandler( override );
            }
        },  
        error: function (jqXHR, XMLHttpRequest, textStatus, errorThrown) { }
     });
}

But this does not help a lot regarding the actual question of how to prevent both events from triggering my Ajax Update.


Solution

  • I would try to set up a value-checking function like this:

    var $inputIntance = $("#path-to-your-input");
    var lastInputValue;
    
    function checkInputValue () {
        var newValue = $inputIntance.val();
        if (newValue != lastInputValue) {
            // make your AJAX call here
            lastInputValue = newValue;
            el = $inputIntance.closest('ul');
            widget[func](dyn, el, lib_template, locale, lastInputValue, "update");
        }
    }
    

    and then then fire this checks by any user-action event you like:

    $inputIntance.on('keyup change', function(e) {
        checkInputValue();
    }
    

    or like this

    $inputIntance.on('keyup change', checkInputValue );
    

    UPDATE: there might be the case when you have to limit the number of AJAX requests per time. I added time control functionality to my previous code. You can find the code below and try it live here in JSFiddle.

    $(document).ready(function () {
        var $inputIntance = $("#test-input");
        var lastInputValue;
        var valueCheckTimer;
        var MIN_TIME_BETWEEN_REQUESTS = 100; //100ms
        var lastCheckWasAt = 0;
    
        function checkInputValue () {
            lastCheckWasAt = getTimeStamp();
            var newValue = $inputIntance.val();
            if (newValue != lastInputValue) {
                // make your AJAX call here
                lastInputValue = newValue;
                $("#output").append("<p>AJAX request on " + getTimeStamp() + "</p>");
                //el = $inputIntance.closest('ul');
                //widget[func](dyn, el, lib_template, locale, lastInputValue, "update");
            }
        }
    
        function getTimeStamp () {
            return (new Date()).getTime();
        }
    
        function checkInputValueScheduled() {
            if (valueCheckTimer) { // check is already planned: it will be performed in MIN_TIME_BETWEEN_REQUESTS
                return;
            } else { // no checks planned
                if  ((getTimeStamp() - lastCheckWasAt) > MIN_TIME_BETWEEN_REQUESTS) { // check was more than MIN_TIME_BETWEEN_REQUESTS ago
                    checkInputValue();
                } else { // check was not so much time ago - schedule new check in MIN_TIME_BETWEEN_REQUESTS
                    valueCheckTimer = window.setTimeout(
                        function () {
                            valueCheckTimer = null;
                            checkInputValue();
                        }, 
                        MIN_TIME_BETWEEN_REQUESTS
                    );
                }
            }
        }
    
        $inputIntance.bind('keyup change', function(e) {
            $("#output").append("<p>input event captured</p>");
            checkInputValueScheduled();
        });
    });