Files
survey-bundle/public/js/survey-frontend.js
T
Claude Fable 5 833a8964cf Add topic sections, ordered inserts, rich-text descriptions and grouped results (Ausbaustufe 3)
- section content type with structure service, reader context and grouped results/PDF/Excel
- new questions append at the end (backend oncreate) and per-section add buttons
- rich-text descriptions: Quill 2 (frontend), reduced TinyMCE (backend), server-side whitelist sanitizer
- externalize inline assets, bundle Chart.js locally, harden debug access, validate range bounds

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-22 07:57:40 +02:00

239 lines
7.7 KiB
JavaScript

document.addEventListener('DOMContentLoaded', function () {
var surveyEditor = document.querySelector('[data-survey-handorgel]');
var resetEditorViewport = function () {
if (!surveyEditor) {
return;
}
window.scrollTo(0, 0);
};
if (surveyEditor && window.history && 'scrollRestoration' in window.history) {
window.history.scrollRestoration = 'manual';
}
resetEditorViewport();
window.addEventListener('load', function () {
window.setTimeout(resetEditorViewport, 0);
}, { once: true });
document.querySelectorAll('[data-survey-range]').forEach(function (input) {
var targetId = input.getAttribute('data-survey-range-target');
var output = targetId ? document.getElementById(targetId) : null;
if (!output) {
return;
}
var update = function () {
output.textContent = input.value;
};
input.addEventListener('input', update);
update();
});
document.querySelectorAll('[data-survey-question-type]').forEach(function (select) {
var wrapper = select.closest('[data-survey-question-editor]');
if (!wrapper) {
return;
}
var toggle = function () {
var showRange = select.value === 'range';
var showYesNoMaybe = select.value === 'yes_no_maybe';
var showChoice = select.value === 'choice';
var isSection = select.value === 'section';
wrapper.querySelectorAll('.question-only-field').forEach(function (element) {
element.style.display = isSection ? 'none' : '';
});
wrapper.querySelectorAll('.section-only-field').forEach(function (element) {
element.style.display = isSection ? '' : 'none';
});
wrapper.querySelectorAll('.range-config-field').forEach(function (element) {
element.style.display = showRange ? '' : 'none';
});
wrapper.querySelectorAll('.yes-no-config-field').forEach(function (element) {
element.style.display = showYesNoMaybe ? '' : 'none';
});
wrapper.querySelectorAll('.choice-config-field').forEach(function (element) {
element.style.display = showChoice ? '' : 'none';
});
};
select.addEventListener('change', toggle);
toggle();
});
document.querySelectorAll('[data-copy-text]').forEach(function (button) {
var originalLabel = button.getAttribute('data-copy-label') || button.textContent;
var successLabel = button.getAttribute('data-copy-success') || originalLabel;
var copyText = function (value) {
if (navigator.clipboard && navigator.clipboard.writeText) {
return navigator.clipboard.writeText(value);
}
return new Promise(function (resolve, reject) {
var helper = document.createElement('textarea');
helper.value = value;
helper.setAttribute('readonly', 'readonly');
helper.style.position = 'absolute';
helper.style.left = '-9999px';
document.body.appendChild(helper);
helper.select();
try {
document.execCommand('copy');
document.body.removeChild(helper);
resolve();
} catch (error) {
document.body.removeChild(helper);
reject(error);
}
});
};
button.addEventListener('click', function () {
var text = button.getAttribute('data-copy-text') || '';
if (!text) {
return;
}
copyText(text).then(function () {
button.textContent = successLabel;
window.setTimeout(function () {
button.textContent = originalLabel;
}, 1800);
}).catch(function () {
button.textContent = originalLabel;
});
});
});
var surveyDirtyChecks = [];
var surveyIsSubmitting = false;
document.querySelectorAll('[data-survey-dirty-form]').forEach(function (form) {
var formId = form.getAttribute('id') || '';
var hint = form.querySelector('[data-survey-dirty-hint]');
if (!hint && formId) {
hint = document.querySelector('[data-survey-dirty-hint-for="' + formId + '"]');
}
var serializeForm = function () {
var entries = [];
var formData = new FormData(form);
formData.forEach(function (value, key) {
entries.push(key + '=' + String(value));
});
return entries.join('&');
};
var initialState = serializeForm();
var isDirty = function () {
return serializeForm() !== initialState;
};
surveyDirtyChecks.push(isDirty);
var updateDirtyState = function () {
if (hint) {
hint.hidden = !isDirty();
}
};
if (hint) {
hint.hidden = true;
}
form.addEventListener('input', updateDirtyState);
form.addEventListener('change', updateDirtyState);
form.addEventListener('reset', function () {
window.setTimeout(function () {
if (hint) {
hint.hidden = true;
}
}, 0);
});
});
if (surveyDirtyChecks.length > 0) {
// Ein bewusstes Absenden (Speichern oder eine Aktion) darf nicht warnen.
document.addEventListener('submit', function () {
surveyIsSubmitting = true;
}, true);
window.addEventListener('beforeunload', function (event) {
if (surveyIsSubmitting) {
return undefined;
}
var hasUnsavedChanges = surveyDirtyChecks.some(function (isDirty) {
return isDirty();
});
if (!hasUnsavedChanges) {
return undefined;
}
// Moderne Browser zeigen einen generischen Hinweis; der Text wird
// ignoriert, muss aber gesetzt werden, damit der Dialog erscheint.
event.preventDefault();
event.returnValue = '';
return '';
});
}
if (typeof handorgel === 'function') {
document.querySelectorAll('[data-survey-handorgel]').forEach(function (element) {
var accordion = new handorgel(element, {
collapsible: true,
multiSelectable: false,
initialOpenTransition: true,
initialOpenTransitionDelay: 0
});
if (accordion && Array.isArray(accordion.folds)) {
accordion.folds.forEach(function (fold) {
if (!fold) {
return;
}
var article = fold.header ? fold.header.closest('article') : null;
var shouldOpen = !!(article && article.hasAttribute('data-survey-open'));
if (shouldOpen && !fold.expanded) {
fold.open(false);
} else if (!shouldOpen && fold.expanded) {
fold.close();
}
});
var openArticle = element.querySelector('article[data-survey-open]');
if (openArticle && typeof openArticle.scrollIntoView === 'function') {
window.setTimeout(function () {
openArticle.scrollIntoView({ block: 'start', behavior: 'smooth' });
}, 50);
}
}
});
}
resetEditorViewport();
});