NJA NJA - 4 months ago 13
Javascript Question

How to block the UI for a long running Javascrpt loop

I need to do the opposite of this post, "Best way to iterate over an array without blocking the UI"

I have to loop through hundreds of rows and set a value for each. But that job has to complete before I allow the users to do the next step and submit the updated rows to the database.

The javascript is below.

// toolbar events/actions
changeZeroDiscountButton.click(function (event) {
var value = discountComboBox.jqxComboBox('val');

if ((value != null) && (value != "")) {
value = value / 100;

// get all the rows (this may have to be changed if we have paging
var datainformations = $('#jqxgrid').jqxGrid('getdatainformation');
var rowscounts = datainformations.rowscount;

for (var i = 0; i < rowscounts; i++) {
var preSetDiscount = $("#jqxgrid").jqxGrid('getcellvalue', i, "discount");

if (preSetDiscount == .0000) {
$("#jqxgrid").jqxGrid('setcellvalue', i, "discount", value);
}
}
}

});

Answer

JavaScript is designed so it does not block the UI in any way, and this is one of its most important features for the browsers. The only exceptions are the popup message boxes (i.e. alert(), confirm(), and propmpt()). Even if it is possible, it's highly not recommended to block the UI.

There are many alternative ways to prevent the user from firing actions that shouldn't be fired until something else happens. Examples:

  • Disable the action's button until your processing ends then enable it back.
  • Set a flag (e.g. var processing = true) and check that flag in the click event of the action's button so it displays a message (e.g. "still processing, please wait...") when flag is true and execute the action when flag is false. Remember not to use alert() for the message otherwise you'll block the processing. Use a popup div instead.
  • Set the event handler at the beginning of the processing to a function that displays a message (e.g. "still processing, please wait...") and at the end of the processing, set the event handler to the function that will do the action. Remember not to use alert() for the message otherwise you'll block the processing. Use a popup div instead.
  • Show a modal popup div at the beginning of the processing with a message (e.g. "still processing, please wait..."), or progress bar, or some animation. The modal popup prevents the user from interacting with the page so they cannot click anything. For that to work, the modal popup must not have a close button or any other way to close it. At the end of processing, close the modal popup so the user can now continue.

Important Note: You mentioned in your comment to the other answer that the overlay (which is similar to the modal popup in my last point) is not displayed until the end of processing. That's because your processing is occupying the processor and preventing it from handling the UI thread. When you can do is delay your processing. So first display the modal popup (or overlay), then use setTimeout() to start processing 1 second later (maybe 500 millisecond or even less is enough). This gives the processor enough time to handle the UI thread before it starts your long processing.

Edit Here is an example of the last method:

function start() {
  disableUI();
  setTimeout(function() {
    process();
  }, 1000);
}

function process() {
  var s = (new Date()).getTime();
  var x = {};
  for (var i = 0; i < 99999; i++) {
    x["x" + i] = i * i + i;
  }
  var e = new Date().getTime();
  $("#results").text("Execution time: " + (e - s));
  enableUI();
}

function disableUI() {
  $("#uiOverlay").dialog({
    modal: true,
    closeOnEscape: false,
    dialogClass: "dialog-no-close",
  });
}

function enableUI() {
  $("#uiOverlay").dialog("close");
}
.dialog-no-close .ui-dialog-titlebar {
  display: none;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.4/themes/smoothness/jquery-ui.css">
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.4/jquery-ui.min.js"></script>

<button type="button" onclick="start()">Start</button>
<div id="results"></div>
<div id="uiOverlay" style="display: none;">Processing... Please wait...</div>

Edit 2 Here is an example of the third method:

$("#StartButton").on("click", start);

function start() {
  //remove all previous click events
  $("#StartButton").off("click");
  //set the click event to show the message
  $("#StartButton").on("click", showProcessingMsg);
  //clear the previous results
  $("#results").text("");
  setTimeout(function() {
    process();
  }, 1000);
}

function process() {
  var s = (new Date()).getTime();
  var x = {};
  for (var i = 0; i < 99999; i++) {
    x["x" + i] = i * i + i;
  }
  var e = new Date().getTime();
  $("#results").text("Execution time: " + (e - s));

  //remove all previous click events
  $("#StartButton").off("click");
  //set the click event back to original
  $("#StartButton").on("click", start);
}

function showProcessingMsg() {
  $("#results").text("Still processing, please wait...");
}
.dialog-no-close .ui-dialog-titlebar {
  display: none;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<button type="button" id="StartButton">Start</button>
<div id="results"></div>

Comments