En este videotutorial veremos cómo eliminar registros utilizando Javascript y AJAX con Laravel y Blade. Para no depender tanto de las nuevas tecnologías y darte un conocimiento más amplio y aplicable vamos a usar JS sin framework. Los conocimientos de esta lección, sin embargo, te serán útiles sin importar si en tus proyectos usas Vue.js, React, Alpine, Stimulus y otros frameworks de JS. Puesto que existen numerosos frameworks de JS es fundamental poseer una base sólida en el lenguaje como tal.
Esta lección incluye un video premium
Regístrate para ver este video y cientos de lecciones exclusivas.
Mira el código en GitHub: actual, resultado, comparación.
Identificando elementos para eliminar
Lo primero es identificar los elementos con los que vamos a trabajar para que nuestro código Javascript pueda interactuar con ellos. Vamos a agregar un atributo data específico a cada enlace de eliminación:
<a href="#" data-js-delete-note="ID_DE_LA_NOTA">Eliminar</a>
Inserción de código JavaScript
Para añadir nuestro código JavaScript, vamos a crear un nuevo «slot» dentro de nuestra plantilla, que denominaremos simplemente «JavaScript». Aquí, podríamos incluir un simple alert("Funciona!")
para comprobar que nuestro script está activo y funcionando correctamente:
<!-- resources/views/notes/index.blade.php --> <x-slot:javascript> <script> alert("Funciona!"); </script> </x-slot:javascript>
Y en nuestro Layout vamos a imprimir el slot, lo cual lo podemos lograr simplemente usando la variable $javascript
:
<!-- resources/views/layouts/app.blade.php --> {{ $javascript ?? '' }}
Para evitar errores si el slot no existe podemos usar el operador ??
(«Operador de coalescencia nula»). Este operador es usado para devolver su primer operando si existe y no es null
; de lo contrario, devuelve su segundo operando. Es muy útil para establecer valores predeterminados para variables que podrían no estar definidas o que podrían ser null
.
Asignando el evento «on click» a los enlaces de eliminación
Tras confirmar que nuestro alert se muestra en el navegador, procederemos a seleccionar todos los enlaces correspondientes a la acción de eliminar. Usaremos el método querySelectorAll
, buscando el atributo que definimos momentos antes:
const deleteLinks = document.querySelectorAll('[data-js-delete-note]');
Podemos enlazar un evento «on click» a cada uno de estos elementos de esta forma:
document.querySelectorAll('a[data-js-delete-note]').forEach(function (link) { link.addEventListener('click', function (event) { deleteNote(event.target.closest('a')); }); });
La función callback que pasamos como primer argumento a forEach
recibe cada uno de los elementos, mientras que la función callback que pasamos a addEventListener
recibe un objeto con información del evento.
Podemos obtener el elemento que el usuario presionó con event.target
y puesto que este elemento es un icono y no el enlace como tal, podemos obtener el enlace con event.target.closest('a')
.
Obteniendo el ID de la nota que queremos eliminar
Podemos obtener el valor de cualquier atributo dataset
partiendo del elemento en cuestión de esta forma:
<!-- El atributo data es 'data-js-delete-note'. Nota cómo pasamos de 'kebab case' a 'camel case'. deleteNoteLink.dataset.jsDeleteNote;
Generando la URL para eliminar la nota con JavaScript y Blade
Puesto que el código de JS lo escribimos en una plantilla de Blade, podemos utilizar las interpolaciones de Blade para obtener nuestras rutas:
let deleteUrlPlaceholder = '{{ route('notes.destroy', ':id') }}';
Nota que he usado un «placeholder» (:id
) puesto que aún no sabemos el ID de la nota.
Cuando conozcamos el ID de la nota, podemos usar esta variable para obtener la URL completa reemplazando el placeholder por el ID:
let deleteNoteUrl = deleteUrlPlaceholder.replace(':id', noteId);
Enviando la petición AJAX
Utilizamos la función global fetch()
nativa de JavaScript, que es ampliamente soportada por los navegadores modernos. Aquí especificaremos la URL, el método HTTP (en este caso DELETE
), y añadiremos los headers necesarios para indicar al servidor que estamos enviando una solicitud AJAX con contenido JSON.
fetch(url, { method: 'DELETE', // or GET, POST, PUT or PATCH headers: { "Content-Type": "application/json", "X-Requested-With": "XMLHttpRequest" }, body: JSON.stringify({ _token: csrfToken }) }).then(function (response) { // Logic here }).catch(function (error) { // Error logic here });
Recuerda que podemos obtener el token para protecciones contra CSRF utilizando la función {{ csrf_token() }}
.
Para una lista de frameworks y librerías de JS para peticiones AJAX puedes ver este enlace en Wikipedia.
Ocultando y mostrando elementos con JavaScript
Hay varias formas en que podemos mostrar y ocultar elementos con JS. Una manera sencilla es la siguiente:
// Asumiendo que noteCard es una variable que contiene una referencia a un elemento // Ocultamos noteCard.style.display = 'none'; // Mostramos de nuevo noteCard.style.display = 'block'; // o flex // Eliminamos por completo del DOM: noteCard.remove();
Peticiones de AJAX con enfoque optimista VS pesimista
Los enfoques «optimista» y «pesimista» se refieren a dos estrategias diferentes para manejar la actualización de la interfaz de usuario:
Enfoque Optimista
Con este enfoque, cuando se realiza una solicitud AJAX, la interfaz de usuario se actualiza inmediatamente presuponiendo una si la operación hubiera sido exitosa, sin esperar la respuesta del servidor. Esto brinda al usuario la impresión de una respuesta más rápida.
Si la operación falla (por ejemplo, debido a un error de servidor o de red), la aplicación deberá advertirle al usuario del error y revertir la actualización en la interfaz de usuario (por ejemplo, en nuestro caso volver a mostrar la nota que no fue eliminada con éxito). Este enfoque es «optimista» porque asume que la mayoría de las solicitudes se completarán con éxito.
Ejemplo de enfoque optimista:
function mainAction(myElement) { myElement.style.display = 'none'; fetch(theUrl, { method: 'POST', // o PUT, PATCH o DELETE headers: { "Content-Type": "application/json", "X-Requested-With": "XMLHttpRequest" }, body: JSON.stringify({ _token: csrfToken }) }).then(function (response) { if (response.status < 200 || response.status >= 300) { myElement.style.display = 'block'; alert('Ocurrió un error'); return; } myElement.remove(); }).catch(function (error) { myElement.style.display = 'block'; alert('Ocurrió un error: ' + error); }); }
Como puedes ver ocultamos el elemento tan pronto como se ejecuta la acción principal y lo mostramos nuevamente si la acción falla.
Enfoque Pesimista
Por otro lado, el enfoque pesimista no actualiza la interfaz de usuario hasta que la operación haya sido confirmado con éxito por parte del servidor. Cuando se envía una solicitud AJAX, la interfaz de usuario debería mostrar un indicador de carga, deshabilitar el botón en cuestión (si esto aplica) y esperar la respuesta del servidor antes de realizar cualquier cambio a la interfaz de usuario.
Este enfoque es más conservador y evita la necesidad de revertir cambios en caso de fallo, pero puede parecer más lento para el usuario ya que la actualización de la interfaz de usuario depende del tiempo de respuesta del servidor.
Ejemplo de enfoque pesimista:
function mainAction(myElement, myButton, loadingElement) { myButton.disable(); loadingElement.style.display = 'block'; fetch(theUrl, { method: 'DELETE', headers: { "Content-Type": "application/json", "X-Requested-With": "XMLHttpRequest" }, body: JSON.stringify({ _token: csrfToken }) }).then(function (response) { if (response.status < 200 || response.status >= 300) { alert('Ocurrió un error'); return; } myElement.remove(); }).catch(function (error) { alert('Ocurrió un error: ' + error); }).finally(function () { myButton.enable(); loadingElement.style.display = 'none'; }); }
En este ejemplo, fíjate cómo no ocultamos el elemento principal (myElement
) al principio y solo lo eliminamos una vez que el servidor responda con un código exitoso (2xx). Sin embargo, esta segunda opción requiere de un elemento de carga que debemos mostrar tan pronto comience la acción, y opcionalmente deberíamos desactivar el botón o enlace en cuestión para evitar que el usuario lo presione varias veces.
¿Debo ser optimista o pesimista en el manejo de las peticiones de AJAX?
En esta lección he usado el enfoque optimista porque me pareció el más adecuado, por ejemplo hace que la interfaz responda inmediatamente sin necesidad de usar un cargador, y no espero que esta operación falle. Sin embargo, en operaciones más cruciales, como por ejemplo la transferencia de dinero, habría usado un enfoque pesimista y confirmado la acción una vez que el servidor devuelva una respuesta exitosa.
Extrayendo nuestro código a funciones más pequeñas
Por último, quiero resaltar la importancia de mantener limpio y ordenado nuestro código. Al extraer parte de la lógica a una función separada, como la función deleteNote
, nuestro código no solo se hace más legible, sino también más fácil de mantener y de modificar en el futuro.
Como ejercicio final, práctica extrayendo la lógica para armar la URL para eliminar la nota, en una función adicional llamada getDeleteNoteUrl
que acepte como único argumento la referencia al elemento con el enlace deleteNoteLink
.
Regístrate hoy en Styde y obtén acceso a todo nuestro contenido.
Lección anterior Eliminación de registros en Laravel 10 o superior con eliminado lógico Lección siguiente Creación de componentes Blade con Laravel 10 en adelante