197 lines
6.8 KiB
JavaScript
197 lines
6.8 KiB
JavaScript
/**
|
|
* Choices Builder Module
|
|
* Provides a visual UI for managing multiple choice options instead of raw JSON
|
|
*/
|
|
|
|
class ChoicesBuilder {
|
|
constructor() {
|
|
this.init();
|
|
}
|
|
|
|
init() {
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
this.setupChoicesBuilders();
|
|
});
|
|
}
|
|
|
|
setupChoicesBuilders() {
|
|
// Find all choice fields
|
|
const choiceTextareas = document.querySelectorAll('textarea[name$="-choices_json"]');
|
|
|
|
choiceTextareas.forEach(textarea => {
|
|
this.createChoicesUI(textarea);
|
|
});
|
|
|
|
// Watch for dynamically added questions
|
|
const observer = new MutationObserver((mutations) => {
|
|
mutations.forEach((mutation) => {
|
|
mutation.addedNodes.forEach((node) => {
|
|
if (node.nodeType === 1) { // Element node
|
|
const newTextarea = node.querySelector('textarea[name$="-choices_json"]');
|
|
if (newTextarea && !newTextarea.dataset.choicesBuilder) {
|
|
this.createChoicesUI(newTextarea);
|
|
}
|
|
}
|
|
});
|
|
});
|
|
});
|
|
|
|
observer.observe(document.body, {
|
|
childList: true,
|
|
subtree: true
|
|
});
|
|
}
|
|
|
|
createChoicesUI(textarea) {
|
|
if (textarea.dataset.choicesBuilder) return;
|
|
textarea.dataset.choicesBuilder = 'true';
|
|
|
|
// Hide original textarea
|
|
textarea.style.display = 'none';
|
|
|
|
// Get parent container
|
|
const parent = textarea.closest('.choices-field');
|
|
if (!parent) return;
|
|
|
|
// Create choices UI container
|
|
const uiContainer = document.createElement('div');
|
|
uiContainer.className = 'choices-ui';
|
|
uiContainer.style.marginBottom = '10px';
|
|
|
|
// Create choices list
|
|
const choicesList = document.createElement('div');
|
|
choicesList.className = 'choices-list';
|
|
choicesList.style.marginBottom = '10px';
|
|
|
|
// Add choice button
|
|
const addBtn = document.createElement('button');
|
|
addBtn.type = 'button';
|
|
addBtn.className = 'btn btn-sm btn-success';
|
|
addBtn.innerHTML = '<i class="bi bi-plus-circle me-1"></i>Add Choice';
|
|
addBtn.addEventListener('click', () => this.addChoice(choicesList, textarea));
|
|
|
|
// Parse existing choices from textarea
|
|
const existingChoices = this.parseChoices(textarea.value);
|
|
existingChoices.forEach(choice => {
|
|
this.createChoiceElement(choicesList, textarea, choice);
|
|
});
|
|
|
|
// Assemble UI
|
|
uiContainer.appendChild(choicesList);
|
|
uiContainer.appendChild(addBtn);
|
|
|
|
// Insert before textarea
|
|
parent.insertBefore(uiContainer, textarea);
|
|
}
|
|
|
|
parseChoices(jsonString) {
|
|
if (!jsonString || jsonString.trim() === '') {
|
|
return [];
|
|
}
|
|
try {
|
|
return JSON.parse(jsonString);
|
|
} catch (e) {
|
|
console.error('Error parsing choices JSON:', e);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
addChoice(choicesList, textarea) {
|
|
const choice = {
|
|
value: String(choicesList.children.length + 1),
|
|
label: '',
|
|
label_ar: ''
|
|
};
|
|
this.createChoiceElement(choicesList, textarea, choice);
|
|
}
|
|
|
|
createChoiceElement(choicesList, textarea, choiceData = {}) {
|
|
const choiceDiv = document.createElement('div');
|
|
choiceDiv.className = 'choice-item mb-2 p-2 border rounded';
|
|
choiceDiv.style.display = 'flex';
|
|
choiceDiv.style.alignItems = 'center';
|
|
choiceDiv.style.gap = '10px';
|
|
|
|
// Drag handle (for future drag-and-drop)
|
|
const dragHandle = document.createElement('span');
|
|
dragHandle.className = 'drag-handle text-muted';
|
|
dragHandle.style.cursor = 'grab';
|
|
dragHandle.innerHTML = '<i class="bi bi-grip-vertical"></i>';
|
|
|
|
// Value input
|
|
const valueInput = document.createElement('input');
|
|
valueInput.type = 'text';
|
|
valueInput.className = 'form-control form-control-sm';
|
|
valueInput.style.width = '80px';
|
|
valueInput.placeholder = 'Value';
|
|
valueInput.value = choiceData.value || '';
|
|
valueInput.addEventListener('input', () => this.updateChoicesJSON(choicesList, textarea));
|
|
|
|
// English label input
|
|
const labelInput = document.createElement('input');
|
|
labelInput.type = 'text';
|
|
labelInput.className = 'form-control form-control-sm';
|
|
labelInput.style.flex = '1';
|
|
labelInput.placeholder = 'Choice (English)';
|
|
labelInput.value = choiceData.label || '';
|
|
labelInput.addEventListener('input', () => this.updateChoicesJSON(choicesList, textarea));
|
|
|
|
// Arabic label input
|
|
const labelArInput = document.createElement('input');
|
|
labelArInput.type = 'text';
|
|
labelArInput.className = 'form-control form-control-sm';
|
|
labelArInput.style.flex = '1';
|
|
labelArInput.placeholder = 'الخيار (Arabic)';
|
|
labelArInput.value = choiceData.label_ar || '';
|
|
labelArInput.addEventListener('input', () => this.updateChoicesJSON(choicesList, textarea));
|
|
|
|
// Delete button
|
|
const deleteBtn = document.createElement('button');
|
|
deleteBtn.type = 'button';
|
|
deleteBtn.className = 'btn btn-sm btn-outline-danger';
|
|
deleteBtn.innerHTML = '<i class="bi bi-trash"></i>';
|
|
deleteBtn.addEventListener('click', () => {
|
|
choiceDiv.remove();
|
|
this.updateChoicesJSON(choicesList, textarea);
|
|
});
|
|
|
|
// Assemble
|
|
choiceDiv.appendChild(dragHandle);
|
|
choiceDiv.appendChild(valueInput);
|
|
choiceDiv.appendChild(labelInput);
|
|
choiceDiv.appendChild(labelArInput);
|
|
choiceDiv.appendChild(deleteBtn);
|
|
|
|
choicesList.appendChild(choiceDiv);
|
|
|
|
// Update JSON
|
|
this.updateChoicesJSON(choicesList, textarea);
|
|
}
|
|
|
|
updateChoicesJSON(choicesList, textarea) {
|
|
const choices = [];
|
|
const choiceItems = choicesList.querySelectorAll('.choice-item');
|
|
|
|
choiceItems.forEach(item => {
|
|
const valueInput = item.querySelector('input:nth-of-type(1)');
|
|
const labelInput = item.querySelector('input:nth-of-type(2)');
|
|
const labelArInput = item.querySelector('input:nth-of-type(3)');
|
|
|
|
if (valueInput.value || labelInput.value) {
|
|
choices.push({
|
|
value: valueInput.value || '',
|
|
label: labelInput.value || '',
|
|
label_ar: labelArInput.value || ''
|
|
});
|
|
}
|
|
});
|
|
|
|
textarea.value = JSON.stringify(choices, null, 2);
|
|
}
|
|
}
|
|
|
|
// Initialize
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
window.choicesBuilder = new ChoicesBuilder();
|
|
});
|