Polling & Confirmation
Gale provides x-poll for automatic data refreshing and x-confirm
for action confirmation dialogs. Both directives work seamlessly with Gale's SSE-based
response system.
The x-poll Directive
The x-poll directive automatically fetches data from an endpoint at regular
intervals. It's perfect for real-time dashboards, status updates, and live feeds.
<!-- Poll every 5 seconds (default) -->
<div x-data="{ status: 'unknown' }" x-poll="/api/status">
Status: <span x-text="status"></span>
</div>
<!-- Poll every 5 seconds (default) -->
<div x-data="{ status: 'unknown' }" x-poll="/api/status">
Status: <span x-text="status"></span>
</div>
Custom Interval
Set a custom polling interval using modifiers. Supports seconds (.5s)
or milliseconds (.500ms).
<!-- Poll every 2 seconds -->
<div x-poll.2s="/api/notifications">...</div>
<!-- Poll every 10 seconds -->
<div x-poll.10s="/api/metrics">...</div>
<!-- Poll every 500ms for fast updates -->
<div x-poll.500ms="/api/realtime">...</div>
<!-- Plain number is treated as seconds -->
<div x-poll.3="/api/data">...</div>
<!-- Poll every 2 seconds --> <div x-poll.2s="/api/notifications">...</div> <!-- Poll every 10 seconds --> <div x-poll.10s="/api/metrics">...</div> <!-- Poll every 500ms for fast updates --> <div x-poll.500ms="/api/realtime">...</div> <!-- Plain number is treated as seconds --> <div x-poll.3="/api/data">...</div>
Visibility-Aware Polling
Use the .visible modifier to pause polling when the browser tab is hidden.
This saves bandwidth and server resources.
<!-- Only poll when tab is visible -->
<div
x-data="{ messages: [] }"
x-poll.visible.5s="/api/messages">
<template x-for="msg in messages">
<div x-text="msg.content"></div>
</template>
</div>
<!-- Only poll when tab is visible -->
<div
x-data="{ messages: [] }"
x-poll.visible.5s="/api/messages">
<template x-for="msg in messages">
<div x-text="msg.content"></div>
</template>
</div>
Conditional Stop
Use x-poll-stop to stop polling when a condition is met. This is useful
for job status monitoring or one-time data fetching.
<!-- Stop polling when job is complete -->
<div
x-data="{ status: 'pending', progress: 0 }"
x-poll.2s="/api/job/123"
x-poll-stop="status === 'complete'">
<div class="progress-bar">
<div :style="'width: ' + progress + '%'"></div>
</div>
<span x-text="status"></span>
</div>
<!-- Stop polling when job is complete -->
<div
x-data="{ status: 'pending', progress: 0 }"
x-poll.2s="/api/job/123"
x-poll-stop="status === 'complete'">
<div class="progress-bar">
<div :style="'width: ' + progress + '%'"></div>
</div>
<span x-text="status"></span>
</div>
CSRF Protection
Add the .csrf modifier to include CSRF tokens with poll requests.
Required for endpoints that modify data.
<!-- Include CSRF token with requests -->
<div
x-data="{ heartbeat: null }"
x-poll.csrf.30s="/api/heartbeat">
Last ping: <span x-text="heartbeat"></span>
</div>
<!-- Include CSRF token with requests -->
<div
x-data="{ heartbeat: null }"
x-poll.csrf.30s="/api/heartbeat">
Last ping: <span x-text="heartbeat"></span>
</div>
Backend Response
Poll requests work like any Gale request. Return state updates from your controller:
public function jobStatus(Job $job)
{
return gale()->state([
'status' => $job->status,
'progress' => $job->progress,
'message' => $job->status_message,
]);
}
public function notifications()
{
$notifications = auth()->user()
->unreadNotifications()
->latest()
->take(10)
->get();
return gale()->state('notifications', $notifications);
}
public function jobStatus(Job $job)
{
return gale()->state([
'status' => $job->status,
'progress' => $job->progress,
'message' => $job->status_message,
]);
}
public function notifications()
{
$notifications = auth()->user()
->unreadNotifications()
->latest()
->take(10)
->get();
return gale()->state('notifications', $notifications);
}
The x-confirm Directive
The x-confirm directive shows a confirmation dialog before allowing
click or submit events to proceed. Uses the native browser dialog by default.
<!-- Basic confirmation -->
<button
x-confirm="Are you sure you want to delete this?"
@click="$deletex('/item/123')">
Delete Item
</button>
<!-- Basic confirmation -->
<button
x-confirm="Are you sure you want to delete this?"
@click="$deletex('/item/123')">
Delete Item
</button>
Default Message
If no message is provided, the default "Are you sure?" message is shown:
<!-- Uses default "Are you sure?" message -->
<button x-confirm @click="$deletex('/reset')">
Reset Everything
</button>
<!-- Uses default "Are you sure?" message -->
<button x-confirm @click="$deletex('/reset')">
Reset Everything
</button>
Dynamic Messages
Use expressions for dynamic confirmation messages that include data from your component:
<div x-data="{ itemName: 'Project Alpha' }">
<!-- String concatenation -->
<button
x-confirm="'Delete ' + itemName + '?'"
@click="$deletex('/item')">
Delete
</button>
<!-- Template literal -->
<button
x-confirm=`Are you sure you want to archive ${itemName}?`
@click="$postx('/archive')">
Archive
</button>
</div>
<div x-data="{ itemName: 'Project Alpha' }">
<!-- String concatenation -->
<button
x-confirm="'Delete ' + itemName + '?'"
@click="$deletex('/item')">
Delete
</button>
<!-- Template literal -->
<button
x-confirm="`Are you sure you want to archive ${itemName}?`"
@click="$postx('/archive')">
Archive
</button>
</div>
Form Submission
The x-confirm directive works on forms to confirm before submission:
<form
x-data="{ email: '', name: '' }"
x-confirm="Submit this registration form?"
@submit.prevent="$postx('/register')">
<input x-model="name" placeholder="Name">
<input x-model="email" placeholder="Email">
<button type="submit">Register</button>
</form>
<form
x-data="{ email: '', name: '' }"
x-confirm="Submit this registration form?"
@submit.prevent="$postx('/register')">
<input x-model="name" placeholder="Name">
<input x-model="email" placeholder="Email">
<button type="submit">Register</button>
</form>
Custom Confirm Handler
Replace the native browser dialog with a custom confirmation UI using
Alpine.gale.configureConfirm():
// Configure custom confirm handler
Alpine.gale.configureConfirm((message) => {
// Return a promise that resolves to boolean
return new Promise((resolve) => {
// Show your custom modal
customModal.show({
message,
onConfirm: () => resolve(true),
onCancel: () => resolve(false)
});
});
});
// Or use a third-party library like SweetAlert
Alpine.gale.configureConfirm((message) => {
return Swal.fire({
title: 'Confirm',
text: message,
icon: 'warning',
showCancelButton: true,
confirmButtonText: 'Yes',
cancelButtonText: 'Cancel'
}).then((result) => result.isConfirmed);
});
// Configure custom confirm handler
Alpine.gale.configureConfirm((message) => {
// Return a promise that resolves to boolean
return new Promise((resolve) => {
// Show your custom modal
customModal.show({
message,
onConfirm: () => resolve(true),
onCancel: () => resolve(false)
});
});
});
// Or use a third-party library like SweetAlert
Alpine.gale.configureConfirm((message) => {
return Swal.fire({
title: 'Confirm',
text: message,
icon: 'warning',
showCancelButton: true,
confirmButtonText: 'Yes',
cancelButtonText: 'Cancel'
}).then((result) => result.isConfirmed);
});
Live Dashboard Example
<div
x-data="{
metrics: {
activeUsers: 0,
requests: 0,
errors: 0,
cpu: 0,
memory: 0
},
lastUpdated: null
}"
x-poll.visible.3s="/api/dashboard/metrics"
class="grid grid-cols-3 gap-4">
<!-- Metrics cards -->
<div class="bg-white p-4 rounded shadow">
<h3>Active Users</h3>
<p class="text-3xl font-bold" x-text="metrics.activeUsers"></p>
</div>
<div class="bg-white p-4 rounded shadow">
<h3>Requests/min</h3>
<p class="text-3xl font-bold" x-text="metrics.requests"></p>
</div>
<div class="bg-white p-4 rounded shadow">
<h3>Error Rate</h3>
<p class="text-3xl font-bold"
:class="metrics.errors > 5 && 'text-red-500'"
x-text="metrics.errors + '%'"></p>
</div>
<!-- CPU and Memory bars -->
<div class="col-span-3 bg-white p-4 rounded shadow">
<div class="mb-4">
<div class="flex justify-between mb-1">
<span>CPU</span>
<span x-text="metrics.cpu + '%'"></span>
</div>
<div class="h-2 bg-gray-200 rounded">
<div
class="h-full bg-blue-500 rounded transition-all"
:style="'width: ' + metrics.cpu + '%'"></div>
</div>
</div>
<div>
<div class="flex justify-between mb-1">
<span>Memory</span>
<span x-text="metrics.memory + '%'"></span>
</div>
<div class="h-2 bg-gray-200 rounded">
<div
class="h-full bg-green-500 rounded transition-all"
:style="'width: ' + metrics.memory + '%'"></div>
</div>
</div>
</div>
<!-- Last updated -->
<div class="col-span-3 text-sm text-gray-500 text-right">
<span x-loading>Updating...</span>
<span x-loading.remove>Last updated: <span x-text="lastUpdated"></span></span>
</div>
</div>
<div
x-data="{
metrics: {
activeUsers: 0,
requests: 0,
errors: 0,
cpu: 0,
memory: 0
},
lastUpdated: null
}"
x-poll.visible.3s="/api/dashboard/metrics"
class="grid grid-cols-3 gap-4">
<!-- Metrics cards -->
<div class="bg-white p-4 rounded shadow">
<h3>Active Users</h3>
<p class="text-3xl font-bold" x-text="metrics.activeUsers"></p>
</div>
<div class="bg-white p-4 rounded shadow">
<h3>Requests/min</h3>
<p class="text-3xl font-bold" x-text="metrics.requests"></p>
</div>
<div class="bg-white p-4 rounded shadow">
<h3>Error Rate</h3>
<p class="text-3xl font-bold"
:class="metrics.errors > 5 && 'text-red-500'"
x-text="metrics.errors + '%'"></p>
</div>
<!-- CPU and Memory bars -->
<div class="col-span-3 bg-white p-4 rounded shadow">
<div class="mb-4">
<div class="flex justify-between mb-1">
<span>CPU</span>
<span x-text="metrics.cpu + '%'"></span>
</div>
<div class="h-2 bg-gray-200 rounded">
<div
class="h-full bg-blue-500 rounded transition-all"
:style="'width: ' + metrics.cpu + '%'"></div>
</div>
</div>
<div>
<div class="flex justify-between mb-1">
<span>Memory</span>
<span x-text="metrics.memory + '%'"></span>
</div>
<div class="h-2 bg-gray-200 rounded">
<div
class="h-full bg-green-500 rounded transition-all"
:style="'width: ' + metrics.memory + '%'"></div>
</div>
</div>
</div>
<!-- Last updated -->
<div class="col-span-3 text-sm text-gray-500 text-right">
<span x-loading>Updating...</span>
<span x-loading.remove>Last updated: <span x-text="lastUpdated"></span></span>
</div>
</div>
Delete Confirmation Example
<div x-data="{ items: [] }">
<table class="w-full">
<thead>
<tr>
<th>Name</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<template x-for="item in items" :key="item.id">
<tr>
<td x-text="item.name"></td>
<td class="space-x-2">
<!-- Edit - no confirmation -->
<button
@click=`$get('/items/${item.id}/edit')`
class="text-blue-500">
Edit
</button>
<!-- Delete - with confirmation -->
<button
x-confirm=`Delete "${item.name}"? This cannot be undone.`
@click=`$deletex('/items/${item.id}')`
x-loading.attr="disabled"
class="text-red-500">
<span x-loading.remove>Delete</span>
<span x-loading>Deleting...</span>
</button>
</td>
</tr>
</template>
</tbody>
</table>
</div>
<div x-data="{ items: [] }">
<table class="w-full">
<thead>
<tr>
<th>Name</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<template x-for="item in items" :key="item.id">
<tr>
<td x-text="item.name"></td>
<td class="space-x-2">
<!-- Edit - no confirmation -->
<button
@click="`$get('/items/${item.id}/edit')`"
class="text-blue-500">
Edit
</button>
<!-- Delete - with confirmation -->
<button
x-confirm="`Delete "${item.name}"? This cannot be undone.`"
@click="`$deletex('/items/${item.id}')`"
x-loading.attr="disabled"
class="text-red-500">
<span x-loading.remove>Delete</span>
<span x-loading>Deleting...</span>
</button>
</td>
</tr>
</template>
</tbody>
</table>
</div>