Form Builder について
Form Builderは、入力可能なフォームやアンケート、インタラクティブな文書を作成するスキルです。PDFやHTML出力に対応し、入力検証、条件分岐、回答処理を扱います。
主な機能
- 01入力可能なPDFフォームの作成
- 02HTMLフォームの生成
- 03条件分岐付きアンケートの作成
- 04フィールドの入力検証
- 05送信データの処理とエクスポート
- 06進捗表示付き複数ページフォーム
活用シーン
- 01登録フォームや申請フォームの作成
- 02フィードバック調査や質問票の作成
- 03フォーム回答の収集と処理
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ eddiebe147/ claude-settings --skill form-builderインストール後、Claude Code で/form-builderを入力して読み込まれたか確認します。
詳細
フォームビルダー
フォームビルダーのスキルは、入力可能なフォーム、アンケート、質問票、インタラクティブなドキュメントの作成を自動化します。登録フォーム、フィードバックアンケート、申請フォーム、受付フォーム、評価ツールなど、さまざまな種類のフォームに対応しています。検証ルール、条件分岐、データ収集機能に加え、複数の出力形式(入力可能なフィールドを備えたPDF、HTMLフォーム、Google Formsとの連携)をサポートしています。
あらゆるデータ収集ニーズに対応する、適切なフィールドタイプ、検証、スタイル、送信処理を備えたプロフェッショナルなフォームを生成します。
主なワークフロー
ワークフロー 1:入力可能なPDFフォームの作成
目的: インタラクティブな入力可能フィールドを備えたPDFを生成します
手順:
- フォームの構成とフィールドを定義する
- フィールドタイプを設定する(テキスト、チェックボックス、ラジオボタン、ドロップダウン、日付)
- 各フィールドに検証ルールを追加する
- フィールドのプロパティを設定する(必須、maxLengthなど)
- スタイルとレイアウトを適用する
- 説明とヘルプテキストを追加する
- 入力可能フィールドを備えたPDFを生成する
- フォームの機能をテストする
実装:
const { PDFDocument, PDFTextField, PDFCheckBox, PDFDropdown, rgb } = require('pdf-lib');
const fs = require('fs');
async function createFillablePDFForm(formData, outputPath) {
const pdfDoc = await PDFDocument.create();
const page = pdfDoc.addPage([612, 792]); // Letter size
const { width, height } = page.getSize();
const fontSize = 11;
const labelFontSize = 10;
// Add title
page.drawText(formData.title, {
x: 50,
y: height - 50,
size: 18,
color: rgb(0.2, 0.2, 0.2)
});
// Add instructions
if (formData.instructions) {
page.drawText(formData.instructions, {
x: 50,
y: height - 80,
size: 10,
color: rgb(0.4, 0.4, 0.4),
maxWidth: width - 100
});
}
let currentY = height - 120;
// Add form fields
for (const field of formData.fields) {
// Draw field label
page.drawText(field.label + (field.required ? ' *' : ''), {
x: 50,
y: currentY,
size: labelFontSize,
color: rgb(0, 0, 0)
});
currentY -= 25;
// Create field based on type
switch (field.type) {
case 'text':
case 'email':
case 'number':
const textField = pdfDoc.getForm().createTextField(field.name);
textField.addToPage(page, {
x: 50,
y: currentY,
width: 300,
height: 20,
borderColor: rgb(0.5, 0.5, 0.5),
borderWidth: 1
});
if (field.placeholder) {
textField.setText(field.placeholder);
}
if (field.maxLength) {
textField.setMaxLength(field.maxLength);
}
break;
case 'textarea':
const textArea = pdfDoc.getForm().createTextField(field.name);
textArea.addToPage(page, {
x: 50,
y: currentY - 40,
width: 500,
height: 60,
borderColor: rgb(0.5, 0.5, 0.5),
borderWidth: 1
});
textArea.enableMultiline();
currentY -= 40;
break;
case 'checkbox':
field.options.forEach((option, index) => {
const checkbox = pdfDoc.getForm().createCheckBox(`${field.name}_${index}`);
checkbox.addToPage(page, {
x: 50,
y: currentY - (index * 20),
width: 12,
height: 12,
borderColor: rgb(0.5, 0.5, 0.5),
borderWidth: 1
});
page.drawText(option, {
x: 70,
y: currentY - (index * 20) + 2,
size: 10,
color: rgb(0, 0, 0)
});
});
currentY -= field.options.length * 20;
break;
case 'radio':
const radioGroup = pdfDoc.getForm().createRadioGroup(field.name);
field.options.forEach((option, index) => {
radioGroup.addOptionToPage(option, page, {
x: 50,
y: currentY - (index * 20),
width: 12,
height: 12,
borderColor: rgb(0.5, 0.5, 0.5),
borderWidth: 1
});
page.drawText(option, {
x: 70,
y: currentY - (index * 20) + 2,
size: 10,
color: rgb(0, 0, 0)
});
});
currentY -= field.options.length * 20;
break;
case 'dropdown':
const dropdown = pdfDoc.getForm().createDropdown(field.name);
dropdown.addOptions(field.options);
dropdown.addToPage(page, {
x: 50,
y: currentY,
width: 200,
height: 20,
borderColor: rgb(0.5, 0.5, 0.5),
borderWidth: 1
});
break;
case 'date':
const dateField = pdfDoc.getForm().createTextField(field.name);
dateField.addToPage(page, {
x: 50,
y: currentY,
width: 150,
height: 20,
borderColor: rgb(0.5, 0.5, 0.5),
borderWidth: 1
});
dateField.setText('MM/DD/YYYY');
break;
}
// Add help text if provided
if (field.helpText) {
page.drawText(field.helpText, {
x: 370,
y: currentY + 5,
size: 8,
color: rgb(0.6, 0.6, 0.6),
maxWidth: 200
});
}
currentY -= 40;
// Add new page if running out of space
if (currentY < 100) {
const newPage = pdfDoc.addPage([612, 792]);
currentY = height - 50;
}
}
// Add submit button placeholder (text)
page.drawText('Please save and email this completed form to: ' + formData.submitEmail, {
x: 50,
y: 50,
size: 9,
color: rgb(0.3, 0.3, 0.3)
});
const pdfBytes = await pdfDoc.save();
fs.writeFileSync(outputPath, pdfBytes);
return outputPath;
}
ワークフロー 2:HTMLフォームの生成
目的: クライアント側の検証機能を備えたインタラクティブなHTMLフォームを作成します
手順:
- フォームの構成とフィールドを定義する
- HTMLマークアップを生成する
- プロフェッショナルな外観になるようCSSスタイルを追加する
- JavaScriptによる検証を実装する
- フォームの送信を処理する
- アクセシビリティ機能を追加する(ARIAラベル、キーボード操作)
- モバイルデバイスに対応するレスポンシブデザインにする
- 各ブラウザーでテストする
実装:
function generateHTMLForm(formData, outputPath) {
const html = `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>${formData.title}</title>
<style>
* { box-sizing: border-box; }
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
max-width: 700px;
margin: 40px auto;
padding: 20px;
background: #f5f5f5;
}
.form-container {
background: white;
padding: 40px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
h1 {
color: #333;
margin-bottom: 10px;
}
.instructions {
color: #666;
margin-bottom: 30px;
font-size: 14px;
}
.form-group {
margin-bottom: 25px;
}
label {
display: block;
margin-bottom: 8px;
font-weight: 600;
color: #333;
}
.required { color: #e74c3c; }
input[type="text"],
input[type="email"],
input[type="number"],
input[type="tel"],
input[type="date"],
textarea,
select {
width: 100%;
padding: 10px 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
transition: border-color 0.3s;
}
input:focus,
textarea:focus,
select:focus {
outline: none;
border-color: #3498db;
}
textarea {
resize: vertical;
min-height: 100px;
}
.help-text {
font-size: 12px;
color: #888;
margin-top: 5px;
}
.checkbox-group,
.radio-group {
margin-top: 8px;
}
.checkbox-item,
.radio-item {
margin-bottom: 8px;
}
.checkbox-item input,
.radio-item input {
width: auto;
margin-right: 8px;
}
.error {
color: #e74c3c;
font-size: 12px;
margin-top: 5px;
display: none;
}
.error.visible {
display: block;
}
input.invalid,
textarea.invalid,
select.invalid {
border-color: #e74c3c;
}
button[type="submit"] {
background: #3498db;
color: white;
padding: 12px 30px;
border: none;
border-radius: 4px;
font-size: 16px;
cursor: pointer;
transition: background 0.3s;
}
button[type="submit"]:hover {
background: #2980b9;
}
button[type="submit"]:disabled {
background: #bdc3c7;
cursor: not-allowed;
}
.success-message {
display: none;
background: #27ae60;
color: white;
padding: 15px;
border-radius: 4px;
margin-bottom: 20px;
}
@media (max-width: 600px) {
body { padding: 10px; }
.form-container { padding: 20px; }
}
</style>
</head>
<body>
<div class="form-container">
<div class="success-message" id="successMessage">
Form submitted successfully! Thank you.
</div>
<h1>${formData.title}</h1>
${formData.instructions ? `<p class="instructions">${formData.instructions}</p>` : ''}
<form id="mainForm" action="${formData.submitUrl || '#'}" method="${formData.method || 'POST'}">
${formData.fields.map(field => generateFieldHTML(field)).join('\n')}
<div class="form-group">
<button type="submit" id="submitBtn">Submit</button>
</div>
</form>
</div>
<script>
${generateValidationScript(formData)}
</script>
</body>
</html>
`;
fs.writeFileSync(outputPath, html, 'utf8');
return outputPath;
}
function generateFieldHTML(field) {
const requiredAttr = field.required ? 'required' : '';
const requiredLabel = field.required ? '<span class="required">*</span>' : '';
switch (field.type) {
case 'text':
case 'email':
case 'number':
case 'tel':
case 'date':
return `
<div class="form-group">
<label for="${field.name}">${field.label} ${requiredLabel}</label>
<input
type="${field.type}"
id="${field.name}"
name="${field.name}"
placeholder="${field.placeholder || ''}"
${requiredAttr}
${field.maxLength ? `maxlength="${field.maxLength}"` : ''}
${field.pattern ? `pattern="${field.pattern}"` : ''}
>
${field.helpText ? `<div class="help-text">${field.helpText}</div>` : ''}
<div class="error" id="${field.name}-error">${field.errorMessage || 'This field is required'}</div>
</div>
`;
case 'textarea':
return `
<div class="form-group">
<label for="${field.name}">${field.label} ${requiredLabel}</label>
<textarea
id="${field.name}"
name="${field.name}"
placeholder="${field.placeholder || ''}"
${requiredAttr}
${field.maxLength ? `maxlength="${field.maxLength}"` : ''}
></textarea>
${field.helpText ? `<div class="help-text">${field.helpText}</div>` : ''}
<div class="error" id="${field.name}-error">This field is required</div>
</div>
`;
case 'select':
return `
<div class="form-group">
<label for="${field.name}">${field.label} ${requiredLabel}</label>
<select id="${field.name}" name="${field.name}" ${requiredAttr}>
<option value="">-- Select --</option>
${field.options.map(opt => `<option value="${opt}">${opt}</option>`).join('\n')}
</select>
${field.helpText ? `<div class="help-text">${field.helpText}</div>` : ''}
<div class="error" id="${field.name}-error">Please select an option</div>
</div>
`;
case 'checkbox':
return `
<div class="form-group">
<label>${field.label} ${requiredLabel}</label>
<div class="checkbox-group">
${field.options.map((opt, idx) => `
<div class="checkbox-item">
<input type="checkbox" id="${field.name}_${idx}" name="${field.name}" value="${opt}">
<label for="${field.name}_${idx}">${opt}</label>
</div>
`).join('\n')}
</div>
${field.helpText ? `<div class="help-text">${field.helpText}</div>` : ''}
</div>
`;
case 'radio':
return `
<div class="form-group">
<label>${field.label} ${requiredLabel}</label>
<div class="radio-group">
${field.options.map((opt, idx) => `
<div class="radio-item">
<input type="radio" id="${field.name}_${idx}" name="${field.name}" value="${opt}" ${requiredAttr}>
<label for="${field.name}_${idx}">${opt}</label>
</div>
`).join('\n')}
</div>
${field.helpText ? `<div class="help-text">${field.helpText}</div>` : ''}
<div class="error" id="${field.name}-error">Please select an option</div>
</div>
`;
default:
return '';
}
}
function generateValidationScript(formData) {
return `
const form = document.getElementById('mainForm');
const submitBtn = document.getElementById('submitBtn');
const successMessage = document.getElementById('successMessage');
// Validation rules
const validationRules = {
email: /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/,
phone: /^[\\d\\s\\-\\(\\)]+$/
};
// Real-time validation
form.addEventListener('input', (e) => {
validateField(e.target);
});
// Form submission
form.addEventListener('submit', async (e) => {
e.preventDefault();
// Validate all fields
let isValid = true;
const fields = form.querySelectorAll('input, textarea, select');
fields.forEach(field => {
if (!validateField(field)) {
isValid = false;
}
});
if (!isValid) {
return;
}
// Disable submit button
submitBtn.disabled = true;
submitBtn.textContent = 'Submitting...';
try {
// Collect form data
const formData = new FormData(form);
const data = Object.fromEntries(formData);
// Submit to server
${formData.submitUrl ? `
const response = await fetch('${formData.submitUrl}', {
method: '${formData.method || 'POST'}',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
if (response.ok) {
successMessage.style.display = 'block';
form.reset();
} else {
alert('Submission failed. Please try again.');
}
` : `
// No submit URL - just show success
console.log('Form data:', data);
successMessage.style.display = 'block';
form.reset();
`}
} catch (error) {
alert('An error occurred. Please try again.');
console.error(error);
} finally {
submitBtn.disabled = false;
submitBtn.textContent = 'Submit';
}
});
function validateField(field) {
const errorEl = document.getElementById(field.name + '-error');
if (!errorEl) return true;
let isValid = true;
let errorMessage = '';
// Required check
if (field.hasAttribute('required') && !field.value.trim()) {
isValid = false;
errorMessage = 'This field is required';
}
// Email validation
if (field.type === 'email' && field.value && !validationRules.email.test(field.value)) {
isValid = false;
errorMessage = 'Please enter a valid email address';
}
// Pattern validation
if (field.hasAttribute('pattern') && field.value) {
const pattern = new RegExp(field.getAttribute('pattern'));
if (!pattern.test(field.value)) {
isValid = false;
errorMessage = 'Please enter a valid value';
}
}
// Update UI
if (isValid) {
field.classList.remove('invalid');
errorEl.classList.remove('visible');
} else {
field.classList.add('invalid');
errorEl.textContent = errorMessage;
errorEl.classList.add('visible');
}
return isValid;
}
`;
}
ワークフロー 3:アンケート/質問票ジェネレーター
目的: 複数の質問タイプとロジックを備えた包括的なアンケートを作成します
手順:
- アンケートの構成とセクションを定義する
- さまざまな質問タイプを追加する(複数選択、評価、テキストなど)
- 条件分岐ロジックを実装する(スキップパターン)
- 検証ルールを追加する
- 回答の選択肢と尺度を設定する
- 結果の収集と分析方法を設計する
- 希望する形式でアンケートを生成する
- 回答の追跡を設定する
実装:
async function generateSurvey(surveyData, outputPath) {
const survey = {
title: surveyData.title,
description: surveyData.description,
sections: surveyData.sections.map(section => ({
title: section.title,
questions: section.questions.map((q, idx) => ({
id: `q${section.id}_${idx + 1}`,
type: q.type,
text: q.text,
required: q.required !== false,
options: q.options,
validation: q.validation,
conditionalLogic: q.conditionalLogic
}))
})),
settings: {
allowAnonymous: surveyData.allowAnonymous !== false,
showProgress: surveyData.showProgress !== false,
randomizeQuestions: surveyData.randomizeQuestions || false,
allowMultipleSubmissions: surveyData.allowMultipleSubmissions || false
}
};
// Generate HTML survey
const html = generateSurveyHTML(survey);
fs.writeFileSync(outputPath, html, 'utf8');
return {
surveyPath: outputPath,
questionCount: survey.sections.reduce((sum, s) => sum + s.questions.length, 0),
settings: survey.settings
};
}
function generateSurveyHTML(survey) {
return `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>${survey.title}</title>
<style>
/* Survey-specific styles */
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
background: #f9f9f9;
}
.survey-container {
background: white;
padding: 40px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.progress-bar {
height: 8px;
background: #e0e0e0;
border-radius: 4px;
margin-bottom: 30px;
}
.progress-fill {
height: 100%;
background: #4CAF50;
border-radius: 4px;
transition: width 0.3s;
}
.section {
margin-bottom: 40px;
}
.section-title {
font-size: 20px;
font-weight: bold;
margin-bottom: 20px;
color: #333;
}
.question {
margin-bottom: 30px;
padding: 20px;
background: #f5f5f5;
border-radius: 6px;
}
.question-text {
font-size: 16px;
margin-bottom: 15px;
color: #333;
}
.required-indicator {
color: #e74c3c;
}
.rating-scale {
display: flex;
gap: 10px;
justify-content: center;
margin: 20px 0;
}
.rating-option {
width: 50px;
height: 50px;
border: 2px solid #ddd;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.3s;
}
.rating-option:hover {
border-color: #4CAF50;
background: #f0f0f0;
}
.rating-option.selected {
background: #4CAF50;
color: white;
border-color: #4CAF50;
}
button.submit-survey {
background: #4CAF50;
color: white;
padding: 15px 40px;
border: none;
border-radius: 6px;
font-size: 16px;
cursor: pointer;
width: 100%;
margin-top: 20px;
}
button.submit-survey:hover {
background: #45a049;
}
</style>
</head>
<body>
<div class="survey-container">
<h1>${survey.title}</h1>
<p>${survey.description}</p>
${survey.settings.showProgress ? `
<div class="progress-bar">
<div class="progress-fill" id="progressFill" style="width: 0%"></div>
</div>
` : ''}
<form id="surveyForm">
${survey.sections.map(section => `
<div class="section">
<h2 class="section-title">${section.title}</h2>
${section.questions.map(q => generateQuestionHTML(q)).join('\n')}
</div>
`).join('\n')}
<button type="submit" class="submit-survey">Submit Survey</button>
</form>
</div>
<script>
// Survey logic and submission
${generateSurveyScript(survey)}
</script>
</body>
</html>
`;
}
function generateQuestionHTML(question) {
const requiredMark = question.required ? '<span class="required-indicator">*</span>' : '';
switch (question.type) {
case 'multipleChoice':
return `
<div class="question" data-question-id="${question.id}">
<div class="question-text">${question.text} ${requiredMark}</div>
${question.options.map((opt, idx) => `
<div>
<input type="radio" id="${question.id}_${idx}" name="${question.id}" value="${opt}" ${question.required ? 'required' : ''}>
<label for="${question.id}_${idx}">${opt}</label>
</div>
`).join('\n')}
</div>
`;
case 'rating':
const scale = question.scale || 5;
return `
<div class="question" data-question-id="${question.id}">
<div class="question-text">${question.text} ${requiredMark}</div>
<div class="rating-scale">
${Array.from({ length: scale }, (_, i) => i + 1).map(num => `
<div class="rating-option" data-value="${num}" onclick="selectRating('${question.id}', ${num})">
${num}
</div>
`).join('\n')}
</div>
<input type="hidden" name="${question.id}" id="${question.id}" ${question.required ? 'required' : ''}>
</div>
`;
case 'text':
return `
<div class="question" data-question-id="${question.id}">
<div class="question-text">${question.text} ${requiredMark}</div>
<textarea name="${question.id}" rows="4" style="width: 100%; padding: 10px;" ${question.required ? 'required' : ''}></textarea>
</div>
`;
case 'checkbox':
return `
<div class="question" data-question-id="${question.id}">
<div class="question-text">${question.text} ${requiredMark}</div>
${question.options.map((opt, idx) => `
<div>
<input type="checkbox" id="${question.id}_${idx}" name="${question.id}" value="${opt}">
<label for="${question.id}_${idx}">${opt}</label>
</div>
`).join('\n')}
</div>
`;
default:
return '';
}
}
function generateSurveyScript(survey) {
return `
function selectRating(questionId, value) {
// Remove previous selection
const container = document.querySelector(\`[data-question-id="\${questionId}"]\`);
container.querySelectorAll('.rating-option').forEach(opt => {
opt.classList.remove('selected');
});
// Add new selection
event.target.classList.add('selected');
document.getElementById(questionId).value = value;
}
// Form submission
document.getElementById('surveyForm').addEventListener('submit', async (e) => {
e.preventDefault();
const formData = new FormData(e.target);
const responses = Object.fromEntries(formData);
console.log('Survey responses:', responses);
// Submit to server or save locally
alert('Thank you for completing the survey!');
e.target.reset();
});
// Progress tracking
${survey.settings.showProgress ? `
const questions = document.querySelectorAll('.question');
const progressFill = document.getElementById('progressFill');
document.addEventListener('input', updateProgress);
function updateProgress() {
let answered = 0;
questions.forEach(q => {
const inputs = q.querySelectorAll('input, textarea');
const hasAnswer = Array.from(inputs).some(input => {
if (input.type === 'radio' || input.type === 'checkbox') {
return input.checked;
}
return input.value.trim() !== '';
});
if (hasAnswer) answered++;
});
const progress = (answered / questions.length) * 100;
progressFill.style.width = progress + '%';
}
` : ''}
`;
}
ワークフロー 4:フォームデータの処理
目的: フォームの送信を処理し、収集したデータを処理します
手順:
- 送信エンドポイントを設定する
- 送信されたデータを検証する
- 入力を処理してサニタイズする
- データベースまたはスプレッドシートにデータを保存する
- 確認メールを送信する
- 概要レポートを生成する
- 分析用にデータをエクスポートする
ワークフロー 5:進捗表示付き複数ページフォーム
目的: 進捗を追跡しながら、長いフォームを複数ページに分割して作成します
手順:
- フォームを論理的なセクションに分ける
- ページ間のナビゲーションを作成する
- 進捗バーを実装する
- ページ間で進捗を保存する
- 各ステップで検証を追加する
- 前のページに戻って編集できるようにする
- 最後にすべてのデータを送信する
クイックリファレンス
| アクション | コマンド/トリガー |
|---|---|
| 入力可能なPDFフォーム | "create fillable PDF form" |
| HTMLフォーム | "generate HTML form with [fields]" |
| アンケート | "build survey about [topic]" |
| お問い合わせフォーム | "create contact form" |
| 登録フォーム | "make registration form" |
| フィードバックフォーム | "generate feedback survey" |
| 複数ステップのフォーム | "create multi-page form" |
ベストプラクティス
- わかりやすいラベル: すべてのフィールドに、内容が明確で簡潔なラベルを使用する
- 論理的なグループ分け: 関連するフィールドをまとめる
- 必須フィールド: アスタリスクを付けて必須フィールドを明確に示す
- ヘルプテキスト: 複雑なフィールドにはガイダンスを用意する
- 検証: クライアント側とサーバー側の両方で検証を実装する
- エラーメッセージ: 明確で役に立つエラーメッセージを表示する
- アクセシビリティ: ARIAラベルとキーボード操作を取り入れる
- モバイル対応: すべてのデバイスでフォームが機能することを確認する
- 進捗インジケーター: 長いフォームでは進捗を表示する
- 進捗の保存: ユーザーが進捗を保存し、後から再開できるようにする
- 確認: 送信後に成功メッセージを表示する
- セキュリティ: CSRF対策を使用し、入力をサニタイズする
よくあるパターン
お問い合わせフォーム:
{
fields: [
{ name: 'name', type: 'text', label: 'Full Name', required: true },
{ name: 'email', type: 'email', label: 'Email', required: true },
{ name: 'phone', type: 'tel', label: 'Phone Number' },
{ name: 'subject', type: 'text', label: 'Subject', required: true },
{ name: 'message', type: 'textarea', label: 'Message', required: true }
]
}
イベント登録:
{
fields: [
{ name: 'attendeeName', type: 'text', required: true },
{ name: 'email', type: 'email', required: true },
{ name: 'ticketType', type: 'select', options: ['General', 'VIP'], required: true },
{ name: 'dietaryRestrictions', type: 'checkbox', options: ['Vegetarian', 'Vegan', 'Gluten-Free'] },
{ name: 'specialRequests', type: 'textarea' }
]
}
依存関係
必要なパッケージをインストールします:
npm install pdf-lib # For PDF forms
npm install express # For form submission handling
npm install validator # For input validation
npm install nodemailer # For email confirmations
エラー処理
- 検証エラー: フィールドごとの具体的なエラーを表示します
- ネットワークエラー: 送信の失敗を適切に処理します
- データ損失: データの損失を防ぐため、進行状況を自動保存します
- ブラウザーの互換性: さまざまなブラウザーでテストします
高度な機能
条件付きロジック:
conditionalLogic: {
showIf: { field: 'employment', value: 'employed' },
hideIf: { field: 'student', value: 'yes' }
}
ファイルアップロード:
{
name: 'resume',
type: 'file',
accept: '.pdf,.doc,.docx',
maxSize: 5242880 // 5MB
}
自動保存:
setInterval(() => {
const formData = new FormData(form);
localStorage.setItem('formDraft', JSON.stringify(Object.fromEntries(formData)));
}, 30000);
コメント
Markdown 対応