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

雨燕双飞 提交于 2019-12-01 06:32:27

问题


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?

Thanks!

EDIT:
The Ajax call is triggered here:

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

which calls this function to build a dynmic 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.


回答1:


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



回答2:


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.




回答3:


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.




回答4:


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.



来源:https://stackoverflow.com/questions/13900093/how-to-run-a-function-once-when-binding-to-multiple-events-that-all-trigger-in-j

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!