HH/static/surveys/js/choices-builder.js
2026-01-24 15:27:30 +03:00

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();
});