296 lines
10 KiB
JavaScript
296 lines
10 KiB
JavaScript
/**
|
|
* Survey Preview Module
|
|
* Provides real-time preview of survey questions as they are being built
|
|
*/
|
|
|
|
class SurveyPreview {
|
|
constructor() {
|
|
this.previewContainer = null;
|
|
this.init();
|
|
}
|
|
|
|
init() {
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
this.createPreviewPanel();
|
|
this.setupEventListeners();
|
|
});
|
|
}
|
|
|
|
createPreviewPanel() {
|
|
// Find the main container
|
|
const mainContainer = document.querySelector('.container-fluid');
|
|
if (!mainContainer) return;
|
|
|
|
// Create preview card
|
|
const previewCard = document.createElement('div');
|
|
previewCard.className = 'card';
|
|
previewCard.id = 'survey-preview-card';
|
|
previewCard.style.marginTop = '20px';
|
|
previewCard.style.display = 'none'; // Hidden by default
|
|
|
|
previewCard.innerHTML = `
|
|
<div class="card-header d-flex justify-content-between align-items-center">
|
|
<h5 class="mb-0"><i class="bi bi-eye me-2"></i>Survey Preview</h5>
|
|
<div>
|
|
<button type="button" class="btn btn-sm btn-outline-primary" id="toggle-preview">
|
|
<i class="bi bi-arrows-expand"></i> Expand
|
|
</button>
|
|
<button type="button" class="btn btn-sm btn-outline-secondary" id="close-preview">
|
|
<i class="bi bi-x-lg"></i> Close
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="card-body" id="preview-content">
|
|
<div class="text-center text-muted py-5">
|
|
<i class="bi bi-eye-slash" style="font-size: 3rem;"></i>
|
|
<p class="mt-3">Add questions to see preview</p>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
// Add preview toggle button to header
|
|
const headerTitle = document.querySelector('h2');
|
|
if (headerTitle) {
|
|
const toggleBtn = document.createElement('button');
|
|
toggleBtn.type = 'button';
|
|
toggleBtn.className = 'btn btn-outline-primary ms-3';
|
|
toggleBtn.innerHTML = '<i class="bi bi-eye"></i> Preview';
|
|
toggleBtn.addEventListener('click', () => this.togglePreview());
|
|
headerTitle.parentNode.appendChild(toggleBtn);
|
|
}
|
|
|
|
// Insert preview card after main form
|
|
const mainRow = document.querySelector('.row');
|
|
if (mainRow) {
|
|
mainRow.parentNode.insertBefore(previewCard, mainRow.nextSibling);
|
|
}
|
|
|
|
this.previewContainer = previewCard;
|
|
}
|
|
|
|
setupEventListeners() {
|
|
// Toggle preview button
|
|
const toggleBtn = document.getElementById('toggle-preview');
|
|
if (toggleBtn) {
|
|
toggleBtn.addEventListener('click', () => this.togglePreviewSize());
|
|
}
|
|
|
|
// Close preview button
|
|
const closeBtn = document.getElementById('close-preview');
|
|
if (closeBtn) {
|
|
closeBtn.addEventListener('click', () => this.togglePreview());
|
|
}
|
|
|
|
// Watch for form changes
|
|
const form = document.getElementById('survey-template-form');
|
|
if (form) {
|
|
const observer = new MutationObserver(() => {
|
|
this.updatePreview();
|
|
});
|
|
|
|
observer.observe(form, {
|
|
childList: true,
|
|
subtree: true,
|
|
attributes: true,
|
|
attributeFilter: ['value', 'checked']
|
|
});
|
|
}
|
|
|
|
// Listen for input changes
|
|
document.addEventListener('input', (e) => {
|
|
if (e.target.closest('#questions-container')) {
|
|
this.updatePreview();
|
|
}
|
|
});
|
|
|
|
document.addEventListener('change', (e) => {
|
|
if (e.target.closest('#questions-container')) {
|
|
this.updatePreview();
|
|
}
|
|
});
|
|
}
|
|
|
|
togglePreview() {
|
|
if (!this.previewContainer) return;
|
|
|
|
const isHidden = this.previewContainer.style.display === 'none';
|
|
this.previewContainer.style.display = isHidden ? 'block' : 'none';
|
|
|
|
if (isHidden) {
|
|
this.updatePreview();
|
|
}
|
|
}
|
|
|
|
togglePreviewSize() {
|
|
if (!this.previewContainer) return;
|
|
|
|
const cardBody = this.previewContainer.querySelector('.card-body');
|
|
const isExpanded = cardBody.classList.contains('expanded');
|
|
|
|
if (isExpanded) {
|
|
cardBody.classList.remove('expanded');
|
|
cardBody.style.maxHeight = '500px';
|
|
cardBody.style.overflowY = 'auto';
|
|
} else {
|
|
cardBody.classList.add('expanded');
|
|
cardBody.style.maxHeight = 'none';
|
|
cardBody.style.overflow = 'visible';
|
|
}
|
|
}
|
|
|
|
updatePreview() {
|
|
if (!this.previewContainer || this.previewContainer.style.display === 'none') {
|
|
return;
|
|
}
|
|
|
|
const previewContent = document.getElementById('preview-content');
|
|
if (!previewContent) return;
|
|
|
|
// Get survey name
|
|
const nameInput = document.querySelector('input[name="name"]');
|
|
const name = nameInput ? nameInput.value : 'Untitled Survey';
|
|
|
|
// Get all questions
|
|
const questionForms = document.querySelectorAll('.question-form:not([style*="display: none"])');
|
|
const questions = Array.from(questionForms).map(form => this.extractQuestionData(form)).filter(q => q);
|
|
|
|
if (questions.length === 0) {
|
|
previewContent.innerHTML = `
|
|
<div class="text-center text-muted py-5">
|
|
<i class="bi bi-eye-slash" style="font-size: 3rem;"></i>
|
|
<p class="mt-3">Add questions to see preview</p>
|
|
</div>
|
|
`;
|
|
return;
|
|
}
|
|
|
|
// Generate preview HTML
|
|
let previewHTML = `
|
|
<div class="survey-preview">
|
|
<div class="survey-title mb-4">
|
|
<h4>${this.escapeHtml(name)}</h4>
|
|
<p class="text-muted">Preview - ${questions.length} question(s)</p>
|
|
</div>
|
|
`;
|
|
|
|
questions.forEach((q, index) => {
|
|
previewHTML += this.renderQuestionPreview(q, index + 1);
|
|
});
|
|
|
|
previewHTML += '</div>';
|
|
previewContent.innerHTML = previewHTML;
|
|
}
|
|
|
|
extractQuestionData(form) {
|
|
const textInput = form.querySelector('input[name$="-text"]');
|
|
const textArInput = form.querySelector('input[name$="-text_ar"]');
|
|
const typeSelect = form.querySelector('select[name$="-question_type"]');
|
|
const requiredCheckbox = form.querySelector('input[name$="-is_required"]');
|
|
const choicesTextarea = form.querySelector('textarea[name$="-choices_json"]');
|
|
|
|
if (!textInput || !textInput.value.trim()) {
|
|
return null;
|
|
}
|
|
|
|
const data = {
|
|
text: textInput.value,
|
|
text_ar: textArInput ? textArInput.value : '',
|
|
type: typeSelect ? typeSelect.value : 'text',
|
|
required: requiredCheckbox ? requiredCheckbox.checked : false,
|
|
choices: []
|
|
};
|
|
|
|
if (choicesTextarea) {
|
|
try {
|
|
const choices = JSON.parse(choicesTextarea.value);
|
|
if (Array.isArray(choices)) {
|
|
data.choices = choices;
|
|
}
|
|
} catch (e) {
|
|
// Invalid JSON, ignore
|
|
}
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
renderQuestionPreview(question, number) {
|
|
let questionHTML = `
|
|
<div class="survey-question-preview mb-4 p-3 border rounded">
|
|
<div class="mb-2">
|
|
<strong>Q${number}:</strong> ${this.escapeHtml(question.text)}
|
|
${question.required ? '<span class="text-danger">*</span>' : ''}
|
|
</div>
|
|
`;
|
|
|
|
switch (question.type) {
|
|
case 'text':
|
|
questionHTML += `
|
|
<input type="text" class="form-control" placeholder="Enter your answer" disabled>
|
|
`;
|
|
break;
|
|
|
|
case 'rating':
|
|
questionHTML += `
|
|
<div class="rating-preview">
|
|
${[1, 2, 3, 4, 5].map(n => `
|
|
<label class="rating-option me-2">
|
|
<input type="radio" name="preview-rating-${number}" value="${n}" disabled>
|
|
<span class="badge ${n <= 3 ? 'bg-warning' : 'bg-success'}">${n}</span>
|
|
</label>
|
|
`).join('')}
|
|
</div>
|
|
`;
|
|
break;
|
|
|
|
case 'single_choice':
|
|
if (question.choices.length > 0) {
|
|
questionHTML += '<div class="choices-preview">';
|
|
question.choices.forEach(choice => {
|
|
questionHTML += `
|
|
<label class="d-block mb-1">
|
|
<input type="radio" name="preview-choice-${number}" disabled>
|
|
${this.escapeHtml(choice.label)}
|
|
</label>
|
|
`;
|
|
});
|
|
questionHTML += '</div>';
|
|
} else {
|
|
questionHTML += '<p class="text-muted small">No choices defined</p>';
|
|
}
|
|
break;
|
|
|
|
case 'multiple_choice':
|
|
if (question.choices.length > 0) {
|
|
questionHTML += '<div class="choices-preview">';
|
|
question.choices.forEach(choice => {
|
|
questionHTML += `
|
|
<label class="d-block mb-1">
|
|
<input type="checkbox" disabled>
|
|
${this.escapeHtml(choice.label)}
|
|
</label>
|
|
`;
|
|
});
|
|
questionHTML += '</div>';
|
|
} else {
|
|
questionHTML += '<p class="text-muted small">No choices defined</p>';
|
|
}
|
|
break;
|
|
}
|
|
|
|
questionHTML += '</div>';
|
|
return questionHTML;
|
|
}
|
|
|
|
escapeHtml(text) {
|
|
const div = document.createElement('div');
|
|
div.textContent = text;
|
|
return div.innerHTML;
|
|
}
|
|
}
|
|
|
|
// Initialize
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
window.surveyPreview = new SurveyPreview();
|
|
});
|