Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

SweetAlert: Killing it with Promises

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $12.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

For our last trick, Google for a library called SweetAlert2. Very simply, this library give us sweet alert boxes, like this. And you can customize it in a lot of ways, like having a "Yes" and "Cancel" button.

We're going to use SweetAlert so that when we click the delete icon, an alert opens so the user can confirm the delete before we actually do it.

SweetAlert: Basic Usage

To get this installed, go to the CDN. Copy the JavaScript file first. This time, instead of putting this in our base layout, we'll add the JavaScript to just this page: index.html.twig. Add the <script src=""> and paste:

... lines 1 - 53
{% block javascripts %}
{{ parent() }}
... line 56
<script src="https://cdn.jsdelivr.net/sweetalert2/6.1.0/sweetalert2.min.js"></script>
... lines 58 - 81
{% endblock %}

This also comes with a CSS file: copy that too. Back in index.html.twig, override a block called stylesheets and add the endblock. Call parent() to include the normal stylesheets, and then add the link tag with this path:

... lines 1 - 47
{% block stylesheets %}
{{ parent() }}
<link rel="stylesheet" href="https://cdn.jsdelivr.net/sweetalert2/6.1.0/sweetalert2.min.css" />
{% endblock %}
... lines 53 - 83

Perfect!

This library exposes a global swal() function. Copy the timer example - it's somewhat similar to what we want. Then, open RepLogApp.js. Remember, whenever we reference a global object, we like to pass it into our self-executing function. You don't need to do this, but it's super hipster. Pass swal at the bottom and also swal on top:

... lines 1 - 2
(function(window, $, Routing, swal) {
... lines 4 - 192
})(window, jQuery, Routing, swal);

If you want some auto-completion on that library, you can of course select it and hit option+enter or alt+enter to tell PhpStorm to download it.

Down in handleRepLogDelete, here's the plan. First, we'll open the alert. And then, when the user clicks "OK", we'll run all of the code below that actually deletes the RepLog. To prepare for that, isolate all of that into its own new method: _deleteRepLog with a $link argument:

... lines 1 - 2
(function(window, $, Routing, swal) {
... lines 4 - 48
handleRepLogDelete: function (e) {
e.preventDefault();
var $link = $(e.currentTarget);
... lines 53 - 67
},
_deleteRepLog: function($link) {
$link.addClass('text-danger');
$link.find('.fa')
.removeClass('fa-trash')
.addClass('fa-spinner')
.addClass('fa-spin');
var deleteUrl = $link.data('url');
var $row = $link.closest('tr');
var self = this;
$.ajax({
url: deleteUrl,
method: 'DELETE'
}).then(function() {
$row.fadeOut('normal', function () {
$(this).remove();
self.updateTotalWeightLifted();
});
})
},
... lines 90 - 192
})(window, jQuery, Routing, swal);

This doesn't change anything: we could still just call this function directly from above. But instead, paste the SweetAlert code and update the title - "Delete this log" - and the text - "Did you not actually lift this?". And remove the timer option. Instead, add showCancelButton: true:

Go Deeper!

In version 7, when you click "Cancel", the reject handler is not called anymore. Instead, the success handler is called, but you can use the promise argument to check which button was clicked! See https://github.com/sweetalert2/sweetalert2/releases/tag/v7.0.0 for details!

... lines 1 - 2
(function(window, $, Routing, swal) {
... lines 4 - 26
$.extend(window.RepLogApp.prototype, {
... lines 28 - 48
handleRepLogDelete: function (e) {
e.preventDefault();
var $link = $(e.currentTarget);
... lines 53 - 54
swal({
title: 'Delete this log?',
text: 'What? Did you not actually lift this?',
showCancelButton: true
}).then(
function () {
... line 61
},
function () {
... line 64
}
);
},
... lines 69 - 174
});
... lines 176 - 192
})(window, jQuery, Routing, swal);

With just that, we should be able to refresh, and... oh! Error!

swal is not defined

Of course! I need be more careful with my ordering. Right now, we still need to manually make sure that we include the libraries in the correct order: including SweetAlert first, so that it's available to RepLogApp:

... lines 1 - 53
{% block javascripts %}
... lines 55 - 56
<script src="https://cdn.jsdelivr.net/sweetalert2/6.1.0/sweetalert2.min.js"></script>
<script src="{{ asset('assets/js/RepLogApp.js') }}"></script>
... lines 59 - 81
{% endblock %}

We're going to fix this pesky problem in a future tutorial.

Ok, try it again. Things look happy! Now, click the little trash icon. Boom! We have "OK" and "Cancel".

Handling a SweetAlert Promise

When we call swal(), guess what it returns? A promise! A freaking Promise! We can tell because the code has a .then chained to it, with two arguments. The first argument is the function that's called on success, and the second is called when the Promise is rejected. But, we already knew that.

Specifically, for SweetAlert, the success, or resolved handler is called if we click "OK", and the reject handler is called if we click "Cancel". Easy! Above the swal() call, add var self = this. Then, inside the success handler, use self._deleteRepLog($link):

... lines 1 - 2
(function(window, $, Routing, swal) {
... lines 4 - 26
$.extend(window.RepLogApp.prototype, {
... lines 28 - 48
handleRepLogDelete: function (e) {
... lines 50 - 53
var self = this;
swal({
... lines 56 - 58
}).then(
function () {
self._deleteRepLog($link);
},
function () {
... line 64
}
);
},
... lines 69 - 174
});
... lines 176 - 192
})(window, jQuery, Routing, swal);

Down in the reject function, we don't need to do anything. Just call console.log('canceled'):

... lines 1 - 2
(function(window, $, Routing, swal) {
... lines 4 - 26
$.extend(window.RepLogApp.prototype, {
... lines 28 - 48
handleRepLogDelete: function (e) {
... lines 50 - 53
var self = this;
swal({
... lines 56 - 58
}).then(
function () {
self._deleteRepLog($link);
},
function () {
console.log('canceled');
}
);
},
... lines 69 - 174
});
... lines 176 - 192
})(window, jQuery, Routing, swal);

Let's try it! Refresh, click the trash icon and hit "Cancel". Yea, there's the log! Now hit "OK". It deletes it! Guys, this is why understanding promises is so important.

And we also know that instead of passing two arguments to .then(), we could instead chain a .catch() onto this:

... lines 1 - 2
(function(window, $, Routing, swal) {
... lines 4 - 26
$.extend(window.RepLogApp.prototype, {
... lines 28 - 48
handleRepLogDelete: function (e) {
... lines 50 - 53
var self = this;
swal({
... lines 56 - 58
}).then(function () {
self._deleteRepLog($link);
}).catch(function(arg) {
console.log('canceled', arg);
});
},
... lines 65 - 170
});
... lines 172 - 188
})(window, jQuery, Routing, swal);

Leave a comment!

4
Login or Register to join the conversation

Also, the link to the cdn is kinda small now and you need to drill down for the css file. It took a bit to figure out. But here is the link that makes everything obvious https://www.jsdelivr.com/pa...

1 Reply

That's cool, thanks for sharing it!

Reply

Hey Stephane

Thanks for informing us! I'm going to fix it

Cheers!

Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

This tutorial uses an older version of Symfony... but since it's a JavaScript tutorial, the concepts are still ? valid!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^7.2.0",
        "symfony/symfony": "3.1.*", // v3.1.10
        "twig/twig": "2.10.*", // v2.10.0
        "doctrine/orm": "^2.5", // v2.7.1
        "doctrine/doctrine-bundle": "^1.6", // 1.10.3
        "doctrine/doctrine-cache-bundle": "^1.2", // 1.3.2
        "symfony/swiftmailer-bundle": "^2.3", // v2.4.0
        "symfony/monolog-bundle": "^2.8", // 2.12.0
        "symfony/polyfill-apcu": "^1.0", // v1.2.0
        "sensio/distribution-bundle": "^5.0", // v5.0.22
        "sensio/framework-extra-bundle": "^3.0.2", // v3.0.16
        "incenteev/composer-parameter-handler": "^2.0", // v2.1.2
        "friendsofsymfony/user-bundle": "~2.0@dev", // dev-master
        "doctrine/doctrine-fixtures-bundle": "~2.3", // v2.4.1
        "doctrine/doctrine-migrations-bundle": "^1.2", // v1.2.1
        "composer/package-versions-deprecated": "^1.11", // 1.11.99
        "friendsofsymfony/jsrouting-bundle": "^1.6" // 1.6.0
    },
    "require-dev": {
        "sensio/generator-bundle": "^3.0", // v3.1.1
        "symfony/phpunit-bridge": "^3.0" // v3.1.6
    }
}
userVoice