${index + 1}. ${q.text}
`; switch(q.type) { case 'text': content += ''; break; case 'textarea': content += ''; break; case 'multiple-choice': q.options.forEach(opt => { content += ` ${opt}
`;
});
break;
case 'checkbox':
q.options.forEach(opt => {
content += ` ${opt}
`;
});
break;
case 'scale':
content += '';
q.options.forEach((opt, i) => {
content += `
`;
});
content += '
';
break;
}
qDiv.innerHTML = content;
els.prevQContainer.appendChild(qDiv);
});
}
// Reads the config UI and updates the 'questions' array
function updateQuestionsFromConfig() {
const newQuestions = [];
const editorCards = els.qBuilderContainer.querySelectorAll('.ssi-question-editor');
editorCards.forEach(card => {
const id = parseInt(card.dataset.id);
const text = card.querySelector('.ssi-q-text').value;
const originalQ = questions.find(q => q.id === id);
const newQ = { id: id, type: originalQ.type, text: text };
if (originalQ.type === 'multiple-choice' || originalQ.type === 'checkbox' || originalQ.type === 'scale') {
newQ.options = [];
card.querySelectorAll('.ssi-input-option').forEach(optInp => {
newQ.options.push(optInp.value);
});
}
newQuestions.push(newQ);
});
questions = newQuestions;
}
function handleGenerate() {
updateQuestionsFromConfig();
renderPreview();
switchTab('sheet'); // Spec II.B.4
}
function handleAddQuestion() {
const type = els.qTypeSelect.value;
const newQ = {
id: qIdCounter++,
type: type,
text: 'New Question: (Click to edit)'
};
if (type === 'multiple-choice' || type === 'checkbox') {
newQ.options = ['Option 1', 'Option 2'];
} else if (type === 'scale') {
newQ.options = ['1', '2', '3', '4', '5'];
}
questions.push(newQ);
renderConfig(); // Re-render the editor list
}
// --- PDF DOWNLOAD (Spec II.C & VI.9) ---
async function downloadPDF() {
const { jsPDF } = window.jspdf;
const sheet = els.sheetWrapper; // The element to capture
try {
const canvas = await window.html2canvas(sheet, {
scale: 2, // Higher quality
useCORS: true,
logging: false,
width: sheet.offsetWidth,
});
const imgData = canvas.toDataURL('image/png');
// A4 Paper: 210mm wide, 297mm high
const pdfWidth = 210;
const pdfHeight = 297;
const margin = 10;
const doc = new jsPDF('p', 'mm', 'a4');
// Calculate aspect ratio
const imgWidth = pdfWidth - (margin * 2);
const imgHeight = (canvas.height * imgWidth) / canvas.width;
let heightLeft = imgHeight;
let position = margin;
doc.addImage(imgData, 'PNG', margin, position, imgWidth, imgHeight);
heightLeft -= (pdfHeight - (margin * 2));
// Handle long surveys that need multiple pages
while (heightLeft > 0) {
position = heightLeft - imgHeight + margin; // New position for next page
doc.addPage();
doc.addImage(imgData, 'PNG', margin, position, imgWidth, imgHeight);
heightLeft -= pdfHeight;
}
doc.save(`${els.inpTitle.value.replace(/ /g, '_') || 'survey'}.pdf`);
} catch (error) {
console.error("Error generating PDF:", error);
alert("An error occurred while generating the PDF.");
}
}
// --- EVENT LISTENERS (Spec IV.A) ---
function attachEventListeners() {
// Tabs
els.tabs.forEach(btn => btn.addEventListener('click', () => switchTab(btn.dataset.tab)));
els.prevTabBtn.addEventListener('click', () => switchTab('sheet'));
els.nextTabBtn.addEventListener('click', () => switchTab('config'));
// Main Actions
els.addQBtn.addEventListener('click', handleAddQuestion);
els.generateBtn.addEventListener('click', handleGenerate);
els.downloadPdfBtn.addEventListener('click', downloadPDF);
// Dynamic Listeners for config changes (Spec VI.4)
els.qBuilderContainer.addEventListener('click', function(e) {
const target = e.target;
// Find the parent editor card
const card = target.closest('.ssi-question-editor');
if (!card) return;
const id = parseInt(card.dataset.id);
const q = questions.find(q => q.id === id);
// Delete Question
if (target.classList.contains('ssi-delete-q-btn')) {
if (confirm('Are you sure you want to delete this question?')) {
questions = questions.filter(q => q.id !== id);
renderConfig();
}
}
// Add Option
if (target.classList.contains('ssi-add-option-btn')) {
q.options.push('New Option');
updateQuestionsFromConfig(); // Sync array
renderConfig(); // Re-render
}
// Remove Option
if (target.classList.contains('ssi-remove-option-btn')) {
const index = parseInt(target.dataset.index);
q.options.splice(index, 1);
updateQuestionsFromConfig(); // Sync array
renderConfig(); // Re-render
}
});
}
// --- RUN ---
init();
});

Social Survey Instrument Drafter
Survey Title
Survey introduction text appears here.
General Settings
Survey Questions
Add a New Question