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 SubscribeTo get Turbo Drive to work super nicely, we're going to need to hook into a few events, like turbo:before-cache
. Before we're done, we'll listen into even more of these to help us properly load JavaScript widgets, add transitions, and do more craziness when we talk about Turbo Frames.
So instead of putting all that logic right here in app.js
, let's organize a bit. There's no right or wrong way to do this, but let's create a class that holds all of the special Turbo logic. In the assets/
directory, add a sub-directory called turbo/
and, inside, a new file: turbo-helper.js
. Start with const TurboHelper = class {}
with a constructor() {}
inside.
Now, head back to app.js
, copy all of this code, and paste! When we did that, PhpStorm helpfully added the import { Modal }
for us. At the bottom of this file, export default new TurboHelper()
.
import { Modal } from 'bootstrap'; | |
const TurboHelper = class { | |
constructor() { | |
document.addEventListener('turbo:before-cache', () => { | |
if (document.body.classList.contains('modal-open')) { | |
const modalEl = document.querySelector('.modal'); | |
const modal = Modal.getInstance(modalEl); | |
modalEl.classList.remove('fade'); | |
modal._backdrop._config.isAnimated = false; | |
modal.hide(); | |
modal.dispose(); | |
} | |
// internal way to see if sweetalert2 has been imported yet | |
if (__webpack_modules__[require.resolveWeak('sweetalert2')]) { | |
// because we know it's been imported, this will run synchronously | |
import(/* webpackMode: 'weak' */'sweetalert2').then((Swal) => { | |
if (Swal.default.isVisible()) { | |
Swal.default.getPopup().style.animationDuration = '0ms' | |
Swal.default.close(); | |
} | |
}) | |
} | |
}); | |
} | |
} | |
export default new TurboHelper(); |
This is kind of cool: it instantiates a new instance of our object and exports it. It won't really matter for us... but thanks to this, each time we import this module, we will get the same one instance of this object.
In app.js
, delete all the original code and then import './turbo/turbo-helper'
. We don't need to set that to a variable... and just by importing it, the object will be instantiated and the listeners will be registered. So... this should be enough to get things working!
... lines 1 - 10 | |
// start the Stimulus application | |
import './bootstrap'; | |
import './turbo/turbo-helper'; |
Let's try! Refresh, click to remove an item, go back and go forward. Yep! All good.
Now that we have a class, we can organize a bit more. Copy the modal code, remove it, create a new method below called closeModal()
and paste. Then, back up inside the turbo:before-cache
callback, say this.closeModal()
.
Repeat this for Sweetalert: copy all of the Sweetalert code, create a new method called closeSweetalert()
, paste... and... then back in the callback, use it: this.closeSweetalert()
.
import { Modal } from 'bootstrap'; | |
const TurboHelper = class { | |
constructor() { | |
document.addEventListener('turbo:before-cache', () => { | |
this.closeModal(); | |
this.closeSweetalert(); | |
}); | |
} | |
closeModal() { | |
if (document.body.classList.contains('modal-open')) { | |
const modalEl = document.querySelector('.modal'); | |
const modal = Modal.getInstance(modalEl); | |
modalEl.classList.remove('fade'); | |
modal._backdrop._config.isAnimated = false; | |
modal.hide(); | |
modal.dispose(); | |
} | |
} | |
closeSweetalert() { | |
// internal way to see if sweetalert2 has been imported yet | |
if (__webpack_modules__[require.resolveWeak('sweetalert2')]) { | |
// because we know it's been imported, this will run synchronously | |
import(/* webpackMode: 'weak' */'sweetalert2').then((Swal) => { | |
if (Swal.default.isVisible()) { | |
Swal.default.getPopup().style.animationDuration = '0ms' | |
Swal.default.close(); | |
} | |
}) | |
} | |
} | |
} | |
export default new TurboHelper(); |
That looks better! Let's... make sure we didn't mess anything up. Do the same dance as before: refresh, click remove, go back and go forward. All good!
Next: let's learn what types of things can go wrong when including third-party hosted JavaScript, like a JavaScript widget or analytics code. This type of JavaScript is usually supposed to be included in the body of the page... and often it expects full page refreshes.
Hey It O.!
I got bit by the Symfony World conference and 2 new libraries I built for it - they're kind of fun, and fit right into the Turbo & Stimulus world (one is built on top of Stimulus): https://github.com/symfony/... and https://github.com/symfony/...
Anyways, we should be right back on our normal schedule, I think starting Tuesday. Thanks for your patience - it's been a busy few weeks because of those libraries, but I think they'll be really useful!
Cheers!
// composer.json
{
"require": {
"php": ">=8.1",
"ext-ctype": "*",
"ext-iconv": "*",
"composer/package-versions-deprecated": "1.11.99.1", // 1.11.99.1
"doctrine/annotations": "^1.0", // 1.13.1
"doctrine/doctrine-bundle": "^2.2", // 2.3.2
"doctrine/orm": "^2.8", // 2.9.1
"phpdocumentor/reflection-docblock": "^5.2", // 5.2.2
"sensio/framework-extra-bundle": "^6.1", // v6.1.4
"symfony/asset": "5.3.*", // v5.3.0-RC1
"symfony/console": "5.3.*", // v5.3.0-RC1
"symfony/dotenv": "5.3.*", // v5.3.0-RC1
"symfony/flex": "^1.3.1", // v1.18.5
"symfony/form": "5.3.*", // v5.3.0-RC1
"symfony/framework-bundle": "5.3.*", // v5.3.0-RC1
"symfony/property-access": "5.3.*", // v5.3.0-RC1
"symfony/property-info": "5.3.*", // v5.3.0-RC1
"symfony/proxy-manager-bridge": "5.3.*", // v5.3.0-RC1
"symfony/runtime": "5.3.*", // v5.3.0-RC1
"symfony/security-bundle": "5.3.*", // v5.3.0-RC1
"symfony/serializer": "5.3.*", // v5.3.0-RC1
"symfony/twig-bundle": "5.3.*", // v5.3.0-RC1
"symfony/ux-chartjs": "^1.1", // v1.3.0
"symfony/ux-turbo": "^1.3", // v1.3.0
"symfony/ux-turbo-mercure": "^1.3", // v1.3.0
"symfony/validator": "5.3.*", // v5.3.0-RC1
"symfony/webpack-encore-bundle": "^1.9", // v1.11.2
"symfony/yaml": "5.3.*", // v5.3.0-RC1
"twig/extra-bundle": "^2.12|^3.0", // v3.3.1
"twig/intl-extra": "^3.2", // v3.3.0
"twig/string-extra": "^3.3", // v3.3.1
"twig/twig": "^2.12|^3.0" // v3.3.2
},
"require-dev": {
"doctrine/doctrine-fixtures-bundle": "^3.4", // 3.4.0
"symfony/debug-bundle": "^5.2", // v5.3.0-RC1
"symfony/maker-bundle": "^1.27", // v1.31.1
"symfony/monolog-bundle": "^3.0", // v3.7.0
"symfony/stopwatch": "^5.2", // v5.3.0-RC1
"symfony/var-dumper": "^5.2", // v5.3.0-RC1
"symfony/web-profiler-bundle": "^5.2", // v5.3.0-RC1
"zenstruck/foundry": "^1.10" // v1.10.0
}
}
// package.json
{
"devDependencies": {
"@babel/preset-react": "^7.0.0", // 7.13.13
"@fortawesome/fontawesome-free": "^5.15.3", // 5.15.3
"@hotwired/turbo": "^7.0.0-beta.5", // 1.2.6
"@popperjs/core": "^2.9.1", // 2.9.2
"@symfony/stimulus-bridge": "^2.0.0", // 2.1.0
"@symfony/ux-chartjs": "file:vendor/symfony/ux-chartjs/Resources/assets", // 1.1.0
"@symfony/ux-turbo": "file:vendor/symfony/ux-turbo/Resources/assets", // 0.1.0
"@symfony/ux-turbo-mercure": "file:vendor/symfony/ux-turbo-mercure/Resources/assets", // 0.1.0
"@symfony/webpack-encore": "^1.0.0", // 1.3.0
"bootstrap": "^5.0.0-beta2", // 5.0.1
"chart.js": "^2.9.4",
"core-js": "^3.0.0", // 3.13.0
"jquery": "^3.6.0", // 3.6.0
"react": "^17.0.1", // 17.0.2
"react-dom": "^17.0.1", // 17.0.2
"regenerator-runtime": "^0.13.2", // 0.13.7
"stimulus": "^2.0.0", // 2.0.0
"stimulus-autocomplete": "https://github.com/weaverryan/stimulus-autocomplete#toggle-event-always-dist", // 2.0.0
"stimulus-use": "^0.24.0-1", // 0.24.0-2
"sweetalert2": "^11.0.8", // 11.0.12
"webpack-bundle-analyzer": "^4.4.0", // 4.4.2
"webpack-notifier": "^1.6.0" // 1.13.0
}
}
2 days without videos guys? :(