/** * 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 = '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 = ''; // 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 = ''; 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(); });