JavaScript 动态生成交互式问卷表单:选项与问题管理
在现代Web应用开发中,动态生成表单是一项非常常见且重要的功能。无论是问卷调查、在线测试还是用户反馈系统,我们经常需要根据数据源灵活地构建表单结构,并让用户能够实时添加、删除或修改问题及选项。本文将深入探讨如何使用JavaScript实现一个可动态管理的交互式问卷表单,涵盖从数据结构设计到UI交互的完整流程。
一、核心数据结构设计
一个健壮的动态表单系统离不开清晰的数据结构。我们可以将问卷抽象为以下核心实体:
问卷 (Survey):包含标题和一组问题。
问题 (Question):包含问题标题、问题类型(如单选、多选、文本)以及选项列表。
选项 (Option):对于选择型问题,每个选项有一个唯一标识和显示文本。
以下是一个典型的JavaScript对象表示:
const surveyData = {
title: '用户满意度调查',
questions: [
{
id: 1,
title: '您对我们的服务满意吗?',
type: 'radio', // radio/checkbox/text
options: [
{ id: 101, text: '非常满意' },
{ id: 102, text: '满意' },
{ id: 103, text: '一般' },
{ id: 104, text: '不满意' }
]
},
{
id: 2,
title: '您最常使用哪些功能?',
type: 'checkbox',
options: [
{ id: 201, text: '搜索功能' },
{ id: 202, text: '数据分析' },
{ id: 203, text: '报表导出' }
]
},
{
id: 3,
title: '请留下您的建议:',
type: 'text',
options: [] // 文本类型无需选项
}
]
};这种结构便于我们在运行时增删改查,同时也方便与后端API进行数据交换。
二、动态渲染表单
接下来,我们需要编写一个函数,根据上述数据动态生成对应的HTML表单元素。核心思路是遍历 questions 数组,针对不同 type 创建不同的表单控件。
function renderSurveyForm(survey) {
const formContainer = document.getElementById('surveyForm');
formContainer.innerHTML = ''; // 清空容器
// 问卷标题
const titleEl = document.createElement('h3');
titleEl.textContent = survey.title;
formContainer.appendChild(titleEl);
// 遍历问题
survey.questions.forEach((q, index) => {
const questionDiv = document.createElement('div');
questionDiv.className = 'question-item';
questionDiv.dataset.questionId = q.id;
// 问题标题
const label = document.createElement('label');
label.textContent = `${index + 1}. ${q.title}`;
questionDiv.appendChild(label);
// 根据类型创建控件
if (q.type === 'radio') {
// 单选:使用input[type="radio"]
q.options.forEach(opt => {
const radioLabel = document.createElement('label>');
const radioInput = document.createElement('input');
radioInput.type = 'radio';
radioInput.name = `question_${q.id}`;
radioInput.value = opt.id;
radioLabel.appendChild(radioInput);
radioLabel.appendChild(document.createTextNode(opt.text));
questionDiv.appendChild(radioLabel);
questionDiv.appendChild(document.createElement('br'));
});
} else if (q.type === 'checkbox') {
// 多选:使用input[type="checkbox"]
q.options.forEach(opt => {
const checkLabel = document.createElement('label>');
const checkInput = document.createElement('input');
checkInput.type = 'checkbox';
checkInput.name = `question_${q.id}`;
checkInput.value = opt.id;
checkLabel.appendChild(checkInput);
checkLabel.appendChild(document.createTextNode(opt.text));
questionDiv.appendChild(checkLabel);
questionDiv.appendChild(document.createElement('br'));
});
} else if (q.type === 'text') {
// 文本输入
const textarea = document.createElement('textarea');
textarea.name = `question_${q.id}`;
textarea.rows = 4;
textarea.cols = 50;
textarea.placeholder = '请输入您的回答...';
questionDiv.appendChild(textarea);
}
formContainer.appendChild(questionDiv);
formContainer.appendChild(document.createElement('hr'));
});
// 添加提交按钮
const submitBtn = document.createElement('button');
submitBtn.textContent = '提交问卷';
submitBtn.onclick = handleSubmit;
formContainer.appendChild(submitBtn);
}注意:上面的代码示例中,为了演示,使用了 appendChild(document.createTextNode(...)) 来避免使用复杂的字符串拼接。在实际项目中,也可以使用模板字符串或构建DOM片段。
三、添加问题的动态管理功能
一个真正的交互式问卷系统,应该允许用户(如管理员)在界面上实时添加、编辑或删除问题。下面我们分别实现添加问题和添加选项的功能。
3.1 添加新问题
function addQuestion(survey, type) {
const newId = Date.now(); // 简单生成唯一ID
const newQuestion = {
id: newId,
title: '请输入问题标题',
type: type,
options: type === 'text' ? [] : [
{ id: Date.now() + 1, text: '选项1' }
]
};
survey.questions.push(newQuestion);
renderSurveyForm(survey); // 重新渲染
}调用此函数时,可以通过参数传递问题类型,例如 addQuestion(surveyData, 'radio') 会新增一个名为“请输入问题标题”的单选题,并包含一个默认选项“选项1”。
3.2 删除问题
function removeQuestion(survey, questionId) {
const index = survey.questions.findIndex(q => q.id === questionId);
if (index !== -1) {
survey.questions.splice(index, 1);
renderSurveyForm(survey);
}
}在实际界面中,你可以在每个问题的标题旁添加一个“删除”按钮,绑定 removeQuestion 函数并传入对应ID。
3.3 为问题添加/删除选项
function addOptionToQuestion(survey, questionId) {
const question = survey.questions.find(q => q.id === questionId);
if (question && question.type !== 'text') {
const newOptId = Date.now();
question.options.push({ id: newOptId, text: '新选项' });
renderSurveyForm(survey);
}
}
function removeOptionFromQuestion(survey, questionId, optionId) {
const question = survey.questions.find(q => q.id === questionId);
if (question && question.options) {
const optIndex = question.options.findIndex(opt => opt.id === optionId);
if (optIndex !== -1) {
question.options.splice(optIndex, 1);
renderSurveyForm(survey);
}
}
}四、收集用户输入并提交
表单动态渲染完成后,我们需要能够收集用户填写的数据并转化为结构化对象。下面是一个 handleSubmit 函数示例:
function handleSubmit() {
const formContainer = document.getElementById('surveyForm');
const questionDivs = formContainer.querySelectorAll('.question-item');
const answers = [];
questionDivs.forEach(div => {
const qId = parseInt(div.dataset.questionId);
const question = surveyData.questions.find(q => q.id === qId);
if (!question) return;
if (question.type === 'radio') {
const selected = div.querySelector('input[type="radio"]:checked');
if (selected) {
answers.push({ questionId: qId, answer: [parseInt(selected.value)] });
}
} else if (question.type === 'checkbox') {
const checked = div.querySelectorAll('input[type="checkbox"]:checked');
const values = Array.from(checked).map(cb => parseInt(cb.value));
answers.push({ questionId: qId, answer: values });
} else if (question.type === 'text') {
const textarea = div.querySelector('textarea');
const value = textarea ? textarea.value : '';
answers.push({ questionId: qId, answer: value });
}
});
// 将收集到的数据发送到服务器(示例)
console.log('提交的答案:', JSON.stringify(answers, null, 2));
// 实际项目中可调用 fetch 或 axios 发送 POST 请求
// fetch('https://www.ipipp.com/api/survey/submit', { method: 'POST', body: JSON.stringify(answers) });
}注意:上述代码中,我们假设每个问题div具有类名为 question-item,并且在生成时设置了 data-question-id 属性。你可以在 renderSurveyForm 中添加相应类名。
四、优化与边界情况
动态表单系统在实际应用中需要处理的细节非常多。以下是一些常见的优化建议:
性能优化:如果问卷非常长,频繁重建DOM会影响性能。可以使用文档片段(DocumentFragment)来批量构建,或采用虚拟DOM方案。
数据持久化:将
surveyData存储在本地存储(localStorage)或通过API从服务器加载,确保用户刷新页面不丢失数据。表单验证:在提交前检查必填问题是否已回答、选项是否符合规则(如最少选择数量)等。
可访问性:为所有的表单控件添加
aria-label或for属性,确保屏幕阅读器能正确解读。
此外,还要考虑当选项为空时,应禁用相关操作按钮。例如,如果问题类型是 text,则不应显示“添加选项”按钮。
五、总结
通过本文的示例,我们展示了如何使用纯JavaScript动态生成一个具备选项与问题管理功能的交互式问卷表单。核心思路包括:设计清晰的数据结构、根据数据渲染UI、提供增删改查的函数接口、以及收集用户输入。这种动态表单方案非常适合需要灵活配置的场景,开发者可以根据实际需求扩展更多功能,比如拖拽排序、条件显示逻辑等。
掌握这些基础技术后,你可以轻松地将这套模式应用到各种动态表单项目中,从而大幅提高开发效率。