Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Ejemplo de Stimulus en el mundo real

Video not working?

It looks like your browser may not support the H264 codec. If you're using Linux, try a different browser or try installing the gstreamer0.10-ffmpeg gstreamer0.10-plugins-good packages.

Thanks! This saves us from needing to use Flash or encode videos in multiple formats. And that let's us get back to making more videos :). But as always, please feel free to message us.

Pongamos a prueba a Stimulus. Éste es nuestro objetivo: cuando hagamos clic en el icono de reproducción, haremos una petición Ajax a nuestra ruta de la API... la que está en SongController. Esto devuelve la URL donde se puede reproducir esta canción. Entonces usaremos eso en JavaScript para... ¡reproducir la canción!

Toma hello_controller.js y cámbiale el nombre a, qué tal song-controls_controller.js. Dentro, sólo para ver si esto funciona, en connect(), registra un mensaje. El métodoconnect() se llama cada vez que Stimulus ve un nuevo elemento coincidente en la página.

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() {
console.log('I just appeared into existence!');
}
}

Ahora, en la plantilla, hola ya no va a funcionar, así que quita eso. Lo que quiero hacer es rodear cada fila de canciones con este controlador.... así que es este elementosong-list. Después de la clase, añade {{ stimulus_controller('song-controls') }}.

{% extends 'base.html.twig' %}
{% block title %}Create a new Record | {{ parent() }}{% endblock %}
{% block body %}
<div class="container">
... lines 7 - 36
{% for track in tracks %}
<div class="song-list" {{ stimulus_controller('song-controls') }}>
... lines 39 - 50
</div>
{% endfor %}
... lines 53 - 55
</div>
{% endblock %}

Vamos a probarlo Actualiza, comprueba la consola y... ¡sí! Golpeó nuestro código seis veces! Una vez por cada uno de estos elementos. Y cada elemento recibe su propia instancia de controlador, por separado.

Añadir acciones de Stimulus

Bien, a continuación, cuando hagamos clic en reproducir, queremos ejecutar algún código. Para ello, podemos añadir una acción. Tiene este aspecto: en la etiqueta a, añade {{ stimulus_action() }} -otra función de acceso directo- y pásale el nombre del controlador al que estás adjuntando la acción - song-controls - y luego un método dentro de ese controlador que debe ser llamado cuando alguien haga clic en este elemento. ¿Qué te parece play.

{% extends 'base.html.twig' %}
{% block title %}Create a new Record | {{ parent() }}{% endblock %}
{% block body %}
<div class="container">
... lines 7 - 36
{% for track in tracks %}
<div class="song-list" {{ stimulus_controller('song-controls') }}>
<div class="d-flex mb-3">
<a href="#" {{ stimulus_action('song-controls', 'play') }}>
<i class="fas fa-play me-3"></i>
</a>
... lines 43 - 49
</div>
</div>
{% endfor %}
... lines 53 - 55
</div>
{% endblock %}

Genial, ¿no? De vuelta en el controlador de la canción, ya no necesitamos el método connect(): no tenemos que hacer nada cada vez que veamos otra fila song-list. Pero sí necesitamos un método play().

Y al igual que con los escuchadores de eventos normales, éste recibirá un objeto event... y entonces podremos decir event.preventDefault() para que nuestro navegador no intente seguir el clic del enlace. Para probar, console.log('Playing!').

import { Controller } from '@hotwired/stimulus';
... lines 2 - 11
export default class extends Controller {
play(event) {
event.preventDefault();
console.log('Playing!');
}
}

¡Vamos a ver qué pasa! Actualiza y... haz clic. Ya funciona. Así de fácil es enganchar un oyente de eventos en Stimulus. Ah, y si inspeccionas este elemento... esa funciónstimulus_action() es sólo un atajo para añadir un atributo especial data-actionque Stimulus entiende.

Instalar e importar Axios

Bien, ¿cómo podemos hacer una llamada Ajax desde dentro del método play()? Bueno, podríamos utilizar la función integrada fetch() de JavaScript. Pero en su lugar, voy a instalar una biblioteca de terceros llamada Axios. En tu terminal, instálala diciendo:

yarn add axios --dev

Ahora sabemos lo que hace: descarga este paquete en nuestro directorio node_modules, y añade esta línea a nuestro archivo package.json.

Ah, y nota al margen: puedes utilizar absolutamente jQuery dentro de Stimulus. No lo haré, pero funciona muy bien - y puedes instalar - e importar - jQuery como cualquier otro paquete. Hablamos de ello en nuestro tutorial de Stimulus.

Bien, ¿cómo utilizamos la biblioteca axios? Importándola

Al principio de este archivo, ya hemos importado la clase base Controller destimulus. Ahora import axios from 'axios'. En cuanto lo hagamos, Webpack Encore cogerá el código fuente de axios y lo incluirá en nuestros archivos JavaScript construidos.

... lines 1 - 11
import axios from 'axios';
... lines 13 - 21

Ahora, aquí abajo, podemos decir axios.get() para hacer una petición GET. Pero... ¿qué debemos pasar para la URL? Tiene que ser algo como /api/songs/5... pero ¿cómo sabemos cuál es el "id" de esta fila?

Valores de Stimulus

Una de las cosas más interesantes de Stimulus es que te permite pasar valores de Twig a tu controlador Stimulus. Para ello, declara qué valores quieres permitir que se pasen a través de una propiedad estática especial: static values = {}. Dentro, vamos a permitir que se pase un valor de infoUrl. Me acabo de inventar ese nombre: creo que pasaremos la URL completa a la ruta de la API. Establece esto como el tipo que será. Es decir, un String.

Aprenderemos cómo pasamos este valor desde Twig a nuestro controlador en un minuto. Pero como tenemos esto, abajo, podemos referenciar el valor diciendo this.infoUrlValue.

... lines 1 - 11
import axios from 'axios';
... line 13
export default class extends Controller {
static values = {
infoUrl: String
}
... line 18
play(event) {
... lines 20 - 21
console.log(this.infoUrlValue);
//axios.get()
}
}

Entonces, ¿cómo lo pasamos? De vuelta en homepage.html.twig, añade un segundo argumento a stimulus_controller(). Este es un array de los valores que quieres pasar al controlador. Pasa a infoUrl el conjunto de la URL.

Hmm, pero tenemos que generar esa URL. ¿Esa ruta tiene ya un nombre? No, añade name: 'api_songs_get_one'.

<?php
... lines 3 - 10
class SongController extends AbstractController
{
#[Route('/api/songs/{id<\d+>}', methods: ['GET'], name: 'api_songs_get_one')]
public function getSong(int $id, LoggerInterface $logger): Response
{
... lines 16 - 27
}
}

Perfecto. Copia eso... y de nuevo en la plantilla, establece infoURl a path(), el nombre de la ruta... y luego una matriz con cualquier comodín. Nuestra ruta tiene un comodínid.

En una aplicación real, estas rutas probablemente tendrían cada una un id de base de datos que podríamos pasar. Todavía no lo tenemos... así que para, en cierto modo, falsear esto, voy a utilizarloop.index. Esta es una variable mágica de Twig: si estás dentro de un bucle de Twig for, puedes acceder al índice -como 1, 2, 3, 4- utilizando loop.index. Así que vamos a usar esto como una identificación falsa. Ah, y no olvides decir id: y luegoloop.index.

... lines 1 - 4
{% block body %}
<div class="container">
... lines 7 - 36
{% for track in tracks %}
<div class="song-list" {{ stimulus_controller('song-controls', {
infoUrl: path('api_songs_get_one', { id: loop.index })
}) }}>
... lines 41 - 52
</div>
{% endfor %}
... lines 55 - 57
</div>
{% endblock %}

¡Hora de probar! Refresca. Lo primero que quiero que veas es que, cuando pasamosinfoUrl como segundo argumento a stimulus_controller, lo único que hace es dar salida a un atributo muy especial data que Stimulus sabe leer. Así es como se pasa un valor a un controlador.

Haz clic en uno de los enlaces de reproducción y... lo tienes. ¡A cada objeto controlador se le pasa su URL correcta!

Hacer la llamada Ajax

¡Vamos a celebrarlo haciendo la llamada Ajax! Hazlo con axios.get(this.infoUrlValue) -sí, acabo de escribirlo-, .then() y una devolución de llamada utilizando una función de flecha que recibirá un argumento response. Esto se llamará cuando termine la llamada Ajax. Registra la respuesta para empezar. Ah, y corrige para usar this.infoUrlValue.

import { Controller } from '@hotwired/stimulus';
... lines 3 - 11
import axios from 'axios';
... line 13
export default class extends Controller {
... lines 15 - 18
play(event) {
event.preventDefault();
axios.get(this.infoUrlValue)
.then((response) => {
console.log(response);
});
}
}

Muy bien, actualiza... ¡y haz clic en el enlace de reproducción! ¡Sí! Ha volcado la respuesta... y una de sus claves es data... ¡que contiene el url!

¡Es hora de dar la vuelta de la victoria! De vuelta a la función, podemos reproducir ese audio creando un nuevo objeto Audio -es un objeto JavaScript normal-, pasándoleresponse.data.url... y llamando a continuación a play().

import { Controller } from '@hotwired/stimulus';
... lines 2 - 11
import axios from 'axios';
export default class extends Controller {
... lines 15 - 18
play(event) {
event.preventDefault();
axios.get(this.infoUrlValue)
.then((response) => {
const audio = new Audio(response.data.url);
audio.play();
});
}
}

Y ahora... cuando le demos al play... ¡por fin! Música para mis oídos.

Si quieres aprender más sobre Stimulus - esto ha sido un poco rápido - tenemos un tutorial entero sobre ello... y es genial.

Para terminar este tutorial, vamos a instalar otra biblioteca de JavaScript. Ésta hará que nuestra aplicación se sienta instantáneamente como una aplicación de una sola página. Eso a continuación.

Leave a comment!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": ">=8.0.2",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "symfony/asset": "6.0.*", // v6.0.3
        "symfony/console": "6.0.*", // v6.0.3
        "symfony/dotenv": "6.0.*", // v6.0.3
        "symfony/flex": "^2", // v2.1.5
        "symfony/framework-bundle": "6.0.*", // v6.0.4
        "symfony/monolog-bundle": "^3.0", // v3.7.1
        "symfony/runtime": "6.0.*", // v6.0.3
        "symfony/twig-bundle": "6.0.*", // v6.0.3
        "symfony/ux-turbo": "^2.0", // v2.0.1
        "symfony/webpack-encore-bundle": "^1.13", // v1.13.2
        "symfony/yaml": "6.0.*", // v6.0.3
        "twig/extra-bundle": "^2.12|^3.0", // v3.3.8
        "twig/twig": "^2.12|^3.0" // v3.3.8
    },
    "require-dev": {
        "symfony/debug-bundle": "6.0.*", // v6.0.3
        "symfony/stopwatch": "6.0.*", // v6.0.3
        "symfony/web-profiler-bundle": "6.0.*" // v6.0.3
    }
}