发布到Ajax的问题

I am working on some jQuery to check if a cell in a table has been edited. If it has, the revision gets sent back to the database via ajax. I am using a timeout delay to give the user 2500 milliseconds between keystrokes to make their full correction and catch when they are finished. I am also using blur bypass the timeout delay if they click out to a different td in the html table. Once ajax is complete I add a "saved" class to the td and have ajax inside an if statement so that ajax only happens if the "saved" class is not present. This only allows ajax to happen once.

My issue: If a number is changed and a different td is clicked immediately and the user starts typing, the previous change never makes it to ajax so it is never saved to the database. The 1 millisecond timer makes it seem like there is no way possible this could happen, but something must be slowing it down somehow.

My Question: Why might this be happening and how can I fix this?

Side Note: The toastr code has also stopped working and I am not sure why.

$('td').on('input blur', function(e) {

    var timeoutDelay=2500;

    if( e.type == "blur"){
        timeoutDelay=1;
    }

    // If NOT already saved...
    if( !$(this).hasClass("saved") ){
        var _this = $(this); // preserve reference to the input field here

        clearTimeout(saveTimeout);
        saveTimeout = setTimeout(function() {
            console.log(_this)
            $.ajax({
                method: "POST",
                url: "updatedatabase.php",
                data: { 
                    content: _this.text(), 
                    date: _this.siblings().first().text(),
                    prod: $('tr:first-child th:nth-child(' + (_this.index() + 1) + ')').text(),
                    old: old
                }
            })
            .done(function( msg ) {
                alert( msg );
                // Add the "saved" class to prevent other saving
                _this.addClass("saved");
            });

            toastr.options = {
                "positionClass": "toast-top-center",
                "onclick": null,
                "timeOut": "2500",
            }

            toastr.info(old,'Database Updated!<br><br>Your Previous Amount Was:');

            _this.prop('contenteditable', false);

        }, timeoutDelay);
    }
});

$(document).ready(function () {

    var old;
            
    $('td').click(function(){
                
        old=$(this).text();
                
        $(this).prop('contenteditable', true);
                
                
    });
            
    var saveTimeout;
            
    // Remove the "saved" class on keydown
    $('td').on('keydown', function(e) {
        $(this).removeClass("saved");
    });
    
    $('td').on('input blur', function(e) {
                                
        var timeoutDelay=2500;
               
        if( e.type == "blur"){
            timeoutDelay=1;
        }
                
        // If NOT already saved...
        if( !$(this).hasClass("saved") ){
            var _this = $(this); // preserve reference to the input field here
            
            // Add the "saved" class to prevent other saving
            _this.addClass("saved");
            
            clearTimeout(saveTimeout);
            saveTimeout = setTimeout(function() {
                console.log(_this)
                $.ajax({
                    method: "POST",
                    url: "updatedatabase.php",
                    data: { 
                        content: _this.text(), 
                        date: _this.siblings().first().text(),
                        prod: $('tr:first-child th:nth-child(' + (_this.index() + 1) + ')').text(),
                        old: old
                    }
                })
                .done(function( msg ) {
                    alert( msg );
                });

                toastr.options = {
                    "positionClass": "toast-top-center",
                    "onclick": null,
                    "timeOut": "2500",
                }

                toastr.info(old,'Database Updated!<br><br>Your Previous Amount Was:');
                        
                _this.prop('contenteditable', false);
                        
            }, timeoutDelay);
        }
    });
            
            
    $("td").hover(function(){
                                
                
                    
        $(this).addClass('highlight').siblings().first().addClass('highlight');

        $('tr:eq(1) th:eq('+$(this).index()+')').addClass('highlight');
                
                
    },function(){
                
                
                    
        $(this).removeClass("highlight").siblings().first().removeClass('highlight');

        $('tr:eq(1) th:eq('+$(this).index()+')').removeClass('highlight');
                
                
    });
    
}); 
table,th, td {
  
    border: 1px solid black;
    border-collapse: collapse;
        
}

.highlight {
    
    background-color:#E0E0E0;
    color: blue;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/toastr.js/1.3.1/css/toastr.css" rel="stylesheet"/> 
        <script src="http://cdnjs.cloudflare.com/ajax/libs/toastr.js/1.3.1/js/toastr.js"></script>
<table>
  <tr>
    <th>Item #</th>
    <th>1234567</th>
    <th>7654321</th>
    <th>5678945</th>
  </tr>
  <tr>
    <th>Product</th>
    <th><u>22 ounce Dark</u></th>
    <th><u>12count 4oz Dark</u></th>
    <th><u>24count 6oz TJ</u></th>
  </tr>
  <tr>
    <th>2016-01-03</th>
    <td>13587</td>
    <td>2203</td>
    <td>4111</td>
  </tr>
  <tr>
    <th>2016-01-04</th>
    <td>14111</td>
    <td>3247</td>
    <td>4332</td>
  </tr>
  <tr>
    <th>2016-01-05</th>
    <td>13212</td>
    <td>3101</td>
    <td>3911</td>
  </tr>
  <tr>
    <th>2016-01-06</th>
    <td>16335</td>
    <td>3299</td>
    <td>4001</td>
  </tr>
  <tr>
    <th>2016-01-07</th>
    <td>15421</td>
    <td>3100</td>
    <td>4078</td>
  </tr>
</table>

</div>

Whenever your $('td').on('input blur', function(e) { event handler is called, you clear the timeout reference saveTimeout and the previous timeout is never called. The solution is to have a timeout reference per each td.

Without checking here is a draft

if ( _this.saveTimeout )
    clearTimeout(_this.saveTimeout);
_this.saveTimeout = setTimeout(function { ........  

and so on.

CORRECTED:

clearTimeout(_this.data('saveTimeout'));
_this.data('saveTimeout', setTimeout(function() {
     console.log("Saving " + _this.text()) + "...";
     ...
}, timeoutDelay));

Here's the sequence of events:

  1. User types in the 1st cell which gets an input event.
  2. Event handler called: The td for this cell does not have the class saved so the branch for if (!$(this).hasClass("saved")) is taken.
  3. The class saved is added to td.
  4. A new timeout is set and saved in saveTimeout. Let's call this timeout, timeout A.
  5. User clicks in a 2nd cell: the 1st cell gets a blur event.
  6. Event handler called: the td for this cell still has the class saved so the branch is not taken, and no new timeout is set. The only timeout still in effect is timeout A.
  7. User types in the 2nd cell, which gets an input event.
  8. Event handler called: the td for this 2nd cell does not have the saved class, so the branch is taken.
  9. The handler finds timeout A in saveTimeout and cancels it. It sets a new timeout to save the change in the 2nd cell.

Ok, so I've used your code snippet and made some modifications to it. Your snippet was giving an error because toastr is not loaded, and there's nothing to receive the AJAX, and this snippet suffers from the same issues. However, it can serve to illustrate the solution. What I did was:

  1. Make it so that if the even is blur then a new timeout is forcibly set even if the saved class is present.

  2. The timeout is saved on the DOM element itself. I've done this before in other contexts and I've tested across multiple browsers and it works fine. However, you may be targeting browsers that I don't use. If this method makes you can replace it with any method that allows retreiving a saved timeout on the basis of the DOM element. You could use element ids to look into a map from id to timeout, or what-have-you. Use whatever makes you comfortable.

With the changes I made, I can see in the logs that the save request for the first cell is fired and a different request for the 2nd cell also fires.

$(document).ready(function () {

    var old;
            
    $('td').click(function(){
                
        old=$(this).text();
                
        $(this).prop('contenteditable', true);
                
                
    });
                    
    // Remove the "saved" class on keydown
    $('td').on('keydown', function(e) {
        console.log("remove");
        $(this).removeClass("saved");
    });
    
    $('td').on('input blur', function(e) {
                                
        var timeoutDelay=2500;
               
        if( e.type == "blur"){
            timeoutDelay=1;
        }
                
        // If NOT already saved... or if it is a blur
        if( !$(this).hasClass("saved") || e.type === "blur" ){
            var _this = $(this); // preserve reference to the input field here
            
            // Add the "saved" class to prevent other saving
            _this.addClass("saved");
            
            console.log("setting new timeout");
            if (this.saveTimeout) {
                clearTimeout(this.saveTimeout);
                this.saveTimeout = null;
            }
            this.saveTimeout = setTimeout(function() {
                console.log("timeout", _this.text());
                $.ajax({
                    method: "POST",
                    url: "updatedatabase.php",
                    data: { 
                        content: _this.text(), 
                        date: _this.siblings().first().text(),
                        prod: $('tr:first-child th:nth-child(' + (_this.index() + 1) + ')').text(),
                        old: old
                    }
                })
                .done(function( msg ) {
                    alert( msg );
                });

                toastr.options = {
                    "positionClass": "toast-top-center",
                    "onclick": null,
                    "timeOut": "2500",
                }

                toastr.info(old,'Database Updated!<br><br>Your Previous Amount Was:');
                        
                _this.prop('contenteditable', false);
                        
            }, timeoutDelay);
        }
    });
            
            
    $("td").hover(function(){
                                
                
                    
        $(this).addClass('highlight').siblings().first().addClass('highlight');

        $('tr:eq(1) th:eq('+$(this).index()+')').addClass('highlight');
                
                
    },function(){
                
                
                    
        $(this).removeClass("highlight").siblings().first().removeClass('highlight');

        $('tr:eq(1) th:eq('+$(this).index()+')').removeClass('highlight');
                
                
    });
    
});
table,th, td {
  
    border: 1px solid black;
    border-collapse: collapse;
        
}

.highlight {
    
    background-color:#E0E0E0;
    color: blue;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/toastr.js/1.3.1/css/toastr.css" rel="stylesheet"/> 
        <script src="http://cdnjs.cloudflare.com/ajax/libs/toastr.js/1.3.1/js/toastr.js"></script>
<table>
  <tr>
    <th>Item #</th>
    <th>1234567</th>
    <th>7654321</th>
    <th>5678945</th>
  </tr>
  <tr>
    <th>Product</th>
    <th><u>22 ounce Dark</u></th>
    <th><u>12count 4oz Dark</u></th>
    <th><u>24count 6oz TJ</u></th>
  </tr>
  <tr>
    <th>2016-01-03</th>
    <td>13587</td>
    <td>2203</td>
    <td>4111</td>
  </tr>
  <tr>
    <th>2016-01-04</th>
    <td>14111</td>
    <td>3247</td>
    <td>4332</td>
  </tr>
  <tr>
    <th>2016-01-05</th>
    <td>13212</td>
    <td>3101</td>
    <td>3911</td>
  </tr>
  <tr>
    <th>2016-01-06</th>
    <td>16335</td>
    <td>3299</td>
    <td>4001</td>
  </tr>
  <tr>
    <th>2016-01-07</th>
    <td>15421</td>
    <td>3100</td>
    <td>4078</td>
  </tr>
</table>

</div>