0
  1. User clicks on search box
  2. User types a surname
  3. If the texbox lengh is more than 2 characters ajax webservice is called

The problem I have is with slow databases with thousands of records...

I can type "mcm" and wait and it will return 40 results in 3 seconds. I can continue to type "mcmil" and it will return 11 results in 1 second.

The problem is if I type "mcmil" all at once from scratch, i can visibly see the first 11 results then the results jump as the results for "mcm" and "mcmi" are loaded in, presumably as they are slower.

When the .keyup fires, I need a way to detect if a .ajax request is currently being made and cancel it before making the new one.

$('#employeesearchbox').keyup(function () {

    if ($("#employeesearchbox").val().length > 2) {

        $.ajax({
            type: 'POST',
            url: './webservices/Contacts.asmx/ContactsDirectorySearch',
            data: JSON.stringify({ Surname: $("#employeesearchbox").val() }),
            contentType: 'application/json; charset=utf-8',
            dataType: 'json',
            success: function (msg) {
                var employees = msg.d;
                $.each(employees, function (index, employee) {
                    $('#message').append("<li><a href='javascript:void(0);' class='response_object'>" + employee.Firstname + " " + employee.Department + "</a></li>");
                });
            },
            error: function (xhr, ajaxOptions, thrownError) {
                console.error("The error was: " + xhr.responseText);
            }
        });

    }

});

I had a look at this post: abort-ajax-requests-using-jquery

It cancels the request directly after the .ajax where as i probably want to test on next key press and when i add this code i get "object undefined" error - as the object doesnt already exist!

please help

thanks

var xhr = $.ajax({
 ...
});

//kill the request
xhr.abort()
Community
  • 1
  • 1
Scott
  • 1,280
  • 4
  • 20
  • 35
  • maybe add a small delay before triggering a request? – Robert Oct 01 '15 at 13:16
  • possibly, but i'd rather clear the previous request before making a new one. Some searches can take up to 5-6 seconds so i'd rather not add a delay to cover that as the system could appear unresponsive and frustrating! – Scott Oct 01 '15 at 13:19
  • You might want to look into typeahead (https://twitter.github.io/typeahead.js/examples/). It handles this for you the timing requests for you and displays the results to the user. It also limits the number of requests sent so that your database isn't hit too often. – bassxzero Oct 01 '15 at 13:20
  • The problem is that after you have fired the ajax request the server is going to go on regardless. I think all the abort() does is stop the callback being fired. I have a lookup that does i-search on 100,000+ records but I don't fire off the request until I have 4 letters typed. I haven't noticed these kinds of race conditions. Is that something you can consider? (Edit OK I see that you wait for two characters, waiting for 4 will considerably reduce the response volume) – david sinfield Oct 01 '15 at 13:23
  • 2
    was your `var xhr = $.ajax({` inside keyup scope, or outside? if your variable declaration was inside scope, you could not cancel it, cause it does not exist. – Robert Oct 01 '15 at 13:23
  • Aborting request client side doesn't stop server processing previous requests. Instead, debounce your ajax request – A. Wolff Oct 01 '15 at 13:24
  • I'd be thinking about putting your ajax in maybe a half second `setTimeout` which is cleared with each new keypress. Most people will type more quickly than that. Also look at the answers to this question - http://stackoverflow.com/questions/4098678/average-inter-keypress-time-when-typing and maybe look at the link in the comments of the accepted answer. – John C Oct 01 '15 at 13:34
  • hi @Robert - yes you are right, when i put the the outsite keyup scope my code is working and previous requests are being cancelled, thanks – Scott Oct 01 '15 at 13:44
  • @JohnC Hi John, thanks for the info, i've added a setTimout with a half second delay and corresponding clearTimeout to cancel unnecessary lookups to the db while the user is typing. – Scott Oct 01 '15 at 14:46
  • @Scott Glad it helped, I've added an answer along the same lines. – John C Oct 01 '15 at 15:43

3 Answers3

1

To expand on my comment -

I'd be thinking about putting your ajax in maybe a half second setTimeout which is cleared with each new keypress. Most people will type more quickly than that. Also look at the answers to this question - Average Inter-Keypress time when typing and maybe look at the link in the comments of the accepted answer

For example -

var delaySearch;
$('#employeesearchbox').keyup(function () {
  if ($("#employeesearchbox").val().length > 2) {
    clearTimeout(delaySearch);
    delaySearch = setTimeout(function() {
      $.ajax({
        type: 'POST',
        url: './webservices/Contacts.asmx/ContactsDirectorySearch',
        data: JSON.stringify({ Surname: $("#employeesearchbox").val() }),
        contentType: 'application/json; charset=utf-8',
        dataType: 'json',
        success: function (msg) {
          var employees = msg.d;
          $.each(employees, function (index, employee) {
            $('#message').append("<li><a href='javascript:void(0);' class='response_object'>" + employee.Firstname + " " + employee.Department + "</a></li>");
          });
        },
        error: function (xhr, ajaxOptions, thrownError) {
          console.error("The error was: " + xhr.responseText);
        }
      });
    }, 500);
  }
});

I've set the timeout to 500ms but but you might want to go a little longer depending on the performance of your database and your intended audience.

Community
  • 1
  • 1
John C
  • 3,052
  • 3
  • 34
  • 47
  • Hi John, thanks for the example, i have various extra bits of code that i didn't post to keep my example short and to the point. I've actually seperated my db call away from the logic, but utimately there's more than one way to skin a cat here. Was interesting to see you put the full ajax part in the setTimeOut :) I went with this timer = setTimeout(searchDatabase, 500); then cleared the timer inside the .keyup – Scott Oct 02 '15 at 13:18
0

What about something along these lines? Create an id for each request sent pass it to the server and send it back from the server. Only update the document if the request isn't too old.

I still think you should consider debouncing these requests Basically, don't send the request until a 300 milliseconds until after they are done typing or something.

    var requestid = 0;
    $('#employeesearchbox').keyup(function () {

        if ($("#employeesearchbox").val().length > 2) {
            requestid++;
            $.ajax({
                type: 'POST',
                url: './webservices/Contacts.asmx/ContactsDirectorySearch',
                data: JSON.stringify({ Surname: $("#employeesearchbox").val(), requestid: requestid }),
                contentType: 'application/json; charset=utf-8',
                dataType: 'json',
                error: function (xhr, ajaxOptions, thrownError) {
                    console.error("The error was: " + xhr.responseText);
                }
            }).done(function(msg){
                if(msg.requestid >= requestid){
                    var employees = msg.d;
                    $.each(employees, function (index, employee) {
                        $('#message').append("<li><a href='javascript:void(0);' class='response_object'>" + employee.Firstname + " " + employee.Department + "</a></li>");
                    });  
                }
            });

        }

    });
bingo
  • 2,298
  • 1
  • 15
  • 21
  • 1
    There are threads on debouncing, too, http://stackoverflow.com/questions/23493726/how-to-properly-debounce-ajax-requests – bingo Oct 01 '15 at 13:37
  • Did you find my answer useful at all? – bingo Oct 01 '15 at 14:10
  • Hi @bingo, yes it was useful, i've added a setTimeout function to act as a buffer between keypresses and calls to the database so there's now a half second delay while the user types. thanks timer = setTimeout(searchDatabase, 500); – Scott Oct 01 '15 at 14:43
  • Great! Will you please upvote my comment? Trying to level up a bit over here! :) I just upvoted Roberts comment, which is actually better than mine overall. – bingo Oct 01 '15 at 14:58
0

Thanks to @robert, here's the working code.

xhr is declaired as null outsite .keyup scope after keypress, i can check if it's not equal null and cancel it with .abort()

var xhr = null;

$('#employeesearchbox').keyup(function () {

    if ($("#employeesearchbox").val().length > 2) {

        if (xhr == null) {
            console.error("null");
        } else {
            //kill the request
            xhr.abort()
        }

        xhr = $.ajax({
            type: 'POST',
            url: './webservices/Contacts.asmx/ContactsDirectorySearch',
            data: JSON.stringify({ Surname: $("#employeesearchbox").val() }),
            contentType: 'application/json; charset=utf-8',
            dataType: 'json',
            success: function (msg) {
                var employees = msg.d;
                $.each(employees, function (index, employee) {
                    $('#message').append("<li><a href='javascript:void(0);' class='response_object'>" + employee.Firstname + " " + employee.Department + "</a></li>");
                });
            },
            error: function (xhr, ajaxOptions, thrownError) {
                console.error("The error was: " + xhr.responseText);
            }
        });

    }

});
Scott
  • 1,280
  • 4
  • 20
  • 35