IronWoods.es

Desarrollo web

Gestión de eventos

La interacción con una página o formulario implica, habitualmente, gestionar eventos como, por ejemplo, la pulsación de un botón.

LLamada de "funciones en línea"

Al clicar un botón puede llamarse una función previamente definida:


<button type="button"
    class="btn btn-danger"
    onclick="deleteRow(1)">
    Eliminar
</button>

En este caso el script con la función que se llama tiene que estar disponible en el momento de la activación del botón, ya sea en el documento, añadiendolo previamente, o en un fichero externo ya cargado. Ésto, que de por sí es un problema ya que impacta el performance de la página, es una práctica no recomendada ya que introducimos parte del código JavaScript en el documento HTML.

Uso de "escuchadores de eventos"

La gestión de eventos debería hacerse mediante el uso de escuchadores de eventos (event listeners).

Por ejemplo, si tenemos el botón:


<button type="button"
    class="btn btn-danger"
    data-action="delete-row"
    data-id="1">
    Eliminar
</button>

En estos casos uso un atributo data-action con referencia a la acción y para controlarlo el siguiente código JavaScript:


<script>
    const button = document.querySelector('button[data-action="delete-row"]');
    button.addEventListener('click', function (ev)
    {
        const rowId = ev.target.getAttribute('data-id');
        if (confirm('¿Eliminar registro con ID: ' + rowId)) {
            // Do something...
        }
    });
</script>

Si sólo se usa un botón y solo se incluye el código JS una vez, funciona.

Si hay mas de un botón, por ejemplo, uno por cada registro mostrado:


<div>
    <p>Row 1</p>
    <button type="button"
        class="btn btn-danger"
        data-action="delete-row"
        data-id="1">
        Eliminar
    </button>
</div>
<div>
    <p>Row 2</p>
    <button type="button"
        class="btn btn-danger"
        data-action="delete-row"
        data-id="2">
        Eliminar
    </button>
</div>
<div>
    <p>Row n</p>
    <button type="button"
        class="btn btn-danger"
        data-action="delete-row"
        data-id="n">
        Eliminar
    </button>
</div>

Se añaden los escuchadores en un bucle:


<script>
    const buttons = document.querySelectorAll('button[data-action="delete-row"]');
    buttons.forEach(button => {
        button.addEventListener('click', function (ev) {
            const rowId = ev.target.getAttribute('data-id');
            if (confirm('¿Eliminar registro con ID: ' + rowId)) {
                // Do something...
            }
        });
    });
</script>

Esta es una gestión básica del evento clic, donde se ejecuta el código dentro de una "función anónima".

Si añadimos este código dentro de una "función declarada" podemos separar la lógica del escuchador del evento:


<script>
    const buttons = document.querySelectorAll('button[data-action="delete-row"]');
    buttons.forEach(button => {
        button.addEventListener('click', clickDeleteButton);
    });

    function clickDeleteButton(ev) // void
    {
        const rowId = ev.target.getAttribute('data-id');
        if (confirm('¿Eliminar registro con ID: ' + rowId)) {
            // Do something...
        }
    }
</script>

Usar una función declarada permite además dejar de escuchar el evento.

Dejar de escuchar el evento, antes de añadir la escucha del mismo, evita que se añada el mismo escuchador repetidas veces al mismo elemento, si esto sucede el evento ejecutaría el script asociado multiples veces.

Para el código anterior:


button.removeEventListener('click', clickDeleteButton);
button.addEventListener('click', clickDeleteButton);

Ahora, si la gestión de eventos se encuentra dentro de un función que se llamará más de una vez, al activar uno de los botones de eliminar, sólo tiene asociada la escucha de la última llamada.

La gestión del evento puede añadirse en una función de utilidad que se usará cada vez que usamos escuchadores de evento, por ejemplo:


function handleListener(node, callbackFunc, event = 'click') // void
{
    node.removeEventListener(event, callbackFunc);
    node.addEventListener(event, callbackFunc);
}

Una vez que tenemos definida la función anterior, el código para gestionar nuestros botones quedaría:


<script>
    const buttons = document.querySelectorAll('button[data-action="delete-row"]');
    buttons.forEach(button => {
        handleListener(button, clickDeleteButton);
    });

    function clickDeleteButton(ev) // void
    {
        const rowId = ev.target.getAttribute('data-id');
        if (confirm('¿Eliminar registro con ID: ' + rowId)) {
            // Do something...
        }
    }
</script>

Pasando datos a la función llamada mediante el evento

Puede observarse que la función declarada que llama el escuchador, recibe como parámetro el propio evento:


function clickDeleteButton(ev) // void

Al tener el evento un "target" este es un buen punto donde pasar a la función los parámetros necesarios, como data atributos del elemento escuchado.

En el botón se añadio un atributo data-id lo que obtiene tras pulsar el botón con el código:


function clickDeleteButton(ev) // void
    {
        const rowId = ev.target.getAttribute('data-id');

Event listener, sintaxis alternativa

Podemos gestionar la escucha de eventos mediante métodos onevent, por ejemplo, para el clic:


<script>
    const buttons = document.querySelectorAll('button[data-action="delete-row"]');
    buttons.forEach(button => {
        button.onclick = function (ev) {
            const rowId = ev.target.getAttribute('data-id');
            if (confirm('¿Eliminar registro con ID: ' + rowId)) {
                // Do something...
            }
        }
    });
</script>

En este caso no usamos addEventListener lo que hace innecesario, e inútil, usar removeEventListener. Si hubiera que eliminar la ejecución asociada al clic, por ejemplo, para un botón que debe activarse una sola vez, igualamos el manejador del evento a null:


buttons.forEach(button => {
    button.onclick = function (ev) {
        const rowId = ev.target.getAttribute('data-id');
        if (confirm('¿Eliminar registro con ID: ' + rowId)) {
            // Do something...
        }

        // Cancel the event handler after the first click:
        ev.target.onclick = onclick;
    }
});

05-04-2024