JavaScript & Events
Gale provides two powerful methods for JavaScript integration: dispatch() for
firing custom DOM events, and js() for executing arbitrary JavaScript. Both
enable seamless communication between your Laravel backend and frontend.
Dispatching Events
Use dispatch() to fire custom DOM events that your Alpine.js components
can listen to. Events are dispatched on the window by default.
// Window-level event with data
gale()->dispatch('user-updated', [
'id' => $user->id,
'name' => $user->name,
]);
// Simple event without data
gale()->dispatch('cart-updated');
// Event with notification data
gale()->dispatch('show-notification', [
'type' => 'success',
'message' => 'Changes saved successfully!',
]);
// Window-level event with data
gale()->dispatch('user-updated', [
'id' => $user->id,
'name' => $user->name,
]);
// Simple event without data
gale()->dispatch('cart-updated');
// Event with notification data
gale()->dispatch('show-notification', [
'type' => 'success',
'message' => 'Changes saved successfully!',
]);
Listening to Events
Listen for dispatched events using Alpine.js event listeners with the .window
modifier. Event data is available via $event.detail.
<!-- Listen for user-updated event -->
<div
x-data="{ user: null }"
@user-updated.window="user = $event.detail">
<template x-if="user">
<p>Updated: <span x-text="user.name"></span></p>
</template>
</div>
<!-- Listen for notification events -->
<div
x-data="{ notifications: [] }"
@show-notification.window="
notifications.push($event.detail);
setTimeout(() => notifications.shift(), 3000);
">
<template x-for="notif in notifications">
<div :class="'alert alert-' + notif.type" x-text="notif.message"></div>
</template>
</div>
<!-- Listen for user-updated event -->
<div
x-data="{ user: null }"
@user-updated.window="user = $event.detail">
<template x-if="user">
<p>Updated: <span x-text="user.name"></span></p>
</template>
</div>
<!-- Listen for notification events -->
<div
x-data="{ notifications: [] }"
@show-notification.window="
notifications.push($event.detail);
setTimeout(() => notifications.shift(), 3000);
">
<template x-for="notif in notifications">
<div :class="'alert alert-' + notif.type" x-text="notif.message"></div>
</template>
</div>
Targeted Events
Dispatch events to specific elements using the selector option:
// Target by class
gale()->dispatch('refresh', ['section' => 'cart'], [
'selector' => '.shopping-cart',
]);
// Target by ID
gale()->dispatch('update', ['data' => $data], [
'selector' => '#user-profile',
]);
// Target multiple elements
gale()->dispatch('price-changed', ['price' => $newPrice], [
'selector' => '[data-product]',
]);
// Target by class
gale()->dispatch('refresh', ['section' => 'cart'], [
'selector' => '.shopping-cart',
]);
// Target by ID
gale()->dispatch('update', ['data' => $data], [
'selector' => '#user-profile',
]);
// Target multiple elements
gale()->dispatch('price-changed', ['price' => $newPrice], [
'selector' => '[data-product]',
]);
Listen without the .window modifier for targeted events:
<!-- Element receives targeted event directly -->
<div
class="shopping-cart"
x-data="{ items: [] }"
@refresh="$get('/cart')">
<!-- Cart content -->
</div>
<!-- Element receives targeted event directly -->
<div
class="shopping-cart"
x-data="{ items: [] }"
@refresh="$get('/cart')">
<!-- Cart content -->
</div>
Event Options
Customize event behavior with standard DOM event options:
gale()->dispatch('notification', ['message' => 'Saved!'], [
'bubbles' => true, // Event bubbles up the DOM (default: true)
'cancelable' => true, // Event can be canceled (default: true)
'composed' => false, // Event crosses shadow DOM (default: false)
]);
gale()->dispatch('notification', ['message' => 'Saved!'], [
'bubbles' => true, // Event bubbles up the DOM (default: true)
'cancelable' => true, // Event can be canceled (default: true)
'composed' => false, // Event crosses shadow DOM (default: false)
]);
| Option | Type | Default | Description |
|---|---|---|---|
selector |
string | null | Target element(s) selector |
bubbles |
bool | true | Event bubbles up DOM tree |
cancelable |
bool | true | Event can be canceled |
composed |
bool | false | Event crosses shadow DOM boundaries |
Executing JavaScript
Use js() to execute arbitrary JavaScript in the browser:
// Simple console log
gale()->js('console.log("Hello from server")');
// Call application functions
gale()->js('myApp.showNotification("Saved!")');
// Scroll to element
gale()->js('document.getElementById("results").scrollIntoView()');
// Focus input
gale()->js('document.querySelector("#email").focus()');
// Multiple statements
gale()->js('
const el = document.getElementById("modal");
el.classList.add("open");
el.querySelector("input").focus();
');
// Simple console log
gale()->js('console.log("Hello from server")');
// Call application functions
gale()->js('myApp.showNotification("Saved!")');
// Scroll to element
gale()->js('document.getElementById("results").scrollIntoView()');
// Focus input
gale()->js('document.querySelector("#email").focus()');
// Multiple statements
gale()->js('
const el = document.getElementById("modal");
el.classList.add("open");
el.querySelector("input").focus();
');
Auto-Remove Script
By default, the injected script element persists. Use autoRemove to clean up:
// Script element removed after execution
gale()->js('myApp.showNotification("Saved!")', [
'autoRemove' => true,
]);
// Script element removed after execution
gale()->js('myApp.showNotification("Saved!")'), [
'autoRemove' => true,
]);
JavaScript with PHP Data
Safely inject PHP values into JavaScript using json_encode():
$userData = json_encode([
'id' => $user->id,
'name' => $user->name,
'email' => $user->email,
]);
gale()->js("myApp.setCurrentUser({$userData})");
// Or with template
$message = json_encode($notification->message);
gale()->js("alert({$message})");
$userData = json_encode([
'id' => $user->id,
'name' => $user->name,
'email' => $user->email,
]);
gale()->js("myApp.setCurrentUser({$userData})");
// Or with template
$message = json_encode($notification->message);
gale()->js("alert({$message})");
dispatch() vs js()
| Feature | dispatch() | js() |
|---|---|---|
| Purpose | Fire DOM events | Execute arbitrary code |
| Integration | Works with Alpine listeners | Direct JavaScript execution |
| Data passing | Via event.detail | Via JSON in code string |
| Targeting | Built-in selector option | Manual querySelector |
| Best for | Component communication | Side effects, 3rd party libs |
Practical Examples
Toast Notification System
// Backend controller
public function save(Request $request)
{
$item->update($request->validated());
return gale()
->dispatch('toast', [
'type' => 'success',
'title' => 'Saved!',
'message' => 'Your changes have been saved.',
]);
}
// Backend controller
public function save(Request $request)
{
$item->update($request->validated());
return gale()
->dispatch('toast', [
'type' => 'success',
'title' => 'Saved!',
'message' => 'Your changes have been saved.',
]);
}
<!-- Toast container component -->
<div
x-data="{
toasts: [],
add(toast) {
const id = Date.now();
this.toasts.push({ ...toast, id });
setTimeout(() => this.remove(id), 5000);
},
remove(id) {
this.toasts = this.toasts.filter(t => t.id !== id);
}
}"
@toast.window="add($event.detail)"
class="fixed top-4 right-4 space-y-2 z-50">
<template x-for="toast in toasts" :key="toast.id">
<div
class="p-4 rounded shadow-lg min-w-64"
:class="{
'bg-green-500 text-white': toast.type === 'success',
'bg-red-500 text-white': toast.type === 'error',
'bg-blue-500 text-white': toast.type === 'info'
}">
<div class="font-bold" x-text="toast.title"></div>
<div x-text="toast.message"></div>
</div>
</template>
</div>
<!-- Toast container component -->
<div
x-data="{
toasts: [],
add(toast) {
const id = Date.now();
this.toasts.push({ ...toast, id });
setTimeout(() => this.remove(id), 5000);
},
remove(id) {
this.toasts = this.toasts.filter(t => t.id !== id);
}
}"
@toast.window="add($event.detail)"
class="fixed top-4 right-4 space-y-2 z-50">
<template x-for="toast in toasts" :key="toast.id">
<div
class="p-4 rounded shadow-lg min-w-64"
:class="{
'bg-green-500 text-white': toast.type === 'success',
'bg-red-500 text-white': toast.type === 'error',
'bg-blue-500 text-white': toast.type === 'info'
}">
<div class="font-bold" x-text="toast.title"></div>
<div x-text="toast.message"></div>
</div>
</template>
</div>
Third-Party Library Integration
// Trigger SweetAlert
gale()->js('
Swal.fire({
title: "Success!",
text: "Your file has been deleted.",
icon: "success"
});
');
// Update Chart.js
$data = json_encode($chartData);
gale()->js("
window.salesChart.data.datasets[0].data = {$data};
window.salesChart.update();
");
// Trigger Confetti
gale()->js('confetti({ particleCount: 100, spread: 70 })');
// Trigger SweetAlert
gale()->js('
Swal.fire({
title: "Success!",
text: "Your file has been deleted.",
icon: "success"
});
');
// Update Chart.js
$data = json_encode($chartData);
gale()->js("
window.salesChart.data.datasets[0].data = {$data};
window.salesChart.update();
");
// Trigger Confetti
gale()->js('confetti({ particleCount: 100, spread: 70 })');
Modal Control
// Open modal after save
public function save()
{
$item = Item::create($data);
return gale()
->state('item', $item)
->dispatch('open-modal', [
'modal' => 'share-modal',
'item' => $item->toArray(),
]);
}
// Close modal on success
public function share()
{
// Share logic...
return gale()
->dispatch('close-modal')
->dispatch('toast', [
'type' => 'success',
'message' => 'Shared successfully!',
]);
}
// Open modal after save
public function save()
{
$item = Item::create($data);
return gale()
->state('item', $item)
->dispatch('open-modal', [
'modal' => 'share-modal',
'item' => $item->toArray(),
]);
}
// Close modal on success
public function share()
{
// Share logic...
return gale()
->dispatch('close-modal')
->dispatch('toast', [
'type' => 'success',
'message' => 'Shared successfully!',
]);
}