Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine
This tutorial has a new version, check it out!

JavaScript, AJAX y el Profiler

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.

Este es nuestro próximo objetivo: escribir algo de JavaScript para que cuando hagamos click en los íconos de arriba o abajo, se realice un request AJAX a nuestra ruta JSON. Este "simula" guardar el voto en la base de datos y retorna el nuevo recuento de votos, el cual usaremos para actualizar el número de votos en la página.

Agregando Clases js- al Template

El template de esta página es: templates/question/show.html.twig. Para cada respuesta, tenemos estos links de votar-arriba y votar-abajo. Voy a agregar algunas clases a esta sección para ayudar a nuestro JavaScript. En el elemento vote-arrows, agrega una clase js-vote-arrows: lo usaremos en el JavaScript para encontrar el elemento. Luego, en el link de vote-up, agrega un atributo data llamado data-direction="up". Haz lo mismo para el link de abajo: data-direction="down". Esto nos ayudará a saber en cuál link se hizo click. Finalmente, rodea el numero de votos - el 6 - con un span que contenga otra clase: js-vote-total. Usaremos esto para encontrar el elemento para poder actualizar ese número.

... lines 1 - 4
{% block body %}
<div class="container">
... lines 7 - 36
<ul class="list-unstyled">
{% for answer in answers %}
<li class="mb-4">
<div class="d-flex justify-content-center">
... lines 41 - 47
<div class="vote-arrows flex-fill pt-2 js-vote-arrows" style="min-width: 90px;">
<a class="vote-up" href="#" data-direction="up"><i class="far fa-arrow-alt-circle-up"></i></a>
<a class="vote-down" href="#" data-direction="down"><i class="far fa-arrow-alt-circle-down"></i></a>
<span>+ <span class="js-vote-total">6</span></span>
</div>
</div>
</li>
{% endfor %}
</ul>
</div>
{% endblock %}

Agregando JavaScript Dentro del Bloque javascripts.

Para simplificar las cosas, el código JavaScript que escribiremos usará jQuery. De hecho, si tu sitio usa jQuery, probablemente querrás incluir jQuery en cada página... Lo cual significa que queremos agregar una etiqueta script a base.html.twig. En la parte de abajo, fíjate que tenemos un bloque llamado javascripts. Dentro de este bloque, voy a pegar una etiqueta <script> para descargar jQuery desde un CDN. Puedes copiar esto desde el bloque de código en esta página, o ir a jQuery para obtenerlo.

Tip

En los nuevos proyectos de Symfony, el bloque javascripts se encuentra en la parte superior de este archivo - dentro de la etiqueta <head>. Puedes dejar el bloque javascripts en <head> o moverlo aquí abajo. Si lo dejas dentro de head, asegúgate de agregar un atributo defer a cada etiqueta script: Esto hará que tu JavaScript sea ejecutado luego de que la página termine de cargar.

... line 1
<html>
... lines 3 - 12
<body>
... lines 14 - 25
{% block javascripts %}
<script
src="https://code.jquery.com/jquery-3.4.1.min.js"
integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo="
crossorigin="anonymous"></script>
{% endblock %}
</body>
</html>

Si te preguntas por qué pusimos esto dentro del bloque javascripts... Más allá de que "parece" un lugar lógico, te mostraré por qué en un minuto. Ya que, técnicamente, si pusiéramos esto luego del bloque javascripts o antes, no habría ninguna diferencia por el momento. Pero ponerlos dentro va a ser útil pronto.

Para nuestro propio JavaScript, dentro del directorio public/, crea un nuevo directorio llamado js/. Y luego, un archivo: question_show.js.

Esta es la idea: usualmente tendrás algún código JavaScript que querrás incluir en cada página. No tenemos ninguno por el momento, pero si lo tuviéramos, yo crearía un archivo app.js y agregaría una etiqueta script para ello en base.html.twig. Luego, en ciertas páginas, podrías necesitar incluir algún JavaScript específico para la página, como por ejemplo, para hacer funcionar el voto de comentarios que solo vive en una página.

Esto es lo que estoy haciendo y esta es la razón por la que creé un archivo llamado question_show.js: Es JavaScript específico para esa página.

Dentro de question_show.js, voy a pegar al rededor de 15 líneas de código.

/**
* Simple (ugly) code to handle the comment vote up/down
*/
var $container = $('.js-vote-arrows');
$container.find('a').on('click', function(e) {
e.preventDefault();
var $link = $(e.currentTarget);
$.ajax({
url: '/comments/10/vote/'+$link.data('direction'),
method: 'POST'
}).then(function(response) {
$container.find('.js-vote-total').text(response.votes);
});
});

Esto encuentra el elemento .js-vote-arrows - el cual agregamos aquí - encuentra cualquier etiqueta dentro del mismo, y registra una función para el evento click allí. Al hacer click, hacemos una llamada AJAX a /comments/10 - el 10 es escrito a mano por ahora - /vote/ y luego leemos el atributo data-direction del elemento <a> para saber si este es un voto arriba o abajo. Al finalizar exitosamente, jQuery nos pasa los datos JSON de nuestra llamada. Renombremos esa variable a data para ser más exactos.

... lines 1 - 4
$container.find('a').on('click', function(e) {
... lines 6 - 8
$.ajax({
... lines 10 - 11
}).then(function(data) {
$container.find('.js-vote-total').text(data.votes);
});
});

Luego usamos el campo votes de los datos - porque en nuestro controlador, estamos retornando una variable votes - para actualizar el total de votos.

Sobreescribiendo el Bloque javascripts.

Entonces... ¿Cómo incluimos este archivo? Si quisiéramos incluir esto en cada página, sería bastante fácil: agrega otra etiqueta script abajo de jQuery en base.html.twig. Pero queremos incluir esto solo en la página show. Aquí es donde tener el script de jQuery dentro del bloque javascripts es útil. Porque, en un template "hijo", podemos sobreescribir ese bloque.

Echemos un vistazo: en show.html.twig, no importa donde - pero vayamos al final, di {% block javascripts %} {% endblock %}. Dentro, agrega una etiqueta <script> con src="". Ah, tenemos que recordar usar la función asset(). Pero... PhpStorm nos sugiere js/question_show.js. Selecciona ese. ¡Muy bien! Agregó la función asset() por nosotros.

... lines 1 - 59
{% block javascripts %}
... lines 61 - 62
<script src="{{ asset('js/question_show.js') }}"></script>
{% endblock %}

Si paráramos ahora, esto literalmente sobreescribiría el bloque javascripts de base.html.twig. Por lo que jQuery no sería incluido en la página. ¡En vez de sobreescribir el bloque, lo que realmente queremos es agregar algo a él! En el HTML final, queremos que nuestra nueva etiqueta script vaya justo debajo de jQuery.

¿Cómo podemos hacer esto? Sobre nuestra etiqueta script, di {{ parent() }}.

... lines 1 - 59
{% block javascripts %}
{{ parent() }}
<script src="{{ asset('js/question_show.js') }}"></script>
{% endblock %}

¡Me encanta! La función parent() toma el contenido del bloque padre, y lo imprime.

¡Probémoslo! Refresca y... Haz click en up. ¡Se actualiza! Y si hacemos click en down, vemos un número muy bajo.

Requests AJAX en el Profiler

Ah, y ¿Ves este número "6" aquí abajo en la barra de herramientas debug? Esto es genial. Refresca la página. Fíjate que el ícono no está aquí abajo. ¡Pero, tan pronto como nuestra página hace una llamada AJAX, aparece! Sip, la barra de herramientas debug detecta llamadas AJAX y las enlista aquí. ¡La mejor parte es que puedes usar esto para saltar al profiler para cualquiera de estos requests! Voy a hacer click con el botón derecho y abriré este link de voto "abajo" en una nueva pestaña.

Este es el profiler completo para la llamada en todo su esplendor. Si usas dump() en alguna parte de tu código, la variable volcada para esa llamada AJAX estará aquí. Y luego, tendremos una sección de base de datos aquí. Esta es una funcionalidad maravillosa.

A continuación, ajustemos nuestra ruta de la API: No deberíamos poder hacer un request GET al mismo - como si lo abriéramos en nuestro navegador. Y... ¿Tenemos algo que valide que el comodín {direction}... de la URL sea up o down pero nada más? Todavía no.

Leave a comment!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^7.3.0 || ^8.0.0",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "easycorp/easy-log-handler": "^1.0.7", // v1.0.9
        "sensio/framework-extra-bundle": "^6.0", // v6.2.1
        "symfony/asset": "5.0.*", // v5.0.11
        "symfony/console": "5.0.*", // v5.0.11
        "symfony/debug-bundle": "5.0.*", // v5.0.11
        "symfony/dotenv": "5.0.*", // v5.0.11
        "symfony/flex": "^1.3.1", // v1.17.5
        "symfony/framework-bundle": "5.0.*", // v5.0.11
        "symfony/monolog-bundle": "^3.0", // v3.5.0
        "symfony/profiler-pack": "*", // v1.0.5
        "symfony/routing": "5.1.*", // v5.1.11
        "symfony/twig-pack": "^1.0", // v1.0.1
        "symfony/var-dumper": "5.0.*", // v5.0.11
        "symfony/webpack-encore-bundle": "^1.7", // v1.8.0
        "symfony/yaml": "5.0.*" // v5.0.11
    },
    "require-dev": {
        "symfony/profiler-pack": "^1.0" // v1.0.5
    }
}