Add autosave script for forms to prevent data loss on reload

This commit is contained in:
Dmitry Belan
2026-09-24 21:48:11 +03:00
parent aa4a35580c
commit aa8894df59
3 changed files with 133 additions and 0 deletions
+128
View File
@@ -0,0 +1,128 @@
document.addEventListener('DOMContentLoaded', function() {
// Unique key for the current page URL
const pageKey = window.location.pathname + window.location.search;
const STORAGE_KEY = 'autosave_' + pageKey;
// Expiration time: 12 hours
const EXPIRATION_MS = 12 * 60 * 60 * 1000;
function saveFormData() {
const forms = document.querySelectorAll('form');
let formData = {};
let hasData = false;
forms.forEach((form, formIndex) => {
const inputs = form.querySelectorAll('input:not([type="password"]):not([type="hidden"]):not([type="submit"]):not([type="file"]), textarea, select');
let formState = {};
inputs.forEach(input => {
// Ignore inputs without a name
if (!input.name) return;
if (input.type === 'checkbox' || input.type === 'radio') {
formState[input.name] = input.checked;
} else {
formState[input.name] = input.value;
}
});
if (Object.keys(formState).length > 0) {
formData[formIndex] = formState;
hasData = true;
}
});
if (hasData) {
const dataToSave = {
timestamp: new Date().getTime(),
data: formData
};
localStorage.setItem(STORAGE_KEY, JSON.stringify(dataToSave));
}
}
function loadFormData() {
const saved = localStorage.getItem(STORAGE_KEY);
if (saved) {
try {
const parsed = JSON.parse(saved);
const now = new Date().getTime();
// Discard data if it's older than expiration time
if (now - parsed.timestamp > EXPIRATION_MS) {
localStorage.removeItem(STORAGE_KEY);
return;
}
const formData = parsed.data;
const forms = document.querySelectorAll('form');
forms.forEach((form, formIndex) => {
if (formData[formIndex]) {
const formState = formData[formIndex];
const inputs = form.querySelectorAll('input:not([type="password"]):not([type="hidden"]):not([type="submit"]):not([type="file"]), textarea, select');
inputs.forEach(input => {
if (input.name && formState[input.name] !== undefined) {
if (input.type === 'checkbox' || input.type === 'radio') {
// Set checked state
if (input.checked !== formState[input.name]) {
input.checked = formState[input.name];
}
} else {
// Set value
if (input.value !== formState[input.name]) {
input.value = formState[input.name];
}
}
}
});
}
});
// Show a small non-intrusive toast notification
showToast("Восстановлены несохраненные данные");
} catch (e) {
console.error("Error parsing autosave data", e);
}
}
}
function showToast(message) {
const toast = document.createElement('div');
toast.textContent = message;
toast.style.position = 'fixed';
toast.style.bottom = '20px';
toast.style.right = '20px';
toast.style.backgroundColor = '#494bd6';
toast.style.color = 'white';
toast.style.padding = '10px 20px';
toast.style.borderRadius = '5px';
toast.style.zIndex = '9999';
toast.style.fontFamily = '"Plus Jakarta Sans", sans-serif';
toast.style.fontSize = '12px';
toast.style.boxShadow = '0 4px 6px rgba(0,0,0,0.3)';
toast.style.transition = 'opacity 0.5s ease';
document.body.appendChild(toast);
setTimeout(() => {
toast.style.opacity = '0';
setTimeout(() => toast.remove(), 500);
}, 3000);
}
// Load data on page load
loadFormData();
// Debounce function for saving data so it doesn't trigger on every single keystroke excessively
let timeout;
document.addEventListener('input', function(e) {
if (['INPUT', 'TEXTAREA', 'SELECT'].includes(e.target.tagName)) {
clearTimeout(timeout);
timeout = setTimeout(saveFormData, 500);
}
});
// Clear saved data on successful form submission
document.addEventListener('submit', function(e) {
localStorage.removeItem(STORAGE_KEY);
});
});
+2
View File
@@ -143,5 +143,7 @@
</main>
<!-- END: MainContent -->
{% load static %}
<script src="{% static 'js/autosave.js' %}"></script>
</body>
</html>
+3
View File
@@ -64,6 +64,9 @@ UNFOLD = {
"STYLES": [
lambda request: "/static/css/unfold_custom.css",
],
"SCRIPTS": [
lambda request: "/static/js/autosave.js",
],
"COLORS": {
"primary": {
"50": "#f2f2ff",