Saltearse al contenido

JavaScript

Uso de JavaScript en componentes Livewire

Livewire y Alpine proporcionan numerosas utilidades para crear componentes dinámicos directamente en tu HTML; sin embargo, hay ocasiones en las que resulta útil salir del HTML y ejecutar JavaScript sin formato para tu componente. Las directivas @script y @assets de Livewire te permiten hacerlo de una forma predecible y fácil de mantener.

Ejecución de scripts

Para ejecutar JavaScript personalizado en su componente Livewire, simplemente envuelva un elemento <script> con @script y @endscript. Esto le indicará a Livewire que se encargue de la ejecución de este JavaScript.

Dado que los scripts dentro de @script son gestionados por Livewire, se ejecutan en el momento perfecto, después de que la página se haya cargado, pero antes de que el componente Livewire se haya renderizado. Esto significa que ya no es necesario envolver los scripts en document.addEventListener('...') para cargarlos correctamente.

Esto también significa que los componentes Livewire cargados de forma diferida o condicional siguen pudiendo ejecutar JavaScript después de que la página se haya inicializado.

<div>
...
</div>
@script
<script>
// Este Javascript se ejecutará cada vez que este componente se cargue en la página...
</script>
@endscript

Aquí hay un ejemplo más completo en el que puedes hacer algo como registrar una acción JavaScript que se utiliza en tu componente Livewire.

<div>
<button wire:click="$js.increment">+</button>
</div>
@script
<script>
$js('increment', () => {
console.log('Incrementar')
})
</script>
@endscript

Uso de $wire desde scripts

Otra característica útil de usar @script para tu JavaScript es que automáticamente tienes acceso al objeto $wire de tu componente Livewire.

Aquí hay un ejemplo del uso de un simple setInterval para actualizar el componente cada 2 segundos (podrías hacerlo fácilmente con wire:poll, pero es una forma sencilla de demostrar el punto):

Puedes obtener más información sobre $wire en la documentación de $wire.

@script
<script>
setInterval(() => {
$wire.$refresh()
}, 2000)
</script>
@endscript

Evaluación de expresiones JavaScript únicas

Además de designar métodos completos para ser evaluados en JavaScript, puede utilizar el método js() para evaluar expresiones individuales más pequeñas en el backend.

Esto suele ser útil para realizar algún tipo de seguimiento del lado del cliente después de que se haya realizado una acción del lado del servidor.

Por ejemplo, aquí hay un ejemplo de un componente CreatePost que activa un cuadro de diálogo de alerta del lado del cliente después de que la publicación se guarda en la base de datos:

<?php
namespace App\Livewire;
use Livewire\Component;
class CreatePost extends Component
{
public $title = '';
public function save()
{
// ...
$this->js("alert('¡Publicación guardada!')");
}
}

La expresión JavaScript alert(“¡Publicación guardada!”) ahora se ejecutará en el cliente después de que la publicación se haya guardado en la base de datos del servidor.

Puedes acceder al objeto $wire del componente actual dentro de la expresión.

Carga de activos

La directiva @script es útil para ejecutar un fragmento de JavaScript cada vez que se carga un componente Livewire; sin embargo, hay ocasiones en las que es posible que desee cargar todos los activos de script y estilo de la página junto con el componente.

A continuación se muestra un ejemplo del uso de @assets para cargar una biblioteca de selección de fechas llamada Pikaday e inicializarla dentro de su componente utilizando @script:

<div>
<input type="text" data-picker>
</div>
@assets
<script src="https://cdn.jsdelivr.net/npm/pikaday/pikaday.js" defer></script>
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/pikaday/css/pikaday.css">
@endassets
@script
<script>
new Pikaday({ field: $wire.$el.querySelector('[data-picker]') });
</script>
@endscript

Cuando se carga este componente, Livewire se asegurará de que todos los @assets se carguen en esa página antes de evaluar los @scripts. Además, se asegurará de que los @assets proporcionados solo se carguen una vez por página, independientemente del número de instancias de este componente que haya, a diferencia de @script, que se evaluará para cada instancia de componente de la página.

Eventos globales de Livewire

Livewire envía dos eventos de navegador útiles para que puedas registrar cualquier punto de extensión personalizado desde scripts externos:

<script>
document.addEventListener('livewire:init', () => {
// Se ejecuta después de que Livewire se haya cargado, pero
// antes de que se inicialice en la página...
})
document.addEventListener('livewire:initialized', () => {
// Se ejecuta inmediatamente después de que Livewire haya
// terminado de inicializarse en la página...
})
</script>

El objeto global Livewire

El objeto global Livewire es el mejor punto de partida para interactuar con Livewire desde scripts externos.

Puedes acceder al objeto JavaScript global Livewire en window desde cualquier lugar dentro de tu código del lado del cliente.

A menudo resulta útil utilizar window.Livewire dentro de un detector de eventos livewire:init

Acceso a componentes

Puedes utilizar los siguientes métodos para acceder a componentes Livewire específicos cargados en la página actual:

// Recupera el objeto $wire para el primer componente de la página...
let component = Livewire.first();
// Recuperar el objeto `$wire` de un componente determinado por su ID...
let component = Livewire.find(id);
// Recuperar una matriz de objetos `$wire` por nombre...
let components = Livewire.getByName(name);
// Recuperar objetos $wire para cada componente de la página...
let components = Livewire.all();

Interactuar con eventos

Además de enviar y escuchar eventos de componentes individuales en PHP, el objeto global Livewire te permite interactuar con el sistema de eventos de Livewire desde cualquier parte de tu aplicación:

// Enviar un evento a cualquier componente Livewire que esté a la escucha...
Livewire.dispatch("post-created", { postId: 2 });
// Enviar un evento a un componente Livewire determinado por su nombre...
Livewire.dispatchTo("dashboard", "post-created", { postId: 2 });
// Escuchar los eventos enviados desde los componentes Livewire...
Livewire.on("post-created", ({ postId }) => {
// ...
});

En determinados casos, es posible que tengas que dar de baja eventos globales de Livewire. Por ejemplo, al trabajar con componentes Alpine y wire:navigate, es posible que se registren varios oyentes al llamar a init al navegar entre páginas. Para solucionar esto, utiliza la función destroy, invocada automáticamente por Alpine. Recorre todos tus oyentes dentro de esta función para darles de baja y evitar cualquier acumulación no deseada.

Alpine.data("MiComponente", () => ({
listeners: [],
init() {
this.listeners.push(
Livewire.on("publicacion-creada", (options) => {
// Haz algo...
})
);
},
destroy() {
this.listeners.forEach((listener) => {
listener();
});
},
}));

Uso de hooks del ciclo de vida

Livewire te permite conectarte a varias partes de su ciclo de vida global utilizando Livewire.hook().

// Registrar una devolución de llamada para ejecutar en un hook interno Livewire determinado...
Livewire.hook("component.init", ({ component, cleanup }) => {
// ...
});

A continuación encontrará más información sobre los hooks JavaScript de Livewire.

Registro de directivas personalizadas

Livewire te permite registrar directivas personalizadas utilizando Livewire.directive().

A continuación se muestra un ejemplo de una directiva personalizada wire:confirm que utiliza el cuadro de diálogo confirm() de JavaScript para confirmar o cancelar una acción antes de enviarla al servidor:

<button wire:confirm="¿Estás seguro?" wire:click="delete">
Eliminar Publicación
</button>

Aquí está la implementación de wire:confirm utilizando Livewire.directive():

Livewire.directive("confirm", ({ el, directive, component, cleanup }) => {
let content = directive.expression;
// El objeto "directive" te da acceso a la directiva analizada.
// Por ejemplo, estos son sus valores para: wire:click.prevent="deletePost(1)"
//
// directive.raw = wire:click.prevent
// directive.value = "click"
// directive.modifiers = [“prevent”]
// directive.expression = "deletePost(1)"
let onClick = (e) => {
if (!confirm(content)) {
e.preventDefault();
e.stopImmediatePropagation();
}
};
el.addEventListener("click", onClick, { capture: true });
// Registra cualquier código de limpieza dentro de `cleanup()` en el caso
// de que un componente Livewire se elimine del DOM mientras
// la página sigue activa.
cleanup(() => {
el.removeEventListener("click", onClick);
});
});

Esquemas de objetos

Al ampliar el sistema JavaScript de Livewire, es importante comprender los diferentes objetos con los que te puedes encontrar.

Aquí tienes una referencia exhaustiva de cada una de las propiedades internas relevantes de Livewire.

Recuerda que el usuario medio de Livewire probablemente nunca interactúe con ellas. La mayoría de estos objetos están disponibles para el sistema interno de Livewire o para usuarios avanzados.

El objeto $wire

Dado el siguiente componente genérico Counter:

<?php
namespace App\Livewire;
use Livewire\Component;
class Counter extends Component
{
public $count = 1;
public function increment()
{
$this->count++;
}
public function render()
{
return view('livewire.counter');
}
}

Livewire expone una representación JavaScript del componente del lado del servidor en forma de un objeto que comúnmente se conoce como $wire:

let $wire = {
// Todas las propiedades públicas de los componentes son directamente accesibles en $wire...
count: 0,
// Todos los métodos públicos están expuestos y se pueden invocar en $wire...
increment() { ... },
// Acceder al objeto `$wire` del componente padre si existe...
$parent,
// Acceder al elemento DOM raíz del componente Livewire...
$el,
// Acceder al ID del componente Livewire actual...
$id,
// Obtener el valor de una propiedad por su nombre...
// Uso: $wire.$get(“count”)
$get(name) { ... },
// Establecer una propiedad en el componente por nombre...
// Uso: $wire.$set(“count”, 5)
$set(name, value, live = true) { ... },
// Cambia el valor de una propiedad booleana...
$toggle(name, live = true) { ... },
// Llama al método...
// Uso: $wire.$call(“increment”)
$call(method, ...params) { ... },
// Define una acción JavaScript...
// Uso: $wire.$js(“increment”, () => { ... })
$js(name, callback) { ... },
// Entrelaza el valor de una propiedad Livewire con una
// propiedad Alpine diferente y arbitraria...
// Uso: <div x-data="{ count: $wire.$entangle(“count”) }">
$entangle(name, live = false) { ... },
// Observa los cambios en el valor de una propiedad...
// Uso: Alpine.$watch(“count”, (value, old) => { ... })
$watch(name, callback) { ... },
// Actualiza un componente enviando una confirmación al servidor
// para volver a renderizar el HTML e intercambiarlo en la página...
$refresh() { ... },
// Idéntico al anterior `$refresh`. Solo que es un nombre más técnico...
$commit() { ... },
// Escucha un evento enviado desde este componente o sus hijos...
// Uso: $wire.$on(“post-created”, () => { ... })
$on(event, callback) { ... },
// Escucha un ghook de vida activado desde este componente o la solicitud...
// Uso: $wire.$hook(“commit”, () => { ... })
$hook(name, callback) { ... },
// Envía un evento desde este componente...
// Uso: $wire.$dispatch(“post-created”, { postId: 2 })
$dispatch(event, params = {}) { ... },
// Enviar un evento a otro componente...
// Uso: $wire.$dispatchTo(“dashboard”, “post-created”, { postId: 2 })
$dispatchTo(otherComponentName, event, params = {}) { ... },
// Envía un evento a este componente y a ningún otro...
$dispatchSelf(event, params = {}) { ... },
// Una API JS para cargar un archivo directamente en el componente
// en lugar de hacerlo a través de `wire:model`...
$upload(
name, // El nombre de la propiedad.
file, // TEl objeto JavaScript del archivo.
finish = () => { ... }, // Se ejecuta cuando finaliza la carga...
error = () => { ... }, // Se ejecuta si se produce un error durante la carga...
progress = (event) => { // Se ejecuta a medida que avanza la carga...
event.detail.progress // Un número entero del 1 al 100...
},
) { ... },
// API para cargar varios archivos al mismo tiempo...
$uploadMultiple(name, files, finish, error, progress) { },
// Eliminar una subida después de que se haya subido temporalmente pero no se haya guardado...
$removeUpload(name, tmpFilename, finish, error) { ... },
// Recuperar el objeto "componente" subyacente...
__instance() { ... },
}

Puedes obtener más información sobre $wire en la documentación de Livewire sobre cómo acceder a propiedades en JavaScript.

El objeto instantánea

Entre cada solicitud de red, Livewire serializa el componente PHP en un objeto que se puede consumir en JavaScript. Esta instantánea se utiliza para deserializar el componente de nuevo en un objeto PHP y, por lo tanto, tiene mecanismos integrados para evitar la manipulación:

let snapshot = {
// El estado serializado del componente (propiedades públicas)...
data: { count: 0 },
// Información detallada sobre el componente...
memo: {
// El ID único del componente...
id: "0qCY3ri9pzSSMIXPGg8F",
// El nombre del componente. Ej.: <livewire:[nombre] />
name: "counter",
// El URI, el método y la configuración regional de la página web en la que
// se cargó originalmente el componente. Se utiliza
// para volver a aplicar cualquier middleware de la solicitud original
// a las solicitudes de actualización de componentes posteriores (confirmaciones)...
path: "/",
method: "GET",
locale: "en",
// Una lista de todos los componentes "hijos" anidados. Clasificados por
// ID de plantilla interna con el ID del componente como valores...
children: [],
// Independientemente de si este componente se cargó de forma diferida...
lazyLoaded: false,
// Una lista de los errores de validación generados durante la
// última solicitud...
errors: [],
},
// Un hash cifrado de forma segura de esta instantánea. De esta manera,
// si un usuario malintencionado manipula la instantánea con el
// objetivo de acceder a recursos que no le pertenecen en el servidor,
// la validación de la suma de comprobación fallará y se generará un error
checksum: "1bc274eea17a434e33d26bcaba4a247a4a7768bd286456a83ea6e9be2d18c1e7",
};

El objeto component

Cada componente de una página tiene un objeto componente correspondiente detrás que realiza un seguimiento de su estado y expone su funcionalidad subyacente. Se trata de una capa más profunda que $wire. Solo está pensada para un uso avanzado.

A continuación se muestra un objeto componente real para el componente Counter anterior con descripciones de las propiedades relevantes en comentarios JS:

let component = {
// El elemento HTML raíz del componente...
el: HTMLElement,
// El identificador único del componente...
id: '0qCY3ri9pzSSMIXPGg8F',
// El "nombre" del componente (<livewire:[name] />)...
name: 'counter',
// El último objeto "efectos". Los efectos son "efectos secundarios" de los viajes de ida y vuelta del servidor.
// Estos incluyen redireccionamientos, descargas de archivos, etc.
effects: {},
// El último estado conocido del componente en el lado del servidor...
canonical: { count: 0 },
// El objeto de datos mutable del componente que representa su
// estado activo del lado del cliente...
ephemeral: { count: 0 },
// Una versión reactiva de `this.ephemeral`. Los cambios en
// este objeto serán recogidos por las expresiones de AlpineJS...
reactive: Proxy,
// Un objeto proxy que se utiliza normalmente dentro de expresiones Alpine
// como `$wire`. Su finalidad es proporcionar una
// interfaz de objeto JS fácil de usar para los componentes Livewire...
$wire: Proxy,
// Una lista de todos los componentes "hijos" anidados. Clasificados por
// ID de plantilla interna con el ID del componente como valores...
children: [],
// La última representación "instantánea" conocida de este componente.
// Las instantáneas se toman del componente del lado del servidor y se utilizan
// para recrear el objeto PHP en el backend...
snapshot: {...},
// La versión sin analizar de la instantánea anterior. Se utiliza para enviarla de vuelta al
// servidor en la siguiente ronda, ya que el análisis de JS interfiere con la codificación PHP,
// lo que a menudo da lugar a discrepancias en la suma de comprobación.
snapshotEncoded: '{"data":{"count":0},"memo":{"id":"0qCY3ri9pzSSMIXPGg8F","name":"counter","path":"\/","method":"GET","children":[],"lazyLoaded":true,"errors":[],"locale":"en"},"checksum":"1bc274eea17a434e33d26bcaba4a247a4a7768bd286456a83ea6e9be2d18c1e7"}',
}

La carga útil del commit

Cuando se realiza una acción en un componente Livewire en el navegador, se activa una solicitud de red. Esa solicitud de red contiene uno o varios componentes y diversas instrucciones para el servidor. Internamente, estas cargas útiles de red de componentes se denominan “commits”.

El término “commit” se eligió como una forma útil de pensar en la relación de Livewire entre el frontend y el backend. Un componente se renderiza y manipula en el frontend hasta que se realiza una acción que requiere que “confirme” su estado y actualizaciones en el backend.

Reconocerás este esquema en la carga útil de la pestaña de red de las herramientas de desarrollo de tu navegador o en los hooks de JavaScript de Livewire:

let commit = {
// Instantánea del objeto
snapshot: { ... },
// Una lista de pares clave-valor de propiedades
// para actualizar en el servidor...
updates: {},
// Una matriz de métodos (con parámetros) para llamar al lado del servidor...
calls: [
{ method: 'increment', params: [] },
],
}

Javascript Hooks

Para usuarios avanzados, Livewire expone su sistema interno de “hooks” del lado del cliente. Puede utilizar los siguientes hooks para ampliar la funcionalidad de Livewire u obtener más información sobre su aplicación Livewire.

Inicialización de componentes

Cada vez que Livewire detecta un nuevo componente, ya sea al cargar la página inicial o más tarde, se activa el evento component.init. Puede conectarse a component.init para interceptar o inicializar cualquier cosa relacionada con el nuevo componente:

Livewire.hook("component.init", ({ component, cleanup }) => {
//
});

Para obtener más información, consulte la documentación sobre el objeto component.

Inicialización de elementos #DOM

Además de activar un evento cuando se inicializan nuevos componentes, Livewire activa un evento para cada elemento DOM dentro de un componente Livewire determinado.

Esto se puede utilizar para proporcionar atributos HTML Livewire personalizados dentro de su aplicación:

Livewire.hook("element.init", ({ component, el }) => {
//
});

DOM Morph hooks

Durante la fase de transformación DOM, que tiene lugar después de que Livewire complete una ronda de red, Livewire activa una serie de eventos para cada elemento que se transforma.

Livewire.hook(
"morph.updating",
({ el, component, toEl, skip, childrenOnly }) => {
//
}
);
Livewire.hook("morph.updated", ({ el, component }) => {
//
});
Livewire.hook("morph.removing", ({ el, component, skip }) => {
//
});
Livewire.hook("morph.removed", ({ el, component }) => {
//
});
Livewire.hook("morph.adding", ({ el, component }) => {
//
});
Livewire.hook("morph.added", ({ el }) => {
//
});

Además de los eventos disparados por elemento, se dispara un evento morph y morphed para cada componente Livewire:

Livewire.hook("morph", ({ el, component }) => {
// Se ejecuta justo antes de que los elementos secundarios de "component" se transformen.
});
Livewire.hook("morphed", ({ el, component }) => {
// Se ejecuta después de que todos los elementos secundarios de "component" se hayan transformado.
});

Hooks de confirmación (commits)

Dado que las solicitudes de Livewire contienen múltiples componentes, el término “solicitud” es demasiado amplio para referirse a la carga útil de la solicitud y la respuesta de un componente individual. En su lugar, internamente, Livewire se refiere a las actualizaciones de los componentes como “confirmaciones”, en referencia a la confirmación del estado de los componentes en el servidor.

Estos hooks exponen objetos de confirmación (commit). Puede obtener más información sobre su esquema leyendo la documentación sobre objetos commit.

Preparación de confirmaciones (commits)

El hook commit.prepare se activará inmediatamente antes de que se envíe una solicitud al servidor. Esto le da la oportunidad de añadir cualquier actualización o acción de última hora a la solicitud saliente:

Livewire.hook("commit.prepare", ({ component }) => {
// Se ejecuta antes de que se recopilen y envíen las cargas útiles de confirmación (commits) al servidor...
});

Interceptar confirmaciones (commits)

Cada vez que se envía un componente Livewire al servidor, se realiza una confirmación. Para conectarse al ciclo de vida y al contenido de una confirmación individual, Livewire expone un hook de commit.

Este hook es extremadamente potente, ya que proporciona métodos para conectarse tanto a la solicitud como a la respuesta de una confirmación de Livewire:

Livewire.hook("commit", ({ component, commit, respond, succeed, fail }) => {
// Se ejecuta inmediatamente antes de que la carga útil de una confirmación se envíe al servidor...
respond(() => {
// Se ejecuta después de recibir una respuesta, pero antes de que se procese...
});
succeed(({ snapshot, effect }) => {
// Se ejecuta después de recibir y procesar una respuesta satisfactoria
// con una nueva instantánea y una lista de efectos...
});
fail(() => {
// Se ejecuta si alguna parte de la solicitud falla...
});
});

Hooks de solicitud

Si prefiere conectarse a toda la solicitud HTTP que va y vuelve del servidor, puede hacerlo utilizando el hook de request:

Livewire.hook(
"request",
({ url, options, payload, respond, succeed, fail }) => {
// Se ejecuta después de compilar las cargas útiles de confirmación, pero antes de enviar una solicitud de red...
respond(({ status, response }) => {
// Se ejecuta cuando se recibe la respuesta...
// "response" es el objeto de respuesta HTTP sin procesar
// antes de que se ejecute await response.text()...
});
succeed(({ status, json }) => {
// Se ejecuta cuando se recibe la respuesta...
// "json" es el objeto de respuesta JSON...
});
fail(({ status, content, preventDefault }) => {
// Se ejecuta cuando la respuesta tiene un código de estado de error...
// "preventDefault" permite desactivar el
// manejo de errores predeterminado de Livewire...
// "content" es el contenido sin procesar de la respuesta...
});
}
);

Personalización del comportamiento de caducidad de la página

Si el cuadro de diálogo predeterminado de caducidad de la página no es adecuado para su aplicación, puede implementar una solución personalizada utilizando el hook de request:

<script>
document.addEventListener('livewire:init', () =>{" "}
{Livewire.hook("request", ({ fail }) => {
fail(({ status, preventDefault }) => {
if (status === 419) {
confirm("El comportamiento de caducidad de tu página personalizada...");
preventDefault();
}
});
})}
)
</script>