-1

I'm trying to figure why/how can I make the button text update before the "each" loop, so I can provide feedback to the users while it's running... but it only seems to update after the "each" has completed.

In my example, the button will go "Updating please wait..." after the each has run. So if it take 10s, the button stay enable and with the text "Update" (original value). I want it to be disable and display "Updating please wait ..." while the Loop (each) is running.

Got an idea why?

  $("#UpdateRFID").click(function() {
    $('#UpdateRFID').prop("disabled", true).prop("value", "Updating please wait...");
    UpdateRFID();
    //$('#UpdateRFID').prop("disabled", false).prop("value", "Update");
  });

  function UpdateRFID() {
    $('.skurow[submitted="0"]').each(function() {
      sku = $(this).attr('sku');
      $.ajax({
        url: 'class.action.php',
        type: 'post',
        async: false,
        dataType: 'JSON',
        data: {
          "action": "GetDataFromTagID",
          "tagid": sku
        },
        success: function(response) {
          console.log('ok');
        },
        error: function(response) {
          console.log(response.responseText);
        }
      });
    });
  }

Button :

<input class="MenuButton" name="UpdateRFID" id="UpdateRFID" value="Update" type="button" />

Here's another test I did. In this Example, when I click the button, I get an alert "Start", then it wait for about 3 secondes (sleep in the PHP Code for testing), then I get the alert "Done", then the button is disable and change to "Recherche ...". I'm not sure why... I want it to be disable before the "each" start.

  function UpdateRFID() {
    $("#UpdateRFID").prop("disabled", true).prop("value", "Recherche ...");
    alert("Start");

    $('.skurow[submitted="0"]').each(function () {
      sku = $(this).attr("sku");
      $.ajax({
        url: "class.action.php",
        type: "post",
        async: false,
        dataType: "JSON",
        data: {
          action: "GetDataFromTagID",
          tagid: sku,
        },
        success: function (response) {
          console.log("ok");
        },
        error: function (response) {
          console.log(response.responseText);
        },
      });
    });

    alert("Done");
    //$('#UpdateRFID').prop("disabled", false).prop("value", "Mise a jour nom RFID");
  }

Here's the full code standalone code to reproduce the problem: jQuery is 2.2.1 PHP is 5.4 (but I don't think it's relevant)

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="includes/jquery.min.js"></script>
    <title>Document</title>
</head>

<body>

    <p class="ScanText">SKU</p> <input class="FullSizeBox" type="text" id="SKU" name="SKU" onclick="BoxSelect(this.id);" />
    <script type="text/javascript">
        $('#SKU').focus();
    </script>

    <br><input class="MenuButton" name="UpdateRFID" id="UpdateRFID" value="Mise a jour nom RFID" type="button" />

    <div id="qtsku" style="margin-left:5px">-</div>

    <div id="divSKUScanned">
        <table id="ScannedSKU">
            <thead>
                <tr>
                    <th></th>
                    <th></th>
                </tr>
            </thead>
            <tbody>
                <!-- updated by JavaScript -->
            </tbody>
        </table>

    </div>

    <div class="ScanSubmit"><input class="MenuButton" id="btnsubmit" type="button" value="Soumettre" onclick="SubmitVE();" disabled></div>

    <script>
        function SkuCount() {
            skucount = $('#ScannedSKU tr').length - 1;
            stritem = 'item';
            if (skucount > 1) {
                stritem = 'items';
            }
            $('#qtsku').html('Total ' + stritem + ': ' + skucount);
            if (skucount == 0) {
                $('#qtsku').html('');
            }
        }
        SkuCount();

        $(document.body).on('click', '.delButton', function() {
            sku = $(this).closest("tr").find('.skudesc').text();
            r = confirm('Effacer ' + sku + ' ?');
            if (r == true) {
                $(this).closest("tr").remove();
            }
            SkuCount();
            $('#SKU').focus(); //TODO: That Focus dosent work...
        });

        $('#SKU').keypress(function(event) {
            keycode = (event.keyCode ? event.keyCode : event.which);
            if (keycode == '13') {
                sku = this.value;
                sku.trim();
                this.value = "";
                if (sku != "") {
                    if (!($('.skurow[sku="' + sku + '"').length)) {
                        delBtn = '<input name="delButton" id="delButton" class="delButton" type="button" value="X" style="background-color:gray; color:black">';
                        $('#ScannedSKU > tbody:last-child').append('<tr class="skurow" submitted="0" sku="' + sku + '"><td class="delbtn">' + delBtn + '</td><td class="skudesc">' + sku + '</td></tr>');
                        $("#btnsubmit").prop("disabled", true);
                        SkuCount();
                    }
                }
            }
        });

        $("#UpdateRFID").click(function() {
            UpdateRFID();
        });

        function UpdateRFID() {
            $('#UpdateRFID').prop("disabled", true).prop("value", "Recherche ...");
            alert('Start');

            $('.skurow[submitted="0"]').each(function() {
                sku = $(this).attr('sku');
                $.ajax({
                    url: 'class.action.php',
                    type: 'post',
                    async: false,
                    dataType: 'JSON',
                    data: {
                        "action": "GetDataFromTagID",
                        "tagid": sku
                    },
                    success: function(response) {
                        console.log('ok');
                    },
                    error: function(response) {
                        console.log(response.responseText);
                    }
                });
            });

            alert('Done');
            //$('#UpdateRFID').prop("disabled", false).prop("value", "Mise a jour nom RFID");
        }
    </script>


</body>

</html>

and this is the php page section for class.action.php

<?php
if ($_POST["action"] == "GetDataFromTagID") {
sleep(3);
}
?>
E_net4
  • 27,810
  • 13
  • 101
  • 139
nka
  • 15
  • 7
  • There is a typo in `$('.skurow[submitted="0"')`, you are missing the closing `]` in the selector. – Matteo Tassinari Nov 22 '22 at 16:06
  • 1
    1. `async: false` is deprecated, gives a poor user experience, and won't work in all browsers. 2. Changing button text [depends on what kind of button it is](https://stackoverflow.com/questions/7035842/how-to-change-the-buttons-text-using-javascript), and you didn't post the relevant HTML. – Jared Smith Nov 22 '22 at 16:09
  • @JaredSmith It's a very old code working on some handlet device. It's an "INPUT" button `` – nka Nov 22 '22 at 16:16
  • what version of jQuery are you using? – Matt Ellen Nov 22 '22 at 16:31
  • @MattEllen jQuery v2.1.1 (just updated in case) – nka Nov 22 '22 at 16:41
  • Is there a reason you can't put the code you want to execute before the call to `.each`? – Matt Ellen Nov 22 '22 at 16:45
  • @MattEllen Wanted to keep thing clean without all the useless stuff. Made a "standalone" page to reproduce de problem and posted the whole code (including the PHP) – nka Nov 22 '22 at 16:56
  • *Got an idea why?* - javascript is **single threaded** - it can only do one thing at a time, and that includes refreshing the page. As your code doesn't "yield" to the UI at any point, it can't update, so updates at the end. Ideally don't use `async:false` and your problem goes away (along with all the async:false issues). If that's not an option (for whatever reason) then change your code from: `UpdateRFID();` to `setTimeout(UpdateRFID, 1);` so that the UI has a chance to update. – freedomn-m Nov 22 '22 at 17:45
  • @freedomn-m Thanks! I understand that javascript is single threaded and that's why I'm confuse. He should update the button before running the "each" loop. I tried to make `async: true`, still dosent work... but `setTimeout(UpdateRFID, 1000);` seems to do the trick (1 was too small). – nka Nov 22 '22 at 18:07
  • @freedomn-m Is there a way I can tell "update the button, THEN run UpdateRFID" ? – nka Nov 22 '22 at 18:07
  • @TimLewis Done, thanks a lot and sorry about that! :) – nka Nov 22 '22 at 19:25
  • No problem! That's much better, cheers! – Tim Lewis Nov 22 '22 at 19:26

3 Answers3

1

What you have may work; or may not over time.

What you should do is use a Promise and after all the loop and ajax is done you can resolve that.

This is not a tested solution nor is this super solid. I did add a few things like how to enable and disable the button based on a custom event trigger, how to wrap the Promise in the function UpdateRFID etc.

I removed onclick="BoxSelect(this.id);" because seeing a click on a text input is just weird and it did not exist in the code; And, you can add a click handler in the JavaScript code (better) with $('#SKU').on('click',function(){BoxSelect(this.id);});

I do see a lot of other things I would probably change but just the Promise is what you need to focus on here.

$('#SKU').focus();

function SkuCount() {
  let skucount = $('#ScannedSKU').find('.skurow').length - 1;
  let stritem = 'item' + !!skucount ? 's' : '';
  let updateText = skucount == 0 ? '' : 'Total ' + stritem + ': ' + skucount;
  $('#qtsku').html(updateText);
}

$('#ScannedSKU').on('click', '.delButton', function() {
  let sku = $(this).closest("tr").find('.skudesc').text();
  let r = confirm('Effacer ' + sku + ' ?');
  if (!!r) {
    $(this).closest("tr").remove();
  }
  SkuCount();
  $('#SKU').focus();
});

$(document.body).on('keypress', function(event) {
  let keycode = event.keyCode ? event.keyCode : event.which;
  if (keycode == '13') {
    const skuContainer = $('#ScannedSKU').find('.sku-container');
    const skuList = skuContainer.find('.skurow');
    let sku = this.value.trim(); //?????
    this.value = "";
    if (!!sku && !skuList.filter('[data-sku="' + sku + '"]').length) {
      let newtr = $('#newrows').find('.skurow').first().clone();
      newtr.data('sku', sku);
      newtr.find('skudesc').html(skudesc);
      skuContainer.append(newtr);
      $("#btnsubmit").prop("disabled", true);
      SkuCount();
    }
  }
});

function UpdateRFID() {
  $('#UpdateRFID').trigger("disable-me");
  alert('Start');
  const myPromise = new Promise((resolve, reject) => {
    $('.skurow[submitted="0"]')
      .each(function() {
        let sku = $(this).data('sku');
        const skuData = {
          "action": "GetDataFromTagID",
          "tagid": sku
        };
        $.ajax({
            url: 'class.action.php',
            type: 'post',
            dataType: 'JSON',
            data: skuData
          })
          .done(function(response) {
            console.log('ok');
          })
          .fail(function(response) {
            console.log(response.responseText);
          });
      });
    resolve("looperdone");
  });

  myPromise
    .then(function() {
      alert('Done');
    })
    .then(function() {
      $('#UpdateRFID').trigger("enable-me");
    });
}

// this was missing so this is just an empty function
function SubmitVE() {}
$('#btnsubmit').on('click', SubmitVE);

$("#UpdateRFID")
  .on('click', UpdateRFID)
  .on('enable-me', function() {
    $(this).prop("disabled", false).prop("value", "Mise a jour nom RFID");
  }).on('disable-me', function() {
    $(this).prop("disabled", true).prop("value", "Recherche ...");
  });

SkuCount();
.delButton {
  background-color: gray;
  color: black;
}

.element-container {
  display: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<p class="ScanText">SKU</p>
<input class="FullSizeBox" type="text" id="SKU" name="SKU" />
<input class="MenuButton" name="UpdateRFID" id="UpdateRFID" value="Mise a jour nom RFID" type="button" />
<div id="qtsku" style="margin-left:5px">-</div>
<div id="divSKUScanned">
  <table id="ScannedSKU">
    <thead>
      <tr>
        <th></th>
        <th></th>
      </tr>
    </thead>
    <tbody class='sku-container'>
      <!-- updated by JavaScript -->
    </tbody>
  </table>
</div>
<div class="ScanSubmit"><input class="MenuButton" id="btnsubmit" type="button" value="Soumettre" disabled></div>

<div class="element-container">
  <table id="newrows">
    <tbody>
      <tr class="skurow" submitted="0" sku="">
        <td class="delbtn"><button name="delButton" class="delButton" type="button">X</button></td>
        <td class="skudesc"></td>
      </tr>
      <tbody>
  </table>
</div>
Mark Schultheiss
  • 32,614
  • 12
  • 69
  • 100
  • Oh yes! That's was I was looking for. Didn't like the way I made it work (using delays is a bit ugly fix). Hope it will work on the handled scanner, I will try that tomorrow. And I will also learn on to work with promise! :) Thanks a lot! :) – nka Nov 23 '22 at 02:12
0

Update #4 : Patched This is working, but the final solution is in the Update #5.

Thanks to @freedomn-m , I only added a little timeout and it fixed it. Wish I could do "update the button then do this", but if it's working like this, I'm OK with it!

 $("#UpdateRFID").click(function() {
    $('#UpdateRFID').prop("disabled", true).prop("value", "Recherche ...");
    setTimeout(UpdateRFID, 100);
  });

Note that setTimeout(UpdateRFID(), 100); dosent work. Not sure why.

nka
  • 15
  • 7
0

Update #5 : SOLVED

This is how my final code is. I manage to use the function $.when to run all my ajax request and then do actions. Now my button update correctly and I also manage to get ride of the async: false that is way better. My page update evertime an "ajax" call is resolved and at the end of all ajax call (done or fail), I re-enable the button.

Thanks for all the help here. Helped me to go into the right direction! :)

function UpdateRFID() {
    $('#UpdateRFID').trigger("disable-me");

    const UpdateRFIDajaxes = [];

    $('.skurow[submitted="0"]')
      .each(function() {
        sku = $(this).attr('sku');
        const skuData = {
          "action": "GetDataFromTagID",
          "tagid": sku
        };
        UpdateRFIDajaxes.push(
          $.ajax({
            url: 'class.action.php',
            type: 'post',
            dataType: 'JSON',
            data: skuData
          })
          .done(function(response) {
            //do ok stuff.
           console.log('ok')
          })
          .fail(function(response) {
            //do failed stuff.
            console.log('failed');
          })
        );
      });
    $.when.apply($, UpdateRFIDajaxes)
      .always(function() {
        $('#UpdateRFID').trigger("enable-me");
        EnableSubmit();
        $('#SKU').focus();
      }).done(function() {
        //console.log('Done.');
      })
      .fail(function() {
        alert('Erreur, ca ne fonctionne pas, etes vous sur le WiFi?');
      });
  }


  $("#UpdateRFID")
    .on('click', function() {
      UpdateRFID();
    })
    .on('enable-me', function() {
      $(this).prop("disabled", false).prop("value", "Mise a jour nom RFID");
    }).on('disable-me', function() {
      $(this).prop("disabled", true).prop("value", "Recherche ...");
    });
nka
  • 15
  • 7