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

5

3

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.

Breannabreanne answered 16/12, 2012 at 9:39 Comment(1)
I don't see where the Ajax call is being done?Hygrothermograph
B
3

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();
    });
});
Bowden answered 16/12, 2012 at 10:24 Comment(1)
also nice. I will give it a tryBreannabreanne
S
1

Any reason for listening to keyUp in addition to change? Maybe what really matters is just one of these events.

If not, I guess you will have to use a closure as you suggested.

Stocks answered 16/12, 2012 at 9:51 Comment(2)
Anyway, I realise my answer gives a direction, but the question definitely makes sense in a another context or for other events.Stocks
Tested it some more. If I only use change, the event triggers very late... like on blur... So I will try to work something out with two events.Breannabreanne
J
1

I came across this problem before and what I ended up doing is saving the request on an object upon creation and test for a previously set one to abort it if necessary. You'd have to edit the function that triggers the ajax call. For example:

if ( ajaxRequest ) { 
  ajaxRequest.abort();
}
ajaxRequest = $.ajax({ ... }); // triggers ajax request and saves it

If the widget function returns an ajax object then you can probably assign that to the variable instead of modifying the original ajax request.

Jacket answered 16/12, 2012 at 9:55 Comment(0)
B
1

Ok. I got it (after a felt 1000 tries...).

Here is what works = triggers whichever event fires first, blocks the trailer:

var isEven;
input.on('keyup change', function(e) {
    isEven = true;
    if (timer) {
        window.clearTimeout(timer);
    }
    timer = window.setTimeout( function() {
        timer = null;
        val = input.val();
        el = input.closest('ul');
        // prevent double firing after interval passed
        if (isEven === true ){
            isEven = false;
        } else {
            isEven = true;
            return;
        }
        widget[func](dyn, el, lib_template, locale, val, "update");
    }, interval );
});

Seems to do what it should. Feel free to correct me, if it's wrong.

Breannabreanne answered 18/12, 2012 at 7:25 Comment(1)
great solutions with mutiple event or need inteval delay of same repete eventsCardcarrying
S
0

If one is triggered first, then delete the other.

const handler = e => {
  const eventType = e.type === 'mousedown' ? 'click' : 'mousedown';
  window.removeEventListener(eventType, handler);
};

window.addEventListener('click', handler);
window.addEventListener('mousedown', handler);
Sanjiv answered 18/9, 2020 at 14:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.