Form guard
A form with unsaved-changes protection. With dirty-check on, leaving the page after fields have changed asks for confirmation: the browser's own warning when leaving through the browser, and the acun:form-guard-dialog dialog on wire:navigate transitions.
Basic usage
acun:form renders a normal <form>; dirty-check turns the protection on. The guard compares the current values of the fields with their values when the page was opened: if you change a field and set it back to its old value, the form counts as "clean" again. The status line below shows the result of this comparison (AcunUI.pro.formGuard.dirty). The old name window.formDirty gives the same value.
<acun:form dirty-check class="space-y-4"
x-data="{ changed: false }"
x-on:input="setTimeout(() => changed = AcunUI.pro.formGuard.dirty)"
x-on:change="setTimeout(() => changed = AcunUI.pro.formGuard.dirty)">
<acun:input name="customer_name" label="Customer name" value="Ayşe Yılmaz" />
<acun:checkbox name="active" label="Active customer" checked />
<p class="text-sm text-gray-500 dark:text-zinc-400" x-text="changed ? 'There are unsaved changes' : 'No changes'"></p>
</acun:form>
| Prop | Values |
|---|---|
dirty-check | true | false |
Livewire form
In Livewire forms, attributes such as wire:submit go straight to the form. Guarded forms added later by a Livewire update are also tracked automatically.
<acun:form wire:submit="save" dirty-check class="space-y-4">
<acun:input wire:model="customer_name" label="Customer name" />
<acun:select wire:model="district_id" label="District" :options="$districts" />
<acun:button type="submit">Save</acun:button>
</acun:form>
After saving
When saving succeeds, or when another record is loaded into the form, dispatch the form-saved event; the forms' current values are taken as the new starting point. When acun:modal closes, only the forms inside it are reset automatically.
public function save(): void
{
$this->validate();
$this->customer->update($this->only(['customer_name', 'district_id']));
$this->dispatch('form-saved');
}
| Prop | Values |
|---|---|
form-saved | Event; marks all guarded forms as clean |
acun:modal:closed | Event; dispatched when acun:modal closes (the old name modal-closed is also dispatched), resets the forms inside it |
Action bar
When placed in a form, acun:form-actions (PRO) binds itself to the form it is in; no extra attribute is needed. Save is enabled only when a field changes, and Previous/Next/Add/Delete only on a saved, clean record. Cancel restores the form to its last clean values (including wire:model values, select boxes and tag inputs); if cancel="revert" is given, that Livewire method is called instead and the values it returns count as the new clean state. sticky keeps the bar at the bottom of the screen on long forms; since it takes up space at the end of the form, nothing on the page shifts when it sticks. If you give the form an id, the InteractsWithFormModal events (formRecordLoaded('product-form'), formSaved('product-form')) update the bar's state and also refresh the guard's starting values.
<acun:form id="product-form" wire:submit="save" dirty-check class="space-y-6">
<acun:input wire:model="name" label="Product name" required />
<acun:combobox wire:model="tags" label="Tags" multiple :options="$tagOptions" />
<acun:form-actions sticky cancel="revert">
<p class="text-xs text-gray-500 dark:text-zinc-400">{{ $name }}</p>
</acun:form-actions>
</acun:form>
| Prop | Values |
|---|---|
sticky | Bar that stays at the bottom of the screen |
cancel | true: last clean values · "revert": Livewire method |
slot | Status line at the start of the bar |
What counts
The fields in the form are included in the comparison; fields carrying data-dirty-ignore (or those inside a container carrying this attribute) are not counted. Components that are not real form fields (acun:combobox, acun:tag-input, acun:rating, acun:input-otp) keep their value in hidden fields named after the property when bound with wire:model, and fire a change event when the user changes it; search and typing boxes and code boxes are not counted.
<acun:form wire:submit="save" dirty-check>
<acun:tag-input wire:model="keywords" label="Keywords" />
<input type="search" placeholder="Search the list" data-dirty-ignore>
</acun:form>
| Prop | Values |
|---|---|
data-dirty-ignore | Field or container; its changes are not counted |
Confirmation dialog
If there are changes and the user tries to go to another page with wire:navigate, the transition is stopped and acun:form-guard-dialog opens; "Continue" completes the transition. acun:layout.sidebar and acun:layout.horizontal already include this dialog; in your own layout, add it once. When leaving through the browser, such as reloading or closing the tab, the browser's own warning appears. In this example the dialog is opened with a button.
<acun:button variant="white" x-data x-on:click="$dispatch('form-guard-confirm', { url: null })">Show the dialog</acun:button>
<acun:form-guard-dialog />
Props and slots
The values the component accepts.
| Prop | Default | Description |
|---|---|---|
form: dirty-check | false | Turns on unsaved-changes protection (data-dirty-check) |
form-actions: sticky | false | PRO: the bar stays at the bottom of the screen (without shifting the layout) |
form-actions: cancel | true | PRO: Cancel returns to the last clean values; when a method name is given, that Livewire method is called |
form-guard-dialog | — | Takes no props; added to the layout once (already included in the ready-made layouts) |