ScriptUI: Always honor Cancel/Esc in onClose validations

When you need to validate user input in an edittext field, it’s not enough to do so in its onChange event — you must also validate it in the dialog’s onClose. It’s the only way to stop the dialog from closing after a click on the OK button, so that the user has a chance to correct their input. But what if that close event occurred because of an explicit user action to simply dismiss the dialog, like clicking the Cancel button?

Please just let me Cancel

By default, it seems that the close event does not distinguish between an “OK” close and a “Cancel” close. This can be problematic in input validations, because if your validator is designed to prevent the dialog from closing upon detection of faulty input, it should absolutely not do so in the event of an explicit “Cancel” (or Esc) close.

The Workaround

Register event handlers for the Cancel button and Escape key, so that a justExit flag can be flipped when they occur. Then, the onClose handler can check the flag to determine whether it needs to validate and obstruct, or just get out of the way.

So first, define the boolean, and maybe a flagging function to flip it:

var justExitFlag = false;

function flipJustExit() {
	justExitFlag = true;
}

Now add event listeners. Specifically, there are three events you’ll want to monitor:

1. an Esc key press within the dialog window

myDialog.addEventListener("keydown", function (k) {
  if (k.keyIdentifier == "U+001B" /* Escape key */) {
    flipJustExit();
  }
});

2. a click on the Cancel button

myCancelBtn.addEventListener("click", flipJustExit);

3. a Spacebar key press on the Cancel button

myCancelBtn.addEventListener("keydown", function (k) {
  if (k.keyIdentifier == "U+0020" /* space key */) {
    flipJustExit();
  }
});

Finally, your onClose handler might be something like this:

function validateInput() {
  if (justExitFlag) {
    return true; /* doesn’t stop dialog from closing */
  } else if (inputIsInvalid()) {
    alertUserWithCrazyPopup();
    return false; /* stops dialog from closing */
  }
}