If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeFor 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.
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".
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); |
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...
// 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
}
}
The link to SweetAlert2 has change : https://sweetalert2.github.io/