Add autosave script for forms to prevent data loss on reload
This commit is contained in:
@@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -143,5 +143,7 @@
|
|||||||
</main>
|
</main>
|
||||||
<!-- END: MainContent -->
|
<!-- END: MainContent -->
|
||||||
|
|
||||||
|
{% load static %}
|
||||||
|
<script src="{% static 'js/autosave.js' %}"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -64,6 +64,9 @@ UNFOLD = {
|
|||||||
"STYLES": [
|
"STYLES": [
|
||||||
lambda request: "/static/css/unfold_custom.css",
|
lambda request: "/static/css/unfold_custom.css",
|
||||||
],
|
],
|
||||||
|
"SCRIPTS": [
|
||||||
|
lambda request: "/static/js/autosave.js",
|
||||||
|
],
|
||||||
"COLORS": {
|
"COLORS": {
|
||||||
"primary": {
|
"primary": {
|
||||||
"50": "#f2f2ff",
|
"50": "#f2f2ff",
|
||||||
|
|||||||
Reference in New Issue
Block a user