Add helper functions for question type inference and options management in question editor

This commit is contained in:
Ross
2025-09-22 12:39:05 +01:00
parent ac562ed1e1
commit f087a6ef49
@@ -172,6 +172,65 @@
const resetQuestionOptionsButton = document.getElementById('reset-question-options'); const resetQuestionOptionsButton = document.getElementById('reset-question-options');
const questionsContainer = document.getElementById('questions-container'); const questionsContainer = document.getElementById('questions-container');
// Helper: get options array from a question regardless of schema shape
function getQuestionOptions(question) {
if (!question) return [];
if (Array.isArray(question.enum)) return question.enum.slice();
if (question.items && Array.isArray(question.items.enum)) return question.items.enum.slice();
return [];
}
// Helper: infer a UI questionType from the schema if question.questionType is not present
function inferQuestionType(question) {
if (!question) return 'text';
if (question.type === 'array') return 'multiselect';
if (question.format === 'select2' && question.type === 'string' && Array.isArray(getQuestionOptions(question))) return 'dropdown-select';
if (question.format === 'select' && question.type === 'string') return 'dropdown';
if (question.format === 'radio' && question.type === 'string') return 'radio';
if (question.format === 'textarea') return 'textarea';
if (question.type === 'number') return 'number';
if (question.type === 'range') return 'range';
if (question.enum && Array.isArray(question.enum) && question.enum.length === 2) {
const opts = question.enum.map(String).map(s => s.toLowerCase());
if ((opts.includes('yes') && opts.includes('no'))) return 'yesno';
if ((opts.includes('true') && opts.includes('false'))) return 'truefalse';
}
return 'text';
}
// Helper: apply an options array to a question object according to the selected UI type
function applyOptionsToQuestion(question, optionsArray, selectedType) {
optionsArray = (optionsArray || []).filter(o => o !== '');
if (selectedType === 'multiselect') {
question.type = 'array';
question.items = question.items || { type: 'string', enum: [] };
question.items.enum = optionsArray.length ? optionsArray : (question.items.enum || []);
question.uniqueItems = true;
question.format = 'select2';
delete question.enum;
} else {
// convert array->single if present
if (question.type === 'array' && question.items && Array.isArray(question.items.enum)) {
question.enum = question.items.enum.slice();
delete question.items;
delete question.uniqueItems;
}
if (optionsArray.length) {
question.enum = optionsArray;
}
// ensure string type for single-select/other
if (question.enum && Array.isArray(question.enum)) {
question.type = 'string';
}
// set formats for some known selected types
if (selectedType === 'dropdown-select') question.format = 'select2';
else if (selectedType === 'dropdown') question.format = 'select';
else if (selectedType === 'radio') question.format = 'radio';
}
// always store the UI mapping
question.questionType = selectedType;
}
question_editor.updateProps({ question_editor.updateProps({
onChange: (updatedContent, previousContent, context) => { onChange: (updatedContent, previousContent, context) => {
// Call the original onChange handler if it exists // Call the original onChange handler if it exists
@@ -576,8 +635,10 @@
while (questionOptionsContainer.firstChild) { while (questionOptionsContainer.firstChild) {
questionOptionsContainer.removeChild(questionOptionsContainer.firstChild); questionOptionsContainer.removeChild(questionOptionsContainer.firstChild);
} }
if (question.enum) { // Support both single-select (question.enum) and multiselect (question.items.enum)
question.enum.forEach((option, index) => { const enumList = getQuestionOptions(question) || [];
if (enumList.length) {
enumList.forEach((option, index) => {
const optionInput = document.createElement('div'); const optionInput = document.createElement('div');
optionInput.className = 'option-input'; optionInput.className = 'option-input';
optionInput.style.marginBottom = '5px'; optionInput.style.marginBottom = '5px';
@@ -586,7 +647,6 @@
<button type="button" class="btn btn-danger btn-sm remove-option-button" style="display: inline-block; width: 8%;">&times;</button> <button type="button" class="btn btn-danger btn-sm remove-option-button" style="display: inline-block; width: 8%;">&times;</button>
`; `;
questionOptionsContainer.appendChild(optionInput); questionOptionsContainer.appendChild(optionInput);
// Add event listener to remove the option // Add event listener to remove the option
optionInput.querySelector('.remove-option-button').addEventListener('click', () => { optionInput.querySelector('.remove-option-button').addEventListener('click', () => {
questionOptionsContainer.removeChild(optionInput); questionOptionsContainer.removeChild(optionInput);
@@ -596,6 +656,8 @@
} else { } else {
optionsConfigurator.style.display = 'none'; optionsConfigurator.style.display = 'none';
} }
// If questionType is not set, infer it from the schema
questionTypeSelect.value = question.questionType || inferQuestionType(question);
// Update the "Add New Question" button to save changes // Update the "Add New Question" button to save changes
addNewQuestionButton.textContent = 'Save Changes'; addNewQuestionButton.textContent = 'Save Changes';
@@ -630,15 +692,13 @@
question.format = questionTypeSelect.value; question.format = questionTypeSelect.value;
// Update the options if applicable // Update the options if applicable
if (question.enum) { const newOptions = questionOptionsContainer ? Array.from(questionOptionsContainer.querySelectorAll('.option-field')).map((input) => input.value.trim()).filter((option) => option !== '') : [];
question.enum = Array.from(questionOptionsContainer.querySelectorAll('.option-field')) const selectedType = questionTypeSelect ? questionTypeSelect.value : null;
.map((input) => input.value.trim()) applyOptionsToQuestion(question, newOptions, selectedType);
.filter((option) => option !== ''); // Exclude empty options
}
// Update the editor with the modified question // Update the editor with the modified question
currentContent.properties[questionKey] = question; currentContent.properties[questionKey] = question;
question_editor.update({ json: currentContent }); try { question_editor.update({ json: currentContent }); } catch (e) { console.warn('Failed to update editor after saveEditedQuestion', e); }
toastr.success(`Question "${question.title}" updated successfully.`); toastr.success(`Question "${question.title}" updated successfully.`);
renderQuestions(); // Re-render the questions list renderQuestions(); // Re-render the questions list