Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handling pending sweet alerts #457

Open
kentmw opened this issue Oct 5, 2015 · 16 comments
Open

Handling pending sweet alerts #457

kentmw opened this issue Oct 5, 2015 · 16 comments

Comments

@kentmw
Copy link

kentmw commented Oct 5, 2015

EDIT: Please look at my later comment for most recent SwalService and guidelines

I know this should be a pull request. But I wanted to add the wrapper I wrote here anyway so I can get feedback if this is a useful addition.

This usage here is that anywhere in your application you can call swalService.swal() and if there are open sweet alerts, it will queue them and put a notification on the current sweet alert that there is a pending alert. The swal method will return a unique identifier so you can call close on a specific sweet alert even if its is pending.

var _ = require('underscore');

var swalService = {
  pendingSwal: [],
  currentSwal: null,
  swalFirstCalled: false,
  __swal: require('sweetalert'),

  swal: function() {
    var pending = {
      args: arguments,
      id: _.uniqueId()
    }
    if (this.isSwalOpen()) {
        this.pendingSwal.push(pending);
    } else {
      this.__swal.apply(null, pending.args);
      this.currentSwal = pending;
      if (!this.swalFirstCalled) {
        $('.sweet-alert').prepend('<span class="other-messages"></span>');
        this.swalFirstCalled = true;
      }
    }
    this.refreshPendingText();
    return pending.id;
  },

  refreshPendingText: function() {
    if (_.isEmpty(this.pendingSwal)) {
      $('.other-messages').text('');
    } else {
      $('.other-messages').text(_.size(this.pendingSwal) + ' unread alerts');
    }
  },

  close: function(id) {
    if (_.isUndefined(id) || (this.currentSwal && this.currentSwal.id == id)) {
      this.__swal.close();
    } else if (!_.isUndefined(id) && !_.isEmpty(this.pendingSwal)) {
      var indexOfSwalToClose;
      for (var i = 0; i < this.pendingSwal.length; i++) {
        if (this.pendingSwal[i].id == id) {
          indexOfSwalToClose = i;
          break;
        }
      }
      if (!_.isUndefined(indexOfSwalToClose)) {
        this.pendingSwal.splice(indexOfSwalToClose, 1);
        this.refreshPendingText();
      }
    }
  },

  onCloseOfCurrentSwal: function() {
    swalService.currentSwal = null;
    if (_.size(swalService.pendingSwal) > 0) {
      var pending = swalService.pendingSwal.shift();
      swalService.swal.apply(swalService, pending.args);
    }
  },

  isSwalOpen: function() {
    return !_.isUndefined(this.currentSwal) && !_.isNull(this.currentSwal);
  },

  setSwalDefaults: function() {
    this.__swal.setDefaults({});
    var originalClose = this.__swal.close;
    this.__swal.close = function() {
      originalClose();
      setTimeout(_.bind(swalService.onCloseOfCurrentSwal, swalService), 400);
    };
  }

};
@danielsawan
Copy link

too bad this is not the default behavior when there is multiple swal notification :/
This would be a nice PR.

@inctor
Copy link

inctor commented Nov 19, 2015

@kentmw What's the possibility of you making a new release with this fix in it, so it's possible to handle multiple Swal-popups after eachother?
Prolly not as a PR, since they won't be merged anyways likely, but maybe a guide, on how to use your above code instead? Is it just copy/paste into the SWAL js file?

@kentmw
Copy link
Author

kentmw commented Nov 19, 2015

@inctor I'll write up a guide in the next 15 minutes or so and update the service to the one I'm currently using as to remove some of the original bugs.

@kentmw
Copy link
Author

kentmw commented Nov 19, 2015

SwalService

Here is the code with only jquery as a project dependencies other than sweetalert:

The Code

var SwalService = function(options) {
  this.pendingSwal = [];
  if (options && options.showPendingMessage != undefined) {
    this.showPendingMessage = options.showPendingMessage;
  }
  this.initialize();
}

SwalService.prototype = {
  currentSwal: null,
  showPendingMessage: true,
  swalFirstCalled: false,
  __swal: swal,
  pendingSwal: null,
  nextId: 1,

  swal: function() {
    var pending = {
      args: arguments,
      id: this.nextId++
    }
    return this._swalWithId(pending);
  },

  _swalWithId: function(pending) {
    var service = this;
    if (this.isSwalOpen() || this.isClosing) {
      this.pendingSwal.push(pending);
    } else {
      this.__swal.apply(null, pending.args);
      this.currentSwal = pending;
      if (!this.swalFirstCalled) {
        $('.sweet-alert').prepend('<span class="other-messages"></span>');
        this.swalFirstCalled = true;
      }
    }
    this.refreshPendingText();
    return pending.id;
  },

  onClosed: function() {
    this.isClosing = false;
    this.openNextSwal();
  },

  refreshPendingText: function() {
    if (this.showPendingMessage) {
      if (this.pendingSwal.length === 0) {
        $('.other-messages').text('');
      } else {
        $('.other-messages').text(this.pendingSwal.length + ' unread alerts');
      }
    }
  },

  close: function(id) {
    if (id === undefined || (this.currentSwal && this.currentSwal.id == id)) {
      this.__swal.close();
    } else if (id !== undefined && this.pendingSwal.length > 0) {
      var indexOfSwalToClose;
      for (var i = 0; i < this.pendingSwal.length; i++) {
        if (this.pendingSwal[i].id == id) {
          indexOfSwalToClose = i;
          break;
        }
      }
      if (!(indexOfSwalToClose === undefined)) {
        this.pendingSwal.splice(indexOfSwalToClose, 1);
        this.refreshPendingText();
      }
    }
  },

  openNextSwal: function() {
    var service = this;
    if (service.pendingSwal.length > 0) {
      var pending = service.pendingSwal.shift();
      service._swalWithId(pending);
    }
  },

  isSwalOpen: function() {
    return this.currentSwal != null && this.currentSwal != undefined;
  },

  closeAndFireCallback: function(id, opts) {
    this.close(id);
    if (this.currentSwal.args && this.currentSwal.args.length > 1 && typeof this.currentSwal.args[1] == 'function') {
      // Currently, programatically closing the swal doesn't invoke the callback.
      var callback = this.currentSwal.args[1];
      callback(opts);
    }
  },

  initialize: function() {
    var service = this;
    var originalClose = this.__swal.close;
    this.__swal.close = function() {
      service.isClosing = true;
      originalClose();
      service.currentSwal = null;
      setTimeout(function() {
        service.onClosed();
      }, 400);
    };
  }
}

Bring this code in anyway you want after sweetalert - if you are using browserify, just add

 module.exports = SwalSevice;

at the bottom of the file.

Initialize

Once it is on the page, initialize the service:

var swalService = new SwalService();

Note: it has not been tested with multiple instances on the same page. For most uses a singleton is what you want anyway.

Create As Usual

To create a sweet alert, use the service's swal method exactly like you would use the original swal method from sweetalert.

var handleId = swalService.swal('My Title', 'My message', 'info');
//or
var handleId = swalService.swal({title: 'My Title', text: 'My message', type: 'info'}, function(args) { ... });

Go ahead, and try it by copying the service code into the console on http://t4t5.github.io/sweetalert/
But now, if you fire another swal afterwards, instead of overriding it, the new swal will be pending and a message shows up.

Close

You can close the current swal through the UI or programatically using swalService.close(). It will close the current swal and open the next swal after a brief (400ms) delay for the animation of closing to finish. Because the swalService's swal method returns a unique id handle, you can close specific swals (not just the current one) by using swalService.close(swalHandleId) even if the swal to target is pending. This is also useful if you have a swal that you don't want the user to close like an indication of loading/pending that doesn't have a close button yet the developer can close it programatically.

Please note that when you have a callback added to you alert that should be invoked upon close, this callback will be automatically invoked if the user initiates the close. But if you wish to invoke this callback while closing the alert programatically, call:

swalService.closeAndFireCallback(id, opts)
// opts will be passed to the callback
// id is an optional identifier for the targeted alert. Defaults to current swal

I believe this can be handled automatically without the need for a separate method (besides .close), but I haven't refactored it yet.

Is Open

The service also has a bit more to the API. You can call swalService.isSwalOpen() to see if there is currently a swal open.

Pending Alert Message

A message about the number of pending swals will appear on the current swal as they queue. You can style this by adding css that targets a span with class "other-messages".

.other-messages {
  color: red;
}

You can also turn off the pending messages altogether by initiailizing the service with:

var swalService = new SwalService({showPendingMessage: false})

Obviously, the state this service is does not support i18n (multilingual audiances), you'll have to override the refreshPendingText() method for that.

Catches

If you don't create a swal correctly, like missing title for instance, the swalService will still think there is a alert open though the base swal library never created one. Be careful to fully construct you alerts.

@Tatsh
Copy link

Tatsh commented Nov 20, 2015

@kentmw Thanks so much. Fixed my issue of needing to present one alert after another.

@QuakePhil
Copy link

Doesn't seem to work if you try and chain two swals, each of which require the user to click "ok" to continue (and not "cancel") - the second swal is shown, but is then immediately closed

@kentmw
Copy link
Author

kentmw commented Jan 27, 2016

@QuakePhil do you mind posting the sequential swalService.swal invocations?

@QuakePhil
Copy link

Actually, I got it to work by nesting the 2nd swal in the callback of the first, and setting closeOnConfirm to false in the first one... (this solution works without the need for swalservice, using swal2)

The code turned out a bit dry, because of the nesting requirement (and so I can't just open a new swal in the callback, the callback itself has to be a direct call to swal, or maybe there is a way to have an intermediate function between the two swals; I just haven't figured it out)

@koullislp
Copy link

@kentmw you are awesome man!! Thanks a lot!!

Your wrapper saved my ass. A few notes. As @QuakePhil mentioned there is an issue if there are multiple alerts in the queue, each with nested responses for confirm/cancel.

In order to deal with this I am closing the parent swal using the ID immediately after the child is created. And in order to pair this Parent (question) - Child(response) scenario, I have made a tiny modification in the wrapper to append all new swals at the beginning of the array/queue and not at the end.

This works with single requests as well. So, consider the following scenario in order to show how the alerts are created:

ID: 1, NAME: swallQA, CONTENT: Would you like to delete this file A? (Confirm/Cancel)
ID: 2, NAME: swallQB, CONTENT: Would you like to delete this file B? (Confirm/Cancel)
ID: 3, NAME: swallCA, CONTENT: Confirmed - delete A
ID: 4, NAME: swallCB, CONTENT: Cancelled - won't delete B

And here is how the alerts will be displayed to the user:

ID: 1, NAME: swallQA, CONTENT: Would you like to delete this file A? (Confirm/Cancel)
ID: 3, NAME: swallCA, CONTENT: Confirmed - delete A
ID: 2, NAME: swallQB, CONTENT: Would you like to delete this file B? (Confirm/Cancel)
ID: 4, NAME: swallCB, CONTENT: Cancelled - won't delete B

If you have any questions let me know.

Once again thanks a lot man, the wrapper did an awesome work.

@kentmw
Copy link
Author

kentmw commented Mar 18, 2016

@koullislp I'm glad it was useful :) Do you mind posting your modifications? I'll update my original post after taking a look at it.

@koullislp
Copy link

@kentmw

Only one word changed in your code:

28c28
<       this.pendingSwal.push(pending);
---
>       this.pendingSwal.unshift(pending);

And the way I am calling it:

var handleId = swalService.swal({
        title: "Are you sure?",
        text: "You are about to delete file"+filename,
        type: "warning",
        showCancelButton: true,
        confirmButtonText: "Confirm",
        cancelButtonText: "Cancel",
        closeOnConfirm: false
    }, function(isConfirm) {
        if (isConfirm) {
            sendRequest("POST", "", "delete", function(actionResult) {
                var handleSecondId = swalService.swal({
                    title: "Result", 
                    text: "deleted!"
                });
                swalService.closeAndFireCallback(handleId);
            }
        }
    });

@Hucxley
Copy link

Hucxley commented May 18, 2016

Any chance you guys can help me conceptualize this using promises? I've been using this with https://github.com/limonte/sweetalert2, but the author feels an alert queue is "spammy" (sweetalert2/sweetalert2#125). However, it's a perfect solution for my use case, and I'm not really familiar enough with promises to figure out how to create and resolve them using his fork of sweetalert and your addon. I suppose I could add a then(function(){}) and put a resolve() and close() in there, but I don't know if that will actually work. Again, I'm not as well versed on promises, yet.

Any suggestions? Basically, I need a way to use this with promises instead of callback functions.

@limonte
Copy link

limonte commented Jun 7, 2016

SweetAlert2 supports modals queue: https://sweetalert2.github.io/#chaining-modals

@koullislp
Copy link

Thanks a lot dude, you are awesome!

@daattali
Copy link

daattali commented Mar 1, 2018

@kentmw that swalservice code is amazing, thank you so much (I know swal2.0 fixes this but I'm purposely staying with 1.0 because I find it much simpler and nicer to use, and also has a few features that were removed in 2.0)

@daattali
Copy link

daattali commented May 18, 2024

Has anyone here tried to implement a SwalService-like queue in sweetalert2? I know they support chaining, but it requires a much more complex syntax, rather than being able to trivially just make sequential alert calls. For my usecase, which is wrapping the library in a different programming language, that wouldn't work, but SwalService worked wonderfully!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

9 participants