Ridge Robinson Ridge Robinson - 21 days ago 5
jQuery Question

Ajax on keyup vs user finished typing

I am wondering if this is even possible what I am looking for:

I have a table that is also a form that looks like this:

Exercise | Reps | Lbs | Kg
-------------------------------------
Squat | 8 | 135 | 61.23
Squat | 8 | 225 | 102.06
Squat | 6 | 315 | 142.88
Squat | 2 | 405 | 183.70
Squat | 1 | 485 | 219.99


where the 'Reps', 'Lbs' and 'Kg' values are all editable by the user.

I am running into an issue with this. I do not want to send an ajax request on every single keyup event, and I'd like to not have a submit button as well. I have implemented a timer so that it will update once a user stops typing for 2 seconds (or however long). However, the issue I am running into is that I cannot separate this timer between the different rows. Let me explain:

If in my table above, if I edit the top row with 135lbs it will 1) update the Kg appropriately immediately && 2) start countdown timer.

If I then start to edit the next row at 225lbs, the timer will start over, meaning that the first row will not get updated.

Potential solution is to allow the timer to start over for each new edit, but then when it is finally finished, then to update everything. But this seems like it might be a waste as well, as my number or rows may be, technically, an infinite amount.

I have thought about adding a class to whatever one's were changed, and then only update those ones...however, if they update one and then update back again to what it was originally...then it also doesn't need to be updated.

Any suggestions on how to approach this?

Answer

You could do the following:

  1. Set a timer on keyup (or paste) when the input value changes.
  2. Make the ajax call immediately when the input looses focus (and it has changed).

The key is to properly cancel the timer and abort the ajax call when necessary.

function clearTimerForInput($input) {
    var timerId = $input.data('timerId');
    if (timerId) {
        clearTimeout(timerId);
        $input.removeData('timerId')
    }
}

function updateServerForInput($input) {
    var jqXhr = $input.data('jqXhr');

    clearTimerForInput($input);

    // Abort an existing ajax call.
    // Note: The call still gets to the server,
    //       but its response will be ignored.
    if (jqXhr) {
        jqXhr.abort();
        $input.removeData('jqXhr');
    }

    // Update the current value.
    $input.data('currentValue', $input.val());

    // Make the ajax call.
    $input.data('jqXhr', $.ajax({
        // ...
    });
}

$('#myTable')
    .on('focus', ':input', function() {
        var $input = $this;
        $input.data('currentValue', $input.val());
    });
    .on('keyup paste', ':input', function() {
        var $input = $this;

        clearTimerForInput($input);

        // Note: Not all keyup events will change the value.
        if ($input.val() != $input.data('currentValue')) {
            $input.data('timerId', setTimeout(function() {
                updateServerForInput($input);
            }, 2000);
        }
    });
    .on('blur', ':input', function() {
        var $input = $this;
        if ($input.val() != $input.data('currentValue')) {
            updateServerForInput($input)
        }
    });

Note: The reason you need to abort the ajax call is in the (extremely) rare case where a second ajax call returns before the first ajax call. If you don't abort the first ajax call, you will update the page with invalid values when you handle its response.

Comments