1

I am attempting to follow the advice within this question to fit the following scenario:

On keyup, send an AJAX request to the server with the term in the input field. But if they continue to type, abort any existing AJAX requests so only one request is sent.

Here is my code as it stands:

jQuery(document).ready(function($) {

    var $input      = $('#s'),
        inputVal    = '',
        inputCount  = '';

    function process_asr_request() {
        $.ajax({
            url: MyAjax.ajaxurl,
            data: {
                'action':'pondera_asr_request',
                'inputVal' : inputVal
            },
            success:function(data) {
                $('#search-results').append( data )
            },
            error: function(errorThrown){
                console.log( errorThrown);
            }
        });
    }

    $input.on("keyup", function() {
        // record the value of the input
        inputVal    = $input.val(),
        // check the lenght on the value
        inputCount  = inputVal.length,      
        // define array for ajax requests
        requests    = [];

        // Only process once there are 3 characters
        if (inputCount > 2) {
            // push ajax requests into the array
            requests.push(
                process_asr_request(i)
            );
            // loop through the array of requests
            for(
                var i = 0;
                i < requests.length;
                i++
            )
            // kill any queued requests
            requests[i].abort();
        };
    });
});

I have two questions:

  1. Is this approach valid for what I am looking to achieve
  2. I get an "Uncaught TypeError: Cannot call method 'abort' of undefined" error with the above code. Where am I going wrong.

I'm fairly new to AJAX so please pardon my naivety.

Community
  • 1
  • 1
Sheixt
  • 2,546
  • 12
  • 36
  • 65
  • 1
    Return the XHR in `process_asr_request()` – Johan Nov 12 '13 at 17:15
  • There's no need to abort it. You can simply ignore it, since AJAX is asynchronous anyway. Instead of `requests`, just use one `request` and on a keyup, just overwrite the previous request. – Overcode Nov 12 '13 at 17:16
  • for a better user interface, consider use a 333ms timeout and oninput instead of onkeyup – dandavis Nov 12 '13 at 17:46
  • From what I understand `oninput` isn't within jQuery yet: http://stackoverflow.com/questions/11189136/fire-oninput-event-with-jquery – Sheixt Nov 12 '13 at 18:00
  • As for the `setTimeout` suggestion, where within the code would you recommend this would be included? Within the `if (inputCount > 2) {` statement? – Sheixt Nov 12 '13 at 18:02

2 Answers2

4
var currXHR;

function process_asr_request() {

    if(currXHR && currXHR.abort) currXHR.abort();

    currXHR = $.ajax({
        url: MyAjax.ajaxurl,
        data: {
            'action':'pondera_asr_request',
            'inputVal' : inputVal
        },
        success:function(data) {
            $('#search-results').append( data )
        },
        error: function(errorThrown){
            console.log( errorThrown);
        }
    });
}
marekful
  • 14,986
  • 6
  • 37
  • 59
  • Ready state does not matter. When a new character is typed, a new request should be sent and any previous, regardless of state, should be cancelled. – marekful Nov 12 '13 at 17:18
  • In fact, the only case you don't need to cancel a request is when readyState IS 4... Because in that case there's nothing to cancel. – marekful Nov 12 '13 at 17:19
  • `currXHR` will refer to the previous XHR, if it exists. If the readyState is == 4 you can ignore it, otherwise you need to cancel it. – Johan Nov 12 '13 at 17:22
  • Ah OK, I misread the condition... Yeah, but if readyState is 4, abort will just silently do nothing (not trigger error). – marekful Nov 12 '13 at 17:23
  • In this example, would I be correct in assuming the array & for loop are no longer required? – Sheixt Nov 12 '13 at 17:37
  • if `process_asr_request()` returns the XHR object as suggested by another answer, it is correct with the `requests` array and the for loop, however, the solution I proposed is simpler and in this case `requests` and the for loop is not needed. – marekful Nov 12 '13 at 17:40
  • I see. I have this working as well as the solution above. Upon reviewing the network latency for both scripts, this seems to take a little longer. Is there any benefits of this approach compared to @megawac – Sheixt Nov 12 '13 at 17:55
4

Close, just need to return the xhr so you can abort it:

 function process_asr_request(inputVal) {
    return $.ajax({
        url: MyAjax.ajaxurl,
        data: {
            'action':'pondera_asr_request',
            'inputVal' : inputVal
        },
        success:function(data) {
            $('#search-results').append( data )
        },
        error: function(errorThrown){
            console.log( errorThrown);
        }
    });
}

Also this loop would be better written as below, so old xhrs are removed from requests:

var xhr;
while (xhr = requests.pop()) {
        // kill any queued requests
        xhr.abort();
}
requests.push(process_asr_request(inputVal));

Another note, requests should be outside the event loop if you want this to work and you have several globals in this function.

var requests = [];
$input.on("keyup", function() {
    var inputVal    = $input.val(),
    // check the lenght on the value
        inputCount  = inputVal.length;
    //...
});
megawac
  • 10,953
  • 5
  • 40
  • 61
  • Thank you for the suggestion. But this seems to be aborting all of my requests. Once the user stops typing nothing is sent. Any ideas? – Sheixt Nov 12 '13 at 17:36
  • Move the aborting part above where you push to the requests array - I updated the second code fragment – megawac Nov 12 '13 at 17:39
  • Also just updated it to pass `inputVal` to the request, you may want to consider updating your code to have `inputVal` in the top scope – megawac Nov 12 '13 at 17:43
  • Okay this seems to work, thank you for the depth of the answer!! When you mention moving the "`inputVal` in the top scope", it is declared at the beginning in the original code... Or am I not following you? – Sheixt Nov 12 '13 at 17:57
  • It works how it is, but if it were my code I probably would only declare the `$input` and `requests` in top scope – megawac Nov 12 '13 at 18:02