further question editing improvements

This commit is contained in:
Ross
2025-05-12 22:45:08 +01:00
parent 7689bded66
commit 9f5fc09e23
+474 -382
View File
@@ -3,7 +3,7 @@
{% block content %}
<div>
{% if previous %}
@@ -14,23 +14,31 @@
<a href="{% url 'atlas:collection_case_details' collection.id next.id %}">Next question</a>
{% endif %}
</div>
<h2>Case: <a href="{% url 'atlas:case_detail' case_detail.case.pk %}">{{case_detail.case.title}}</a></h2>
{% if case_detail.question_schema is not None and not case_detail.question_answers %}
<div class="alert alert-danger" role="alert">
<h4 class="alert-heading">Answers not defined!</h4>
<p>A question schema has been defined but no question answers are found. Please set them below.</p>
</div>
<div class="alert alert-danger" role="alert">
<h4 class="alert-heading">Answers not defined!</h4>
<p>A question schema has been defined but no question answers are found. Please set them below.</p>
</div>
{% endif %}
<p>This form allows you to add questions to a case. A example of how the quesiton will be displayed is shown below.</p>
<p>If answers are supplied the question will be automarked</p>
<details class="help-text">
<summary><i class="bi bi-info-circle"></i> Help</summary>
<p>This form allows you to add questions to a case. A example of how the quesiton will be displayed is shown below.</p>
<p>If answers are supplied the question will be automarked</p>
<p>The question system is built using https://github.com/json-editor/json-editor, this allows for highly flexible forms / question design at a cost of some complexity. To help with this a number of preset questions are avalible to choose from (these can then be edited if needed). It is also possible to copy questions from other cases in the collection. </p>
<p>The question system is built using https://github.com/json-editor/json-editor, this allows for highly flexible forms / question design at a cost of some complexity. To help with this a number of preset questions are avalible to choose from (these can then be edited if needed). It is also possible to copy questions from other cases in the collection. </p>
<p>In most cases it is not necessary to edit the JSON schema directly as questions can be added / edited via the GUI.</p>
<p>To save the question schema you need to click the "Save Questions" button at the bottom of the page. The page will then refresh and you can see how questions will be displayed at the bottom of the page.</p>
</details>
Import schema from:
<button class="btn btn-primary btn-sm" type="button" data-bs-toggle="offcanvas" data-bs-target="#offcanvasRight" aria-controls="offcanvasRight"
@@ -43,49 +51,54 @@
>Preset questions</button>
<details><summary>
Question Block Title
Question Block Title
</summary>
<p>Title of the question block.</p>
<input id="question_block_title_input" type="text" name="title" value="{{case_detail.question_schema.title}}" class="form-control" placeholder="Question title" aria-label="Question title">
</details>
<details open><summary>Add Question</summary>
<details><summary>Modify Questions</summary>
<div id="questions-container" style="margin-top: 20px;">
<p>Title of the question block.</p>
<input id="question_block_title_input" type="text" name="title" value="{{case_detail.question_schema.title}}" class="form-control" placeholder="Question title" aria-label="Question title">
</details>
<details><summary>Modify Questions</summary>
<div id="questions-container" style="margin-top: 20px;">
<!-- Questions will be dynamically rendered here -->
</div>
</details>
<div>
<label for="question-type-select">Select Question Type:</label>
<select id="question-type-select" class="form-select">
<option value="radio">Multiple Choice (Radio)</option>
<option value="dropdown">Multiple Choice (Dropdown)</option>
<option value="text">Text Input</option>
<option value="multiselect">Multiselect</option>
<option value="yesno">Yes/No</option>
<option value="truefalse">True/False</option>
</select>
</div>
</div>
</details>
<details open id="add-question-block"><summary>Add/Edit Question</summary>
<div>
<label for="question-type-select">Select Question Type:</label>
<select id="question-type-select" class="form-select">
<option value="radio">Multiple Choice (Radio)</option>
<option value="dropdown">Multiple Choice (Dropdown)</option>
<option value="dropdown-select">Multiple Choice (Dropdown - Select2)</option>
<option value="text">Text Input</option>
<option value="textarea">Text Area</option>
<option value="multiselect">Multiselect</option>
<option value="yesno">Yes/No</option>
<option value="truefalse">True/False</option>
<option value="number">Number</option>
<option value="range">Range</option>
<div id="options-configurator" style="margin-top: 10px; ">
<label>Enter Options:</label>
<div id="question-options-container">
</select>
</div>
<div id="options-configurator" style="margin-top: 10px; ">
<label>Enter Options:</label>
<div id="question-options-container">
<!-- Individual option input fields will be added here -->
</div>
<button type="button" class="btn btn-secondary btn-sm" id="add-option-button" style="margin-top: 10px;">Add Option</button>
</div>
<div style="margin-top: 10px;">
<label for="question-title-input">Question Title:</label>
<input id="question-title-input" type="text" class="form-control" placeholder="Enter question title">
</div>
</div>
<button type="button" class="btn btn-secondary btn-sm" id="add-option-button" style="margin-top: 10px;">Add Option</button>
</div>
<div style="margin-top: 10px;">
<label for="question-title-input">Question Title:</label>
<input id="question-title-input" type="text" class="form-control" placeholder="Enter question title">
</div>
<div style="margin-top: 10px;">
<label for="question-description-input">Question Description:</label>
<input id="question-description-input" type="text" class="form-control" placeholder="Enter question description">
</div>
<div style="margin-top: 10px;">
<label for="question-description-input">Question Description:</label>
<input id="question-description-input" type="text" class="form-control" placeholder="Enter question description">
</div>
<button class="btn btn-primary btn-sm" id="add-new-question">Add New Question</button>
<button class="btn btn-secondary btn-sm" id="reset-question-options">Reset</button>
</details>
<button class="btn btn-primary btn-sm" id="add-new-question">Add New Question</button>
<button class="btn btn-secondary btn-sm" id="reset-question-options">Reset</button>
</details>
<div class="offcanvas offcanvas-end" tabindex="-1" id="offcanvasRight" aria-labelledby="offcanvasRightLabel"
>
@@ -101,7 +114,7 @@
<form method="POST">
{% csrf_token %}
{{ form.as_p }}
<button type="submit" value="save" name="submit">Submit</button>
<button type="submit" value="save" name="submit">Save Questions</button>
</form>
{% comment %} <h2>Blank form</h2>
@@ -150,14 +163,14 @@
// Wrap the existing onChange handler
const originalOnChange = question_editor.$$.ctx[question_editor.$$.props.onChange];
const addNewQuestionButton = document.getElementById('add-new-question');
const optionsConfigurator = document.getElementById('options-configurator');
const questionOptionsContainer = document.getElementById('question-options-container');
const addOptionButton = document.getElementById('add-option-button');
const questionTypeSelect = document.getElementById('question-type-select');
const questionTitleInput = document.getElementById('question-title-input');
const questionDescriptionInput = document.getElementById('question-description-input');
const resetQuestionOptionsButton = document.getElementById('reset-question-options');
const questionsContainer = document.getElementById('questions-container');
const optionsConfigurator = document.getElementById('options-configurator');
const questionOptionsContainer = document.getElementById('question-options-container');
const addOptionButton = document.getElementById('add-option-button');
const questionTypeSelect = document.getElementById('question-type-select');
const questionTitleInput = document.getElementById('question-title-input');
const questionDescriptionInput = document.getElementById('question-description-input');
const resetQuestionOptionsButton = document.getElementById('reset-question-options');
const questionsContainer = document.getElementById('questions-container');
question_editor.updateProps({
onChange: (updatedContent, previousContent, context) => {
@@ -175,423 +188,502 @@
// Sync changes from the input field to the editor
// Yes this is a bit backwards but it works
questionBlockTitleInput.addEventListener('input', () => {
try {
currentContent = JSON.parse(question_editor.get().text);
}
catch {
currentContent = JSON.parse(JSON.stringify(question_editor.get().json));
}
if (currentContent) {
currentContent.title = questionBlockTitleInput.value; // Update the title in the editor
question_editor.update({json: currentContent}); // Apply the changes to the editor
}
});
questionBlockTitleInput.addEventListener('input', () => {
try {
currentContent = JSON.parse(question_editor.get().text);
}
catch {
currentContent = JSON.parse(JSON.stringify(question_editor.get().json));
}
if (currentContent) {
currentContent.title = questionBlockTitleInput.value; // Update the title in the editor
question_editor.update({json: currentContent}); // Apply the changes to the editor
}
});
// Show or hide the options configurator based on the selected question type
questionTypeSelect.addEventListener('change', () => {
const selectedType = questionTypeSelect.value;
if (selectedType === 'radio' || selectedType === 'dropdown' || selectedType === 'multiselect') {
optionsConfigurator.style.display = 'block';
} else {
optionsConfigurator.style.display = 'none';
}
});
questionTypeSelect.addEventListener('change', () => {
const selectedType = questionTypeSelect.value;
if (selectedType === 'radio' || selectedType === 'dropdown' || selectedType === 'multiselect') {
optionsConfigurator.style.display = 'block';
} else {
optionsConfigurator.style.display = 'none';
}
});
// Add a new option input field
addOptionButton.addEventListener('click', () => {
const optionIndex = questionOptionsContainer.children.length + 1;
const optionInput = document.createElement('div');
optionInput.className = 'option-input';
optionInput.style.marginBottom = '5px';
optionInput.innerHTML = `
addOptionButton.addEventListener('click', () => {
const optionIndex = questionOptionsContainer.children.length + 1;
const optionInput = document.createElement('div');
optionInput.className = 'option-input';
optionInput.style.marginBottom = '5px';
optionInput.innerHTML = `
<input type="text" class="form-control option-field" placeholder="Option ${optionIndex}" style="display: inline-block; width: 90%;">
<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
optionInput.querySelector('.remove-option-button').addEventListener('click', () => {
questionOptionsContainer.removeChild(optionInput);
});
});
optionInput.querySelector('.remove-option-button').addEventListener('click', () => {
questionOptionsContainer.removeChild(optionInput);
});
});
// Reset the question options
resetQuestionOptionsButton.addEventListener('click', () => {
// Clear the title and description fields
questionTitleInput.value = '';
questionDescriptionInput.value = '';
// Reset the question options
resetQuestionOptionsButton.onclick = resetQuestion;
// Clear all option input fields
while (questionOptionsContainer.firstChild) {
questionOptionsContainer.removeChild(questionOptionsContainer.firstChild);
}
function resetQuestion() {
// Reset the question type to the default value
questionTypeSelect.value = 'radio';
// Clear the title and description fields
questionTitleInput.value = '';
questionDescriptionInput.value = '';
// Hide the options configurator if necessary
optionsConfigurator.style.display = 'block';
// Clear all option input fields
while (questionOptionsContainer.firstChild) {
questionOptionsContainer.removeChild(questionOptionsContainer.firstChild);
}
// Reset the "Add New Question" button
addNewQuestionButton.textContent = 'Add New Question';
// Reset the question type to the default value
questionTypeSelect.value = 'radio';
// Hide the options configurator if necessary
optionsConfigurator.style.display = 'block';
// Reset the "Add New Question" button
addNewQuestionButton.textContent = 'Add New Question';
addNewQuestionButton.onclick = addNewQuestion;
toastr.success('Question options have been reset.');
}
// Add a new question
addNewQuestionButton.onclick = addNewQuestion;
toastr.success('Question options have been reset.');
});
// Add a new question
addNewQuestionButton.onclick = addNewQuestion;
function addNewQuestion() {
function addNewQuestion() {
// Get the current content from the editor
try {
currentContent = JSON.parse(question_editor.get().text);
} catch {
currentContent = JSON.parse(JSON.stringify(question_editor.get().json));
}
try {
currentContent = JSON.parse(question_editor.get().text);
} catch {
currentContent = JSON.parse(JSON.stringify(question_editor.get().json));
}
if (!currentContent.properties) {
currentContent.properties = {};
current_question_number = 1;
} else {
current_question_number = Object.keys(currentContent.properties).length + 1;
}
if (!currentContent.properties) {
currentContent.properties = {};
current_question_number = 1;
} else {
current_question_number = Object.keys(currentContent.properties).length + 1;
}
// Get the selected question type
const selectedType = questionTypeSelect.value;
const selectedType = questionTypeSelect.value;
// Collect options from individual input fields
const options = Array.from(questionOptionsContainer.querySelectorAll('.option-field'))
.map(input => input.value.trim())
.filter(option => option !== ""); // Exclude empty options
const options = Array.from(questionOptionsContainer.querySelectorAll('.option-field'))
.map(input => input.value.trim())
.filter(option => option !== ""); // Exclude empty options
// Get the title and description
const title = questionTitleInput.value || `Question ${current_question_number}`;
const description = questionDescriptionInput.value || "Enter the question description here.";
const title = questionTitleInput.value || `Question ${current_question_number}`;
const description = questionDescriptionInput.value || "Enter the question description here.";
// Define the new question template based on the selected type
let newQuestion;
if (selectedType === 'radio') {
newQuestion = {
enum: options.length > 0 ? options : ["Option 1", "Option 2", "Option 3"], // Default options if none provided
type: "string",
title: title,
format: "radio",
description: description,
questionType: selectedType, // Add questionType property
};
} else if (selectedType === 'text') {
newQuestion = {
type: "string",
title: title,
format: "text",
description: description,
questionType: selectedType, // Add questionType property
};
} else if (selectedType === 'dropdown') {
newQuestion = {
enum: options.length > 0 ? options : ["Option 1", "Option 2", "Option 3"], // Default options if none provided
type: "string",
title: title,
format: "select",
description: description,
questionType: selectedType, // Add questionType property
};
} else if (selectedType === 'multiselect') {
newQuestion = {
type: "array",
items: {
enum: options.length > 0 ? options : ["Option 1", "Option 2", "Option 3"], // Default options if none provided
type: "string",
},
title: title,
format: "select2", // Multiselect format
description: description,
uniqueItems: true, // Ensure unique selections
questionType: selectedType, // Add questionType property
};
} else if (selectedType === 'yesno') {
newQuestion = {
enum: ["Yes", "No"],
type: "string",
title: title,
format: "radio",
description: description,
questionType: selectedType, // Add questionType property
};
} else if (selectedType === 'truefalse') {
newQuestion = {
enum: ["True", "False"],
type: "string",
title: title,
format: "radio",
description: description,
questionType: selectedType, // Add questionType property
};
}
let newQuestion;
if (selectedType === 'radio') {
newQuestion = {
enum: options.length > 0 ? options : ["Option 1", "Option 2", "Option 3"], // Default options if none provided
type: "string",
title: title,
format: "radio",
description: description,
questionType: selectedType, // Add questionType property
};
} else if (selectedType === 'text') {
newQuestion = {
type: "string",
title: title,
format: "text",
description: description,
questionType: selectedType, // Add questionType property
};
} else if (selectedType === 'dropdown') {
newQuestion = {
enum: options.length > 0 ? options : ["Option 1", "Option 2", "Option 3"], // Default options if none provided
type: "string",
title: title,
format: "select",
description: description,
questionType: selectedType, // Add questionType property
};
} else if (selectedType === 'dropdown-select') {
newQuestion = {
enum: options.length > 0 ? options : ["Option 1", "Option 2", "Option 3"], // Default options if none provided
type: "string",
title: title,
format: "select2",
description: description,
questionType: selectedType, // Add questionType property
};
} else if (selectedType === 'multiselect') {
newQuestion = {
type: "array",
items: {
enum: options.length > 0 ? options : ["Option 1", "Option 2", "Option 3"], // Default options if none provided
type: "string",
},
title: title,
format: "select2", // Multiselect format
description: description,
uniqueItems: true, // Ensure unique selections
questionType: selectedType, // Add questionType property
};
} else if (selectedType === 'yesno') {
newQuestion = {
enum: ["Yes", "No"],
type: "string",
title: title,
format: "radio",
description: description,
questionType: selectedType, // Add questionType property
};
} else if (selectedType === 'truefalse') {
newQuestion = {
enum: ["True", "False"],
type: "string",
title: title,
format: "radio",
description: description,
questionType: selectedType, // Add questionType property
};
} else if (selectedType === 'textarea') {
newQuestion = {
type: "string",
title: title,
format: "textarea", // Use textarea format
description: description,
questionType: selectedType, // Add questionType property
};
} else if (selectedType === 'number') {
newQuestion = {
type: "number",
title: title,
description: description,
questionType: selectedType, // Add questionType property
};
} else if (selectedType === 'range') {
newQuestion = {
type: "range",
title: title,
description: description,
minimum: 0, // Default minimum value
maximum: 100, // Default maximum value
questionType: selectedType, // Add questionType property
};
}
newQuestion.propertyOrder = current_question_number; // Set the property order
newQuestion.propertyOrder = current_question_number; // Set the property order
// Add the new question to the properties
currentContent.properties[`question_${current_question_number}`] = newQuestion;
currentContent.properties[`question_${current_question_number}`] = newQuestion;
// Update the editor with the new question
question_editor.update({ json: currentContent });
toastr.success(`New question added: ${title}`);
renderQuestions(); // Re-render the questions list
}
question_editor.update({ json: currentContent });
toastr.success(`New question added: ${title}`);
renderQuestions(); // Re-render the questions list
}
function renderQuestions() {
questionsContainer.innerHTML = ''; // Clear the container
function renderQuestions() {
questionsContainer.innerHTML = ''; // Clear the container
try {
let currentContent;
try {
currentContent = JSON.parse(question_editor.get().text);
} catch {
currentContent = JSON.parse(JSON.stringify(question_editor.get().json));
}
const properties = currentContent.properties || {};
try {
let currentContent;
try {
currentContent = JSON.parse(question_editor.get().text);
} catch {
currentContent = JSON.parse(JSON.stringify(question_editor.get().json));
}
const properties = currentContent.properties || {};
// Sort questions by propertyOrder
const sortedKeys = Object.keys(properties).sort((a, b) => {
const orderA = properties[a].propertyOrder !== undefined ? properties[a].propertyOrder : 1000;
const orderB = properties[b].propertyOrder !== undefined ? properties[b].propertyOrder : 1000;
return orderA - orderB;
});
const sortedKeys = Object.keys(properties).sort((a, b) => {
const orderA = properties[a].propertyOrder !== undefined ? properties[a].propertyOrder : 1000;
const orderB = properties[b].propertyOrder !== undefined ? properties[b].propertyOrder : 1000;
return orderA - orderB;
});
sortedKeys.forEach((key, index) => {
const question = properties[key];
const questionElement = document.createElement('div');
questionElement.className = 'question-item';
questionElement.style.marginBottom = '10px';
questionElement.setAttribute('draggable', 'true'); // Make the element draggable
questionElement.setAttribute('data-question-key', key);
questionElement.setAttribute('data-index', index);
sortedKeys.forEach((key, index) => {
const question = properties[key];
const questionElement = document.createElement('div');
questionElement.className = 'question-item';
questionElement.style.marginBottom = '10px';
questionElement.setAttribute('draggable', 'true'); // Make the element draggable
questionElement.setAttribute('data-question-key', key);
questionElement.setAttribute('data-index', index);
questionElement.innerHTML = `
questionElement.innerHTML = `
<strong>${question.title || key}</strong>
<button type="button" class="btn btn-warning btn-sm edit-question-button" data-question-key="${key}" style="margin-left: 10px;">Edit</button>
<button type="button" class="btn btn-danger btn-sm delete-question-button" data-question-key="${key}" style="margin-left: 10px;">Delete</button>
`;
questionsContainer.appendChild(questionElement);
questionsContainer.appendChild(questionElement);
// Add event listener to delete the question
questionElement.querySelector('.delete-question-button').addEventListener('click', (e) => {
const questionKey = e.target.getAttribute('data-question-key');
deleteQuestion(questionKey);
});
questionElement.querySelector('.delete-question-button').addEventListener('click', (e) => {
const questionKey = e.target.getAttribute('data-question-key');
deleteQuestion(questionKey);
});
// Add event listener to edit the question
questionElement.querySelector('.edit-question-button').addEventListener('click', (e) => {
const questionKey = e.target.getAttribute('data-question-key');
editQuestion(questionKey);
});
questionElement.querySelector('.edit-question-button').addEventListener('click', (e) => {
const questionKey = e.target.getAttribute('data-question-key');
editQuestion(questionKey);
});
// Add drag-and-drop event listeners
questionElement.addEventListener('dragstart', handleDragStart);
questionElement.addEventListener('dragover', handleDragOver);
questionElement.addEventListener('drop', handleDrop);
questionElement.addEventListener('dragend', handleDragEnd);
});
} catch (error) {
console.error('Error rendering questions:', error);
}
}
questionElement.addEventListener('dragstart', handleDragStart);
questionElement.addEventListener('dragover', handleDragOver);
questionElement.addEventListener('drop', handleDrop);
questionElement.addEventListener('dragend', handleDragEnd);
});
} catch (error) {
console.error('Error rendering questions:', error);
}
}
// Drag-and-drop event handlers
let draggedElement = null;
let draggedElement = null;
function handleDragStart(e) {
draggedElement = this;
e.dataTransfer.effectAllowed = 'move';
e.dataTransfer.setData('text/html', this.outerHTML);
this.classList.add('dragging');
}
function handleDragStart(e) {
draggedElement = this;
e.dataTransfer.effectAllowed = 'move';
e.dataTransfer.setData('text/html', this.outerHTML);
this.classList.add('dragging');
}
function handleDragOver(e) {
e.preventDefault();
e.dataTransfer.dropEffect = 'move';
}
function handleDragOver(e) {
e.preventDefault();
e.dataTransfer.dropEffect = 'move';
}
function handleDrop(e) {
e.preventDefault();
if (draggedElement !== this) {
const draggedIndex = parseInt(draggedElement.getAttribute('data-index'));
const targetIndex = parseInt(this.getAttribute('data-index'));
function handleDrop(e) {
e.preventDefault();
if (draggedElement !== this) {
const draggedIndex = parseInt(draggedElement.getAttribute('data-index'));
const targetIndex = parseInt(this.getAttribute('data-index'));
try {
let currentContent;
try {
currentContent = JSON.parse(question_editor.get().text);
} catch {
currentContent = JSON.parse(JSON.stringify(question_editor.get().json));
}
const properties = currentContent.properties || {};
const keys = Object.keys(properties);
try {
let currentContent;
try {
currentContent = JSON.parse(question_editor.get().text);
} catch {
currentContent = JSON.parse(JSON.stringify(question_editor.get().json));
}
const properties = currentContent.properties || {};
const keys = Object.keys(properties);
// Sort keys by propertyOrder
const sortedKeys = keys.sort((a, b) => {
const orderA = properties[a].propertyOrder !== undefined ? properties[a].propertyOrder : Infinity;
const orderB = properties[b].propertyOrder !== undefined ? properties[b].propertyOrder : Infinity;
return orderA - orderB;
});
const sortedKeys = keys.sort((a, b) => {
const orderA = properties[a].propertyOrder !== undefined ? properties[a].propertyOrder : Infinity;
const orderB = properties[b].propertyOrder !== undefined ? properties[b].propertyOrder : Infinity;
return orderA - orderB;
});
// Move the dragged question to the target position
const draggedKey = sortedKeys.splice(draggedIndex, 1)[0]; // Remove dragged key
sortedKeys.splice(targetIndex, 0, draggedKey); // Insert dragged key at target position
const draggedKey = sortedKeys.splice(draggedIndex, 1)[0]; // Remove dragged key
sortedKeys.splice(targetIndex, 0, draggedKey); // Insert dragged key at target position
// Reassign propertyOrder to ensure uniqueness and correct order
sortedKeys.forEach((key, index) => {
properties[key].propertyOrder = index + 1;
});
sortedKeys.forEach((key, index) => {
properties[key].propertyOrder = index + 1;
});
// Assign propertyOrder to any questions without it
keys.forEach((key) => {
if (properties[key].propertyOrder === undefined) {
properties[key].propertyOrder = sortedKeys.length + 1;
sortedKeys.push(key);
keys.forEach((key) => {
if (properties[key].propertyOrder === undefined) {
properties[key].propertyOrder = sortedKeys.length + 1;
sortedKeys.push(key);
}
});
currentContent.properties = properties;
question_editor.update({ json: currentContent });
toastr.success('Questions reordered successfully.');
renderQuestions(); // Re-render the questions list
} catch (error) {
console.error('Error reordering questions:', error);
toastr.error('Failed to reorder questions.');
}
}
}
});
currentContent.properties = properties;
question_editor.update({ json: currentContent });
toastr.success('Questions reordered successfully.');
renderQuestions(); // Re-render the questions list
} catch (error) {
console.error('Error reordering questions:', error);
toastr.error('Failed to reorder questions.');
}
}
}
function handleDragEnd() {
this.classList.remove('dragging');
}
function handleDragEnd() {
this.classList.remove('dragging');
}
// Function to delete a question
function deleteQuestion(questionKey) {
try {
try {
currentContent = JSON.parse(question_editor.get().text);
} catch {
currentContent = JSON.parse(JSON.stringify(question_editor.get().json));
}
if (currentContent.properties && currentContent.properties[questionKey]) {
delete currentContent.properties[questionKey]; // Remove the question
question_editor.update({ json: currentContent }); // Update the editor
toastr.success(`Question "${questionKey}" has been deleted.`);
renderQuestions(); // Re-render the questions list
}
} catch (error) {
console.error('Error deleting question:', error);
toastr.error('Failed to delete the question.');
}
}
function deleteQuestion(questionKey) {
try {
try {
currentContent = JSON.parse(question_editor.get().text);
} catch {
currentContent = JSON.parse(JSON.stringify(question_editor.get().json));
}
if (currentContent.properties && currentContent.properties[questionKey]) {
delete currentContent.properties[questionKey]; // Remove the question
question_editor.update({ json: currentContent }); // Update the editor
toastr.success(`Question "${questionKey}" has been deleted.`);
renderQuestions(); // Re-render the questions list
}
} catch (error) {
console.error('Error deleting question:', error);
toastr.error('Failed to delete the question.');
}
}
// Initial render of questions
renderQuestions();
renderQuestions();
function editQuestion(questionKey) {
try {
let currentContent;
try {
currentContent = JSON.parse(question_editor.get().text);
} catch {
currentContent = JSON.parse(JSON.stringify(question_editor.get().json));
}
const properties = currentContent.properties || {};
const question = properties[questionKey];
function editQuestion(questionKey) {
try {
let currentContent;
try {
currentContent = JSON.parse(question_editor.get().text);
} catch {
currentContent = JSON.parse(JSON.stringify(question_editor.get().json));
}
const properties = currentContent.properties || {};
const question = properties[questionKey];
if (question) {
if (question) {
// Populate the form fields with the question's data
questionTitleInput.value = question.title || '';
questionDescriptionInput.value = question.description || '';
questionTypeSelect.value = question.questionType || 'text';
questionTitleInput.value = question.title || '';
questionDescriptionInput.value = question.description || '';
questionTypeSelect.value = question.questionType || 'text';
// Clear existing options and populate them if applicable
while (questionOptionsContainer.firstChild) {
questionOptionsContainer.removeChild(questionOptionsContainer.firstChild);
}
if (question.enum) {
question.enum.forEach((option, index) => {
const optionInput = document.createElement('div');
optionInput.className = 'option-input';
optionInput.style.marginBottom = '5px';
optionInput.innerHTML = `
while (questionOptionsContainer.firstChild) {
questionOptionsContainer.removeChild(questionOptionsContainer.firstChild);
}
if (question.enum) {
question.enum.forEach((option, index) => {
const optionInput = document.createElement('div');
optionInput.className = 'option-input';
optionInput.style.marginBottom = '5px';
optionInput.innerHTML = `
<input type="text" class="form-control option-field" value="${option}" placeholder="Option ${index + 1}" style="display: inline-block; width: 90%;">
<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
optionInput.querySelector('.remove-option-button').addEventListener('click', () => {
questionOptionsContainer.removeChild(optionInput);
});
});
optionsConfigurator.style.display = 'block';
} else {
optionsConfigurator.style.display = 'none';
}
optionInput.querySelector('.remove-option-button').addEventListener('click', () => {
questionOptionsContainer.removeChild(optionInput);
});
});
optionsConfigurator.style.display = 'block';
} else {
optionsConfigurator.style.display = 'none';
}
// Update the "Add New Question" button to save changes
addNewQuestionButton.textContent = 'Save Changes';
addNewQuestionButton.onclick = () => {
saveEditedQuestion(questionKey);
};
}
} catch (error) {
console.error('Error editing question:', error);
toastr.error('Failed to edit the question.');
}
}
addNewQuestionButton.textContent = 'Save Changes';
addNewQuestionButton.onclick = () => {
saveEditedQuestion(questionKey);
};
document.getElementById('add-question-block').open = true; // Open the question block
document.getElementById('add-question-block').scrollIntoView({ behavior: 'smooth', block: "center" }); // Scroll to the question block
function saveEditedQuestion(questionKey) {
try {
let currentContent;
try {
currentContent = JSON.parse(question_editor.get().text);
} catch {
currentContent = JSON.parse(JSON.stringify(question_editor.get().json));
}
const properties = currentContent.properties || {};
const question = properties[questionKey];
}
} catch (error) {
console.error('Error editing question:', error);
toastr.error('Failed to edit the question.');
}
}
if (question) {
function saveEditedQuestion(questionKey) {
try {
let currentContent;
try {
currentContent = JSON.parse(question_editor.get().text);
} catch {
currentContent = JSON.parse(JSON.stringify(question_editor.get().json));
}
const properties = currentContent.properties || {};
const question = properties[questionKey];
if (question) {
// Update the question with the new values from the form
question.title = questionTitleInput.value || `Question ${questionKey}`;
question.description = questionDescriptionInput.value || '';
question.format = questionTypeSelect.value;
question.title = questionTitleInput.value || `Question ${questionKey}`;
question.description = questionDescriptionInput.value || '';
question.format = questionTypeSelect.value;
// Update the options if applicable
if (question.enum) {
question.enum = Array.from(questionOptionsContainer.querySelectorAll('.option-field'))
.map((input) => input.value.trim())
.filter((option) => option !== ''); // Exclude empty options
}
if (question.enum) {
question.enum = Array.from(questionOptionsContainer.querySelectorAll('.option-field'))
.map((input) => input.value.trim())
.filter((option) => option !== ''); // Exclude empty options
}
// Update the editor with the modified question
currentContent.properties[questionKey] = question;
question_editor.update({ json: currentContent });
toastr.success(`Question "${question.title}" updated successfully.`);
renderQuestions(); // Re-render the questions list
currentContent.properties[questionKey] = question;
question_editor.update({ json: currentContent });
toastr.success(`Question "${question.title}" updated successfully.`);
renderQuestions(); // Re-render the questions list
// Reset the "Add New Question" button
addNewQuestionButton.textContent = 'Add New Question';
addNewQuestionButton.onclick = addNewQuestion;
}
} catch (error) {
console.error('Error saving edited question:', error);
toastr.error('Failed to save the question.');
}
}
});
addNewQuestionButton.textContent = 'Add New Question';
addNewQuestionButton.onclick = addNewQuestion;
resetQuestion(); // Reset the question options
}
} catch (error) {
console.error('Error saving edited question:', error);
toastr.error('Failed to save the question.');
}
}
});
</script>
<style>
/* Style the <details> block */
details {
margin: 20px 0;
padding: 15px;
border: 1px solid #ddd;
border-radius: 5px;
background-color:rgba(13, 8, 8, 0.86);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
}
/* Style the <summary> element */
details summary {
font-weight: bold;
font-size: 1.1em;
cursor: pointer;
margin-bottom: 10px;
color: #333;
}
/* Add hover effect to <summary> */
details summary:hover {
color: #007bff;
}
/* Add padding to the content inside <details> */
details > *:not(summary) {
margin-top: 10px;
padding-left: 10px;
}
/* Add a border to the expanded <details> */
details[open] {
border-color: #007bff;
background-color: black;
}
</style>
{% endblock %}