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 identifierroot(HTMLFormElement, required) - Form elementautoRestore(boolean, optional) - Auto-restore on page loaddebounceMs(number, optional) - Debounce delay (default: 500ms)ttl(object, optional) - Draft expiration timeonBeforeSave- Callback before saveonAfterSave- Callback after saveonBeforeRestore- Callback before restoreonAfterRestore- Callback after restore
Returns: FormAutosaveHandle with methods:
restore()- Restore draftclear()- Clear draftdestroy()- CleanuphasDraft()- Check if draft existsgetCurrentValues()- Get current form values
🎯 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>