Stimulus: Escribir JavaScript profesional
Sabemos cómo escribir HTML en nuestras plantillas. Y manejamos CSS con Tailwind. ¿Qué pasa con JavaScript? Bueno, como con CSS, hay un archivo app.js, y ya está incluido en la página. Así que puedes poner aquí el JavaScript que quieras.
Pero te recomiendo encarecidamente que utilices una pequeña, pero malvada, biblioteca JavaScript llamada Stimulus. Es una de mis cosas favoritas de Internet. Tomas una parte de tu HTML existente y lo conectas a un pequeño archivo JavaScript, llamado controlador. Esto te permite añadir un comportamiento: por ejemplo, cuando pulses este botón, se llamará al método greet del controlador.
¡Y eso es todo! Seguro que Stimulus tiene más funciones, pero ya entiendes el núcleo de su funcionamiento. A pesar de su simplicidad, nos permitirá construir cualquier funcionalidad JavaScript y de interfaz de usuario que necesitemos, de forma fiable y predecible. Así que vamos a instalarlo.
Instalar Stimulus
Stimulus es una librería JavaScript, pero Symfony tiene un bundle que ayuda a integrarla. En tu terminal, si quieres ver lo que hace la receta, confirma tus cambios. Yo ya lo he hecho. Luego ejecuta:
composer require symfony/stimulus-bundle
Cuando esto termine... la receta ha hecho algunos cambios. Veamos los más importantes. El primero está en app.js: nuestro archivo JavaScript principal. Ábrelo y ya está.
| import './bootstrap.js'; | |
| /* | |
| * Welcome to your app's main JavaScript file! | |
| * | |
| * This file will be included onto the page via the importmap() Twig function, | |
| * which should already be in your base.html.twig. | |
| */ | |
| import './styles/app.css'; | |
| console.log('This log comes from assets/app.js - welcome to AssetMapper! 🎉'); |
Añadió un import en la parte superior - ./bootstrap.js - a un nuevo archivo que vive justo al lado de éste.
| import { startStimulusApp } from '@symfony/stimulus-bundle'; | |
| const app = startStimulusApp(); | |
| // register any custom, 3rd party controllers here | |
| // app.register('some_controller_name', SomeImportedController); |
El propósito de este archivo es iniciar el motor Stimulus. Además, enimportmap.php, la receta añadió el paquete JavaScript @hotwired/stimulus junto con otro archivo que ayuda a arrancar Stimulus dentro de Symfony.
| // ... lines 1 - 15 | |
| return [ | |
| // ... lines 17 - 20 | |
| '@hotwired/stimulus' => [ | |
| 'version' => '3.2.2', | |
| ], | |
| '@symfony/stimulus-bundle' => [ | |
| 'path' => './vendor/symfony/stimulus-bundle/assets/dist/loader.js', | |
| ], | |
| ]; |
Por último, la receta creó un directorio assets/controllers/. Aquí es donde vivirán nuestros controladores personalizados. ¡E incluía un controlador de demostración para que pudiéramos empezar! ¡Gracias!
| import { Controller } from '@hotwired/stimulus'; | |
| /* | |
| * This is an example Stimulus controller! | |
| * | |
| * Any element with a data-controller="hello" attribute will cause | |
| * this controller to be executed. The name "hello" comes from the filename: | |
| * hello_controller.js -> "hello" | |
| * | |
| * Delete this file or adapt it for your use! | |
| */ | |
| export default class extends Controller { | |
| connect() { | |
| this.element.textContent = 'Hello Stimulus! Edit me in assets/controllers/hello_controller.js'; | |
| } | |
| } |
Estos archivos de controlador tienen una importante convención de nombres. Como se llama hello_controller.js, para conectarlo con un elemento de la página, utilizaremos data-controller="hello".
Cómo funciona Stimulus
Así es como funciona. En cuanto Stimulus vea un elemento en la página condata-controller="hello", instanciará una nueva instancia de este controlador y llamará al método connect(). Así, este controlador hello cambiará automática e instantáneamente el contenido del elemento al que está unido.
Y ya podemos verlo. Actualiza la página. Stimulus está ahora activo en nuestro sitio. Esto significa que está buscando elementos con data-controller. Hagamos algo salvaje: inspecciona los elementos de la página, busca cualquier elemento -como esta etiqueta de anclaje- y añade data-controller="hello". Observa lo que ocurre cuando hago clic en desactivar para activar este cambio. ¡Pum! Stimulus ha visto ese elemento, ha instanciado nuestro controlador y ha llamado al método connect(). Y puedes hacer esto tantas veces como quieras en la página.
La cuestión es: no importa cómo llegue un elemento data-controller a tu página, Stimulus lo ve. Así que si hacemos una llamada Ajax que devuelva HTML y ponemos eso en la página... sí, Stimulus va a verlo y nuestro JavaScript va a funcionar. Ésa es la clave: cuando escribes JavaScript con Stimulus, tu JavaScript siempre funcionará, independientemente de cómo y cuándo se añada ese HTML a la página.
Crear un controlador Stimulus que se pueda cerrar
Utilicemos Stimulus para activar nuestro botón de cierre. En el directorio assets/controller/, duplica hello_controller.js y crea uno nuevo llamadocloseable_controller.js.
Borraré casi todo y me limitaré a lo más básico: importaController de Stimulus... y luego crea una clase que lo extienda.
| import { Controller } from '@hotwired/stimulus'; | |
| export default class extends Controller { | |
| // ... lines 4 - 6 | |
| } |
Esto no hace nada, pero ya podemos adjuntarlo a un elemento de la página. Éste es el plan: vamos a adjuntar el controlador a todo el elemento aside. Luego, cuando pulsemos este botón, eliminaremos el elemento aside.
Ese elemento vive en templates/main/_shipStatusAside.html.twig. Para adjuntar el controlador, añade data-controller="closeable". ¿Ves ese autocompletado? Proviene de un plugin de Stimulus para PhpStorm.
| <aside | |
| // ... line 2 | |
| data-controller="closeable" | |
| > | |
| // ... lines 5 - 35 | |
| </aside> |
Si nos desplazamos y actualizamos, aún no ocurrirá nada: el botón de cerrar no funciona. Pero abre la consola de tu navegador. ¡Qué bien! Stimulus añade útiles mensajes de depuración: que se está iniciando y luego - lo que es importante - closeable initialize,closeable connect.
Esto significa que sí vio el elemento data-controller e inicializó ese controlador.
Así que volvamos a nuestro objetivo: cuando pulsemos este botón, queremos llamar a código dentro del controlador cerrable que elimine el aside. En closeable_controller.js, añade un nuevo método llamado, qué tal, close(). Dentro, digamos this.element.remove().
| // ... lines 1 - 2 | |
| export default class extends Controller { | |
| close() { | |
| this.element.remove(); | |
| } | |
| } |
En Stimulus, this.element será siempre el elemento al que esté unido tu controlador. Por tanto, este elemento aside. Pero por lo demás, este código es JavaScript estándar: cada Elemento tiene un método remove().
Para llamar al método close(), en el botón, añade data-action="" luego el nombre de nuestro controlador - closeable - un signo #, y el nombre del método: close.
| <aside | |
| // ... line 2 | |
| data-controller="closeable" | |
| > | |
| <div class="flex justify-between mt-11 mb-7"> | |
| // ... line 6 | |
| <button data-action="closeable#close"> | |
| <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 448 512"><!--!Font Awesome Pro 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2024 Fonticons, Inc.--><path fill="#fff" d="M384 96c0-17.7 14.3-32 32-32s32 14.3 32 32V416c0 17.7-14.3 32-32 32s-32-14.3-32-32V96zM9.4 278.6c-12.5-12.5-12.5-32.8 0-45.3l128-128c12.5-12.5 32.8-12.5 45.3 0s12.5 32.8 0 45.3L109.3 224 288 224c17.7 0 32 14.3 32 32s-14.3 32-32 32l-178.7 0 73.4 73.4c12.5 12.5 12.5 32.8 0 45.3s-32.8 12.5-45.3 0l-128-128z"/></svg> | |
| </button> | |
| </div> | |
| // ... lines 11 - 35 | |
| </aside> |
Animar el cierre
Ya está Hora de probar. ¡Clic! ¡Ya está! ¡Pero quiero que sea más elegante! Quiero que se anime al cerrarse en lugar de ser instantáneo. ¿Podemos hacerlo? ¡Claro que sí! Y no necesitamos mucho JavaScript... porque el CSS moderno es increíble.
Sobre el elemento aside, añade una nueva clase CSS -puede ir en cualquier sitio- llamadatransition-all.
Es una clase Tailwind que activa las transiciones CSS. Esto significa que si cambian ciertas propiedades de estilo -como que la anchura se ponga de repente a 0- hará una transición de ese cambio, en lugar de cambiarlo instantáneamente.
También añade overflow-hidden para que, al reducirse la anchura, no cree una extraña barra de desplazamiento.
Si probamos esto ahora, se sigue cerrando instantáneamente. Eso es porque no hay nada que transicionar: no estamos cambiando la anchura... sólo eliminando el elemento.
Pero fíjate en esto. Inspecciona el elemento y busca el aside: aquí está. Cambia manualmente la anchura a 0. ¡Genial! ¡Vas pequeñito, grande, pequeñito, grande, pequeñito! El lado CSS de las cosas está funcionando.
De vuelta en nuestro controlador, en lugar de eliminar el elemento, tenemos que cambiar la anchura a cero, esperar a que termine la transición CSS y luego eliminar el elemento. Podemos hacer lo primero con this.element.style.width = 0.
| <aside | |
| // ... line 2 | |
| data-controller="closeable" | |
| > | |
| <div class="flex justify-between mt-11 mb-7"> | |
| // ... line 6 | |
| <button data-action="closeable#close"> | |
| <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 448 512"><!--!Font Awesome Pro 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2024 Fonticons, Inc.--><path fill="#fff" d="M384 96c0-17.7 14.3-32 32-32s32 14.3 32 32V416c0 17.7-14.3 32-32 32s-32-14.3-32-32V96zM9.4 278.6c-12.5-12.5-12.5-32.8 0-45.3l128-128c12.5-12.5 32.8-12.5 45.3 0s12.5 32.8 0 45.3L109.3 224 288 224c17.7 0 32 14.3 32 32s-14.3 32-32 32l-178.7 0 73.4 73.4c12.5 12.5 12.5 32.8 0 45.3s-32.8 12.5-45.3 0l-128-128z"/></svg> | |
| </button> | |
| </div> | |
| // ... lines 11 - 35 | |
| </aside> |
La parte complicada es esperar a que termine la transición CSS antes de eliminar el elemento. Para ayudarte con eso, voy a pegar un método en la parte inferior de nuestro controlador.
| // ... lines 1 - 2 | |
| export default class extends Controller { | |
| async close() { | |
| this.element.style.width = '0'; | |
| // ... lines 6 - 8 | |
| } | |
| #waitForAnimation() { | |
| return Promise.all( | |
| this.element.getAnimations().map((animation) => animation.finished), | |
| ); | |
| } | |
| } |
Si no estás familiarizado, el signo # hace que éste sea un método privado en JavaScript: un pequeño detalle. Este código parece lujoso, pero tiene una función sencilla: pedir al elemento que nos diga cuándo han terminado todas sus animaciones CSS.
Gracias a eso, aquí arriba, podemos decir await this.#waitForAnimation(). Y siempre que utilices await, tienes que poner async en la función alrededor de esto. No entraré en detalles sobre async, pero eso no cambiará el funcionamiento de nuestro código.
| // ... lines 1 - 2 | |
| export default class extends Controller { | |
| async close() { | |
| this.element.style.width = '0'; | |
| await this.#waitForAnimation(); | |
| this.element.remove(); | |
| } | |
| #waitForAnimation() { | |
| return Promise.all( | |
| this.element.getAnimations().map((animation) => animation.finished), | |
| ); | |
| } | |
| } |
¡Comprobemos el resultado! Actualiza. Y... Me encanta.
A continuación, todo el mundo quiere una aplicación de página única, ¿verdad? Un sitio en el que no haya refrescos de página completa. Pero para construir una, ¿no necesitamos utilizar un framework JavaScript como React? ¡No! Vamos a transformar nuestra aplicación en una aplicación de una sola página en... unos 3 minutos con Turbo.
24 Comments
Nice and very interesting tutorial.
One question, how do you bring back the aside part of the page after hiding it ?
In the video, you click the arrow button once to trigger the
closeable#closemethod, and then looks like you click again the screen to bring back the aside div. I'm doing so to not avail or am I missing something ?BTW, how is doing Ryan ?
Hey @symfonyace ,
Yeah, good question, because it's probably not very clear from the video. Actually, Ryan just reloads the page after the sidebar is gone i.e. he clicks the button, the sidebar is animated and removed completely, then he reloads the page in the browser (presses a shortcut like Cmd + R on a Mac to reload the page) that loads a new HTML page again with the sidebar, and finally clicks the button again to remove the sidebar. And so on, just super simple and no magic ;)
Thank you for asking about Ryan! Unfortunately, Ryan passed away last year. You can read the related blog post about him here: https://symfonycasts.com/blog/remembering-ryan-weaver
Cheers!
I am trying to follow this course in 2026, and I do not see the _shipStatusAside.html.twig. What am I missing? This is very hard to follow to be honest.
Update: I found the "aside" element in homepage.html.twig and was able to continue the video.
Also, let me just add that not everybody is using PHPStorm. I am using VSCode and not all that functionality you show off is available here. I feel like this course should focus on purely Symfony and not utilize some features PHPStorm includes. I get it: it is the defacto IDE for PHP. Just know that some of us just learning Symfony did not pay for PHPStorm and are using freely available resources instead. And I had to heavily add extensions to VSCode to even follow this course from the beginning. Just letting my voice heard.
Hey Kay,
I'm happy to see you were able to find that youself, good job!
Thank you for your interest in SymfonyCasts tutorials! Yes, you're totally right, not all devs use PhpStorm for their own reasons, also it's just a matter of taste too. Though we would like to recommend it as the best IDE for PHP and Symfony projects. We're trying to avoid using specific PhpStorm features unless it helps to save some time in the video, and when users should be clear enough on how to do that manually. We constantly try to keep it in mind, and I believe you should not encounter such moments in the tutorials very often.
Anyway, thanks again for reminding that, we appreciate it. And I agree with you on this. If you ever have any problems following our tutorials - please, feel free to ask your questions in the comments section below the video and our support team will help you :)
Cheers!
OMG! I just realized that for some reason, I jumped ahead to chapter 18 instead of doing chapter 15! Idk how that happened, I'm so sorry. I was just following along the track and it landed me on Chapter 18, and I didn't check!
You ARE creating that partial twig file in Chapter 15 that I was kinda missing in this video. Urgh!!! Again, I'm so sorry.
Hey Kay,
Oh, no problem! That may happen if you open that Ch18 in a new tab and play the video, then the system could return you back to that chapter when you ocntinue the course. Or it was just an accidental click that may happen too. Anyway, I'm glad to hear you're back on the right path ;)
If you ever have any questions following our tutorials - please, just ask!
Cheers!
Much appreciated. Thanks!
Any help your side sockets io how to use in symfony how to install example npm install express@4
Hey Himanshu,
If you're talking about web sockets - we have a few screencasts about Mercure Symfony component, kind of like web sockets but cooler :) You can check them here: https://symfonycasts.com/search?q=mercure&sort=most relevant&types[]=video - you can leverage our advanced search on SymfonyCasts to find more content related to a specific stack. Unfortunately, no content about express@4.
I hope it helps!
Cheers!
The
preloadoption is now obsoleted inimportmap.phpfile on the version 7.2 of Symfony ?It’s no longer mentioned in the multi-line comment.
Hey Rom,
I don't see we use that on this tutorial, or am I missing something? If so, please, could you link me to the correct chapter? Also, could you tell me what exactly deprecation message do you see about that preload option? Or do you mean it's gone from the recipe now? If so, IIRC that should be enabled by default now, you can check the source HTML code to see it includes preload. If not, you can always enable it manually in the importmap youself.
Cheers!
Honestly, I feel that there is a bit too much info for introduction tutorial. Enum, stimulus, tailwind... zis is all a bit overwhelming.
Hey @John-S
Thank you for your feedback and I agree with you, all of this can be too much. By the way, we have a dedicated tutorial for Stimulus and Symfony UX https://symfonycasts.com/screencast/stimulus
Cheers!
first of all thank you for these fabulous and very precious courses for those like me who want to learn more, I have a question (perhaps I have already asked it in another post) but a few months ago I remember that you had published a recent video course regarding the concept of asset manager which seemed be the last frontier by putting stimulus and turbo on the bench, am I remembering correctly? My memory recalls the confetti script example. If I don't remember correctly, I ask for forgiveness. I would like to understand, nowadays what is the best method to manage attractive and latest generation UX? Thanks for all ;)
Hey @pasquale_pellicani ,
It depends on your needs, the project, and your knowledge. We do recommend Stimulus/Turbo as it's a straightforward tool with a nice Symfony integration. This is just ideal if you have a server-side project and want to give it a feeling of single-page application. It's ideal if you're OK with rendering templates server-side and let Stimulus handle it for you. In short, you just need to understand Stimulus, it's basic idea and how it works. For this - we do recommend you to watch this Stimulus course :)
But if Stilulus isn't enough for your needs, i.e. if you need more complex UI, if you want to render UI client-side without sending AJAX requests to the server and get already rendered template response that Stimulus just insert into the page for you - then take a look at more serious UI frameworks like VueJS, or React/Angular for example. They all are kinda good, and if you already know something well - probably better to use that as you will spend more time on developing instead of learning and fixing some edge cases when you start with a completely new technology for you.
So that's my personal opinion on it. In. short, if Stimulus and Turbo is enough for you - great, definitely go with it. If not - look at something more serious and focused on client-side rendering like Vue/React/Angular.
I hope that helps!
Cheers!
thanks you always come for the precious answers @Victor , so what do I do? Am I waiting for a reactJs + Symfony video course? ;)
Hey @pasquale_pellicani ,
Haha, we're mostly leaning towards VueJS instead of ReactJS, and we do have some screencasts about it here:
We will definitely want to record more Vue-related content but I don't know when it might be released, we're busy with other good topics lately.
About ReactJS, we worked with it before, and even have a course: https://symfonycasts.com/screencast/reactjs - but then we re-wrote that part of the system with Stimulus instead. and it works great in our specific case :)
Cheers!
So, why would one choose Vue or React or stimulus? What do you think the choice is based on?
Is this the most recent guide on the stimulus/turbo topic? Or are there more recent ones?
thanks
Hey @pasquale_pellicani ,
That https://symfonycasts.com/screencast/last-stack course is the latest indeed, but it includes more topics besides Stimulus & Turbo. If you're looking for a Stimulus and Turbo course in more details, I would recommend you to watch this topic-specific courses:
Well, first of all, Stimulus is not the same as Vue/React, so technically the difference is the same I mentioned above. If you're looking for client-side UI rendering - take a look at Vue/React. If you're OK with rendering UI server-side - Stimulus might be enough for you, it will help you dynamically inject. already server-side rendered templates into the page via AJAX requests.
So if simplify that to "Vue or React"? Well, simply, a matter of taste mostly. Something gets more hipster with a new release, etc. Sometimes people start working with something and so are already familiar with the technology and it makes sense to continue working with it while it's doing the job for you. On the more technical level and limitations - you probably first should get familiar with both technologies so that you could make some conclusions yourself.
I hope that helps!
Cheers!
Hello,
There is problem on my Stimulus that it is now showing output of java script. My project is working under ubuntu apache web server.
Below is my base config and I added {{stimulus_controller ('hello')}} to twig. could you please help to troubleshoot this problem? thank you
Hey Mahmut,
Are you following this course on your private project you did you download the course code and started from the start/ directory? It feels like you're on a private project. Please, download the course code and look at the base.html.twig - that contains some blocks, e.g.:
That are required for Stimulus to work, as they integrate Stimulus code into your project.
Cheers!
thank you.I noticed it and after adding block javascript, it worked.
Hey Mahmut,
Great, thanks for confirming
javascriptblock helped!Cheers!
"Houston: no signs of life"
Start the conversation!