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.
Gale v2.0 Feature
Use gale()->dispatch() and
gale()->js() for backend-to-frontend communication.
Event listeners work with $action() methods.
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="$action.get('/cart')">
<!-- Cart content -->
</div>
<!-- Element receives targeted event directly -->
<div
class="shopping-cart"
x-data="{ items: [] }"
@refresh="$action.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!',
]);
}