Skip to main content

Form Guardian for Vanilla JavaScript

Complete guide to using Form Guardian with vanilla JavaScript. No framework dependencies, works with any HTML form.

🚀 Quick Start

Installation

npm install @form-guardian/dom

Or via CDN:

<script type="module">
import { attachFormAutosave } from 'https://cdn.jsdelivr.net/npm/@form-guardian/dom/+esm';
</script>

Basic Usage

<!DOCTYPE html>
<html>
<head>
<title>Contact Form</title>
</head>
<body>
<form id="contact-form">
<input name="name" placeholder="Name" required />
<input name="email" type="email" placeholder="Email" required />
<textarea name="message" placeholder="Message"></textarea>
<button type="submit">Send</button>
</form>

<script type="module">
import { attachFormAutosave } from '@form-guardian/dom';

const form = document.getElementById('contact-form');

const autosave = attachFormAutosave({
formId: 'contact-form',
root: form,
autoRestore: true,
debounceMs: 500,
});

form.addEventListener('submit', async (e) => {
e.preventDefault();

// Clear draft after successful submission
await autosave.clear();

// Submit form data
const formData = new FormData(form);
await fetch('/api/contact', {
method: 'POST',
body: formData,
});
});
</script>
</body>
</html>

📚 Complete API

attachFormAutosave(options)

Parameters:

  • formId (string, required) - Unique form identifier
  • root (HTMLFormElement, required) - Form element
  • autoRestore (boolean, optional) - Auto-restore on page load
  • debounceMs (number, optional) - Debounce delay (default: 500ms)
  • ttl (object, optional) - Draft expiration time
  • onBeforeSave - Callback before save
  • onAfterSave - Callback after save
  • onBeforeRestore - Callback before restore
  • onAfterRestore - Callback after restore

Returns: FormAutosaveHandle with methods:

  • restore() - Restore draft
  • clear() - Clear draft
  • destroy() - Cleanup
  • hasDraft() - Check if draft exists
  • getCurrentValues() - Get current form values

Full API Reference →

🎯 Common Patterns

With Draft Indicator

<div id="draft-indicator" style="display: none;">
📝 Draft saved at <span id="draft-time"></span>
</div>

<form id="my-form">
<!-- form fields -->
</form>

<script type="module">
import { attachFormAutosave } from '@form-guardian/dom';

const form = document.getElementById('my-form');
const indicator = document.getElementById('draft-indicator');
const timeSpan = document.getElementById('draft-time');

const autosave = attachFormAutosave({
formId: 'my-form',
root: form,
autoRestore: true,
onAfterSave: async () => {
// Show indicator
indicator.style.display = 'block';
timeSpan.textContent = new Date().toLocaleTimeString();

// Hide after 3 seconds
setTimeout(() => {
indicator.style.display = 'none';
}, 3000);
},
});
</script>

With Restore Confirmation

import { attachFormAutosave } from '@form-guardian/dom';

const form = document.getElementById('my-form');
const autosave = attachFormAutosave({
formId: 'my-form',
root: form,
autoRestore: false, // Manual restore
});

// Check and ask user
async function checkForDraft() {
const hasDraft = await autosave.hasDraft();

if (hasDraft) {
const meta = await autosave.getDraftMeta();
const shouldRestore = confirm(
`Found draft from ${new Date(meta.updatedAt).toLocaleString()}. Restore?`
);

if (shouldRestore) {
await autosave.restore();
} else {
await autosave.clear();
}
}
}

checkForDraft();

Multi-Page Form

// Page 1
const step1Form = document.getElementById('step1');
const step1Autosave = attachFormAutosave({
formId: 'wizard-step1',
root: step1Form,
autoRestore: true,
});

document.getElementById('next-btn').addEventListener('click', async () => {
// Save current step
const values = await step1Autosave.getCurrentValues();

// Navigate to next page
window.location.href = '/step2';
});

// Page 2
const step2Form = document.getElementById('step2');
const step2Autosave = attachFormAutosave({
formId: 'wizard-step2',
root: step2Form,
autoRestore: true,
});

step2Form.addEventListener('submit', async (e) => {
e.preventDefault();

// Combine all steps
const step1Data = await loadDraftCore('wizard-step1');
const step2Data = await step2Autosave.getCurrentValues();

const completeData = { ...step1Data?.values, ...step2Data };

// Submit
await fetch('/api/submit', {
method: 'POST',
body: JSON.stringify(completeData),
});

// Clear all drafts
await clearDraft('wizard-step1');
await step2Autosave.clear();
});

With Form Validation

import { attachFormAutosave } from '@form-guardian/dom';

const form = document.getElementById('my-form');
const autosave = attachFormAutosave({
formId: 'my-form',
root: form,
autoRestore: true,
onBeforeSave: (values) => {
// Validate before saving
console.log('Validating before save:', values);
},
onAfterRestore: (values) => {
// Trigger validation after restore
form.querySelectorAll('input').forEach(input => {
input.dispatchEvent(new Event('blur'));
});
},
});

// Custom validation
form.addEventListener('submit', async (e) => {
e.preventDefault();

const values = await autosave.getCurrentValues();

// Validate
if (!values.name || values.name.length < 2) {
alert('Name must be at least 2 characters');
return;
}

if (!values.email || !values.email.includes('@')) {
alert('Invalid email');
return;
}

// Submit
await autosave.clear();
form.submit();
});

🔧 Advanced Features

Analytics Tracking

const autosave = attachFormAutosave({
formId: 'my-form',
root: form,
onBeforeSave: (values) => {
gtag('event', 'draft_saving', { form_id: 'my-form' });
},
onAfterSave: (values) => {
gtag('event', 'draft_saved', { form_id: 'my-form' });
},
onAfterRestore: (values) => {
gtag('event', 'draft_restored', { form_id: 'my-form' });
},
});

Security - Exclude Sensitive Fields

const autosave = attachFormAutosave({
formId: 'payment-form',
root: form,
blacklist: [
'input[type="password"]',
'input[name="cvv"]',
'input[name="cardNumber"]',
],
});

TTL - Auto-Expire Drafts

const autosave = attachFormAutosave({
formId: 'my-form',
root: form,
ttl: { days: 7 }, // Expire after 7 days
onDraftExpired: (draftId) => {
console.log('Draft expired:', draftId);
document.getElementById('draft-expired-msg').style.display = 'block';
},
});

Batch Saving

const autosave = attachFormAutosave({
formId: 'my-form',
root: form,
batchSaveInterval: 5000, // Save every 5 seconds
debounceMs: 300,
});

🎨 UI Examples

Draft Status Badge

<style>
.draft-status {
position: fixed;
top: 10px;
right: 10px;
padding: 8px 16px;
background: #4caf50;
color: white;
border-radius: 4px;
opacity: 0;
transition: opacity 0.3s;
}
.draft-status.show {
opacity: 1;
}
</style>

<div class="draft-status" id="status">Draft saved</div>

<script type="module">
const status = document.getElementById('status');

const autosave = attachFormAutosave({
formId: 'my-form',
root: document.getElementById('my-form'),
onAfterSave: () => {
status.classList.add('show');
setTimeout(() => status.classList.remove('show'), 2000);
},
});
</script>

Restore Dialog

<dialog id="restore-dialog">
<p>Found unsaved draft. Restore?</p>
<button id="restore-yes">Yes</button>
<button id="restore-no">No</button>
</dialog>

<script type="module">
const dialog = document.getElementById('restore-dialog');
const autosave = attachFormAutosave({
formId: 'my-form',
root: form,
autoRestore: false,
});

async function checkDraft() {
if (await autosave.hasDraft()) {
dialog.showModal();
}
}

document.getElementById('restore-yes').onclick = async () => {
await autosave.restore();
dialog.close();
};

document.getElementById('restore-no').onclick = async () => {
await autosave.clear();
dialog.close();
};

checkDraft();
</script>