Save bar

The bar

A message on the left, buttons on the right. Discard is a type="reset" button that restores the original values natively. Save is type="submit". Both point at the form with form="id", so the bar can sit outside the form.

Unsaved changes

The bar names its form with data-form

data-form is the id of the form the bar watches. Place the bar after the form and start it with hidden. The examples on this page use method="dialog" so saving does not reload the page.

Shows on change, hides when undone, stays at the bottom

The bar uses position: sticky; bottom: 0, so it sticks to the bottom of its scroll container (or the viewport). Edit the title and the bar appears. Type "Tote bag" back and it goes away.

Change it, then type it back

Every kind of field is compared

Checkboxes, radios and switches compare their checked state. Selects compare the selected options (multiple too). File inputs compare the chosen files. Other fields compare their value (sliders too). The original value is the one written in the HTML.

Shipping

A bar without hidden stays up

Use this for a new record that has never been saved, or after a failed save. The script still sets data-dirty but never hides the bar.

Unsaved changes

data-warn-on-leave asks before leaving with unsaved changes

Edit this field, then reload or follow a link. The browser asks first (beforeunload). Discard, Save and links inside the bar do not ask.

Events and markClean() after saving without a page load

The form gets tsunami:dirty when it starts to differ from the saved values and tsunami:clean when it matches again. After saving with fetch, call tsunami.saveBar.markClean(form) to make the current values the saved ones.

No changes

API

js/save-bar.js also works with bars and forms added later. No init call needed.

Function Arguments Description
tsunami.saveBar.check(target) target: .save-bar or form Compares the form now, shows or hides the bar, sets data-dirty and fires an event if the state changed. Returns true if there are changes. Call it after a script changes a field without firing input/change.
tsunami.saveBar.markClean(target) target: .save-bar or form Makes the current values the saved ones (updates defaultValue / defaultChecked / defaultSelected, so reset keeps working), then hides the bar.
Event On Detail
tsunami:dirty form (bubbles) { form, bars } Fired when the form starts to differ from the saved values.
tsunami:clean form (bubbles) { form, bars } Fired when the form matches the saved values again, by undoing an edit, reset, submit or markClean().

How fields are compared: checkbox/radio compare checked with defaultChecked, select compares selected with defaultSelected, file compares the chosen files, input type=hidden compares with its value when the script first saw the form, and other fields compare value with defaultValue. A change in the number of fields (rows added or removed) also counts as an edit; a script that adds rows must fire an input event on the form. Not compared: buttons, fieldset, output, fields inside a dialog, the select search box and [data-save-bar-ignore]. After submit, the values count as saved.

Attributes

Attribute / element Values Description
.save-bar Sticky bottom bar with a 1px top border
.save-bar > p Muted message on the left
.save-bar > div Button group on the right
data-form form id The form the bar watches. Without it, the script leaves the bar alone.
hidden present / absent Add it to start hidden and let the script toggle the bar. Without it, the bar is always shown.
data-warn-on-leave present / absent (default absent) Asks before leaving the page with unsaved changes (beforeunload). Discard, Save and links inside the bar do not ask.
data-dirty true, false Set by the script: whether the form has unsaved changes. Use it in CSS or read it from a script.
role="region" + aria-label Lets screen readers find the bar
button[type=reset][form] Discard restores the original values natively; the bar hides itself
button[type=submit][form] Save submits the form from outside it. After submit, the values count as saved.
a[href=""] Alternative Discard: reloads the page to restore the saved values, without asking first
data-save-bar-ignore On a form field: leave it out of the comparison (e.g. a filter box that is not saved)