Fomantic-UI icon indicating copy to clipboard operation
Fomantic-UI copied to clipboard

[checkbox] event beforeChecked, be able to use a Promise<boolean> instead of a boolean to prevent default behavior.

Open Ikaer opened this issue 3 years ago • 6 comments

Feature Request

Hello, a nice feature would be the possibility to return a promise instead of a boolean for the checkbox beforeChecked and beforeUnchecked events (or any kind of events that are use to prevent the default behavior).

That way, it would be possible for exemple to defer the resolution and waiting for an user input.

The scenario I have in mind is to open a modal asking for confirmation before choosing to check or uncheck the checkbox.

Example (if possible)

if would look like this: https://jsfiddle.net/6rdw20vy/1/ (of course it not functional, it's to illustrate what kind of scenario I have in mind)

I found a workaround by using window.confirm (https://jsfiddle.net/6rdw20vy/3/), but it is not very fomantic-style friendly.

Ikaer avatar Nov 10 '21 15:11 Ikaer

It would be nice to allow promises for all of these cancelable callbacks, not sure how much work that'd be though.
The best I've got currently is to return false every time and set checked if the modal is approved: https://jsfiddle.net/n5hcgwu7/

GammaGames avatar Nov 11 '21 15:11 GammaGames

@GammaGames Yes it's a good workaround, I have ended with the same kind of solution. But it's not perfect because "set checked" does not trigger the next callbacks (like onChange), and we cant use "check" because it will cause an infinite loop (beforeChecked -> check -> beforeCheck -> ...), so if there is something that must have been done in the onChange callback, that must be called manually.

Ikaer avatar Nov 11 '21 16:11 Ikaer

I would appreciate having this ability. I often use callbacks with fetch, and would like to keep the modal open if fetch gets a statusCode other than 200. I would like to await the fetch, and return accordingly. However, the callback function has to be marked as async in order to do this.

Briefly looking at the code behind callbacks, we could add await to the callback call, like this from here:

 if (ignoreRepeatedEvents || await settings.onApprove.call(element, $(this)) === false) {

The function that line is in would also need to be marked as async. Not sure where that gets called to know the problems associated it. I wouldn't mind putting together a PR that does this, but I would need to know first what negative effects this may result in.

HeyITGuyFixIt avatar Sep 09 '23 18:09 HeyITGuyFixIt

This is something that i'm willing to implement too, but since the async keyword will not work on IE11 🤔

However, I found an alternative that should work on all browsers. By checking the constructor name, we can determine if the callback is an async function, then we use the Promise system, or else, the good ol' synchronous call. All further code is stored in a callback that is applied when the callback is called.

For example in the modal onApprove code:

event: {
  approve: function() {
    // our callback called after a promise or a classic call
    var onApproveCallback = function (result) {
      if (result === false) {
        module.verbose('Approve callback returned false cancelling hide');
        return;
      }
      module.hide(function() {
        ignoreRepeatedEvents = false;
      });
    };

    if(ignoreRepeatedEvents) {
      return;
    }

    ignoreRepeatedEvents = true;

    // here's the magic, we check if the constructor of our onApprove function
    // is Async. If it's the case, we use the .then() function to call the callback after
    // else our callback is called right now
    if (settings.onApprove.constructor.name == 'AsyncFunction') {
      settings.onApprove.call(element, $(this)).then(function (resolve) { onApproveCallback(resolve); })
    } else {
      onApproveCallback(settings.onApprove.call(element, $(this)))
    }
  },
  ...
}

So we can now use two ways to control the modal closing on approval:

  • The old fashioned way, synchronous:
$('.ui.modal').modal({
  onApprove: function($element) {
    console.log('onApprove');
    return true
  }
});
  • The new cool way, asynchronous backed:
$('.ui.modal').modal({
  onApprove: async function($element) {
    console.log('onApprove');
    return new Promise((resolve) => {
      // mocked async call
      // modal will close is 3 seconds
      setTimeout(function () { resolve(true) }, 3000)
    })
  },
});

Some code will need extra work to do (in checkboxes for example), but it's doable. What do you guys think about it ?

prudho avatar Mar 04 '24 16:03 prudho

Very nice approach! Regardless of ie11, we always need to check if the call is async to decide how to call the callback . If we would always assume an async function, your .then call will break on sync functions. As we have many callbacks in many modules, we should think about making this a bit more generic (?)

lubber-de avatar Mar 04 '24 16:03 lubber-de

Async functions will always have AsyncFunction in their constructor.name, but yeah, we also need to check the existence of a provided .then() method to be sure.

prudho avatar Mar 04 '24 16:03 prudho