jQuery 表單處理
表單是網頁與使用者互動的重要介面。jQuery 提供了便捷的方法來處理表單元素、驗證輸入、序列化資料,以及處理表單提交。
取得表單元素
表單選擇器回顧
$(':input'); // 所有表單元素(input、select、textarea、button)
$(':text'); // type="text" 的 input
$(':password'); // type="password" 的 input
$(':checkbox'); // type="checkbox" 的 input
$(':radio'); // type="radio" 的 input
$(':submit'); // 送出按鈕
$(':reset'); // 重設按鈕
$(':file'); // 檔案上傳
$(':hidden'); // 隱藏的元素
// 狀態選擇器
$(':enabled'); // 啟用的元素
$(':disabled'); // 停用的元素
$(':checked'); // 已勾選的 checkbox/radio
$(':selected'); // 已選取的 option
$(':focus'); // 獲得焦點的元素
表單值操作
val() 基本用法
// 取得值
var username = $('#username').val();
var message = $('#message').val(); // textarea
// 設定值
$('#username').val('John');
$('#message').val('Hello World');
處理 Checkbox
// 取得已勾選的值(單一)
var isChecked = $('#agree').prop('checked');
// 取得已勾選的值(多個)
var hobbies = [];
$('input[name="hobby"]:checked').each(function () {
hobbies.push($(this).val());
});
// 或使用 map()
var hobbies = $('input[name="hobby"]:checked')
.map(function () {
return $(this).val();
})
.get();
// 設定勾選狀態
$('#agree').prop('checked', true);
// 設定多個 checkbox
$('input[name="hobby"]').val(['reading', 'sports']); // 勾選這些值
處理 Radio
// 取得選取的值
var gender = $('input[name="gender"]:checked').val();
// 設定選取
$('input[name="gender"][value="male"]').prop('checked', true);
// 或
$('input[name="gender"]').val(['male']);
處理 Select
// 取得選取值(單選)
var country = $('#country').val();
// 取得選取的 option 文字
var countryText = $('#country option:selected').text();
// 設定選取值
$('#country').val('tw');
// 多選 select
var languages = $('#languages').val(); // 返回陣列
$('#languages').val(['js', 'python']); // 設定多個值
// 取得所有 option
$('#country option').each(function () {
console.log($(this).val(), $(this).text());
});
// 動態新增 option
$('#country').append('<option value="us">美國</option>');
// 或使用物件
$('#country').append(
$('<option>', {
value: 'us',
text: '美國',
})
);
表單驗證
基本驗證
$('form').on('submit', function (e) {
var isValid = true;
// 檢查必填欄位
$(this)
.find('[required]')
.each(function () {
var $field = $(this);
if (!$field.val().trim()) {
$field.addClass('error');
isValid = false;
} else {
$field.removeClass('error');
}
});
if (!isValid) {
e.preventDefault();
alert('請填寫所有必填欄位');
}
});
即時驗證
// 輸入時即時驗證
$('#email').on('input', function () {
var email = $(this).val();
var isValid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
$(this)
.toggleClass('valid', isValid)
.toggleClass('invalid', !isValid && email.length > 0);
});
// 失去焦點時驗證
$('#username').on('blur', function () {
var username = $(this).val();
if (username.length < 3) {
$(this).addClass('error');
$('#username-error').text('使用者名稱至少需要 3 個字元');
} else {
$(this).removeClass('error');
$('#username-error').text('');
}
});
完整驗證範例
var validators = {
required: function (value) {
return value.trim().length > 0;
},
email: function (value) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
},
minLength: function (value, length) {
return value.length >= length;
},
maxLength: function (value, length) {
return value.length <= length;
},
pattern: function (value, pattern) {
return new RegExp(pattern).test(value);
},
};
function validateField($field) {
var value = $field.val();
var rules = $field.data('validate').split('|');
var errors = [];
rules.forEach(function (rule) {
var parts = rule.split(':');
var ruleName = parts[0];
var ruleParam = parts[1];
if (validators[ruleName] && !validators[ruleName](value, ruleParam)) {
errors.push(ruleName);
}
});
return errors;
}
// 使用
// <input type="text" data-validate="required|minLength:3|maxLength:20">
$('form').on('submit', function (e) {
var hasErrors = false;
$(this)
.find('[data-validate]')
.each(function () {
var errors = validateField($(this));
if (errors.length > 0) {
$(this).addClass('error');
hasErrors = true;
} else {
$(this).removeClass('error');
}
});
if (hasErrors) {
e.preventDefault();
}
});
表單事件
常用事件
// 獲得焦點
$('input').on('focus', function () {
$(this).parent().addClass('focused');
});
// 失去焦點
$('input').on('blur', function () {
$(this).parent().removeClass('focused');
});
// 值改變(失去焦點時觸發)
$('select').on('change', function () {
console.log('選擇了:', $(this).val());
});
// 即時輸入
$('input').on('input', function () {
console.log('目前輸入:', $(this).val());
});
表單送出
$('form').on('submit', function (e) {
e.preventDefault();
var $form = $(this);
var formData = $form.serialize();
$.post($form.attr('action'), formData)
.done(function (response) {
alert('送出成功!');
})
.fail(function (error) {
alert('送出失敗');
});
});
防止重複送出
$('form').on('submit', function (e) {
e.preventDefault();
var $form = $(this);
var $submitBtn = $form.find(':submit');
// 檢查是否正在送出
if ($form.data('submitting')) {
return;
}
// 標記為正在送出
$form.data('submitting', true);
$submitBtn.prop('disabled', true).text('處理中...');
$.post($form.attr('action'), $form.serialize())
.done(function (response) {
alert('成功!');
})
.fail(function () {
alert('失敗');
})
.always(function () {
$form.data('submitting', false);
$submitBtn.prop('disabled', false).text('送出');
});
});
表單序列化
serialize()
將表單資料轉為 URL 編碼的查詢字串:
var queryString = $('form').serialize();
// name=John&email=john%40example.com&message=Hello
// 用於 Ajax
$.post('/api/submit', $('form').serialize());
serializeArray()
將表單資料轉為物件陣列:
var dataArray = $('form').serializeArray();
// [
// {name: "name", value: "John"},
// {name: "email", value: "john@example.com"},
// {name: "message", value: "Hello"}
// ]
轉為物件
function serializeObject($form) {
var obj = {};
$.each($form.serializeArray(), function (i, field) {
if (obj[field.name] !== undefined) {
// 處理同名欄位(如多選 checkbox)
if (!Array.isArray(obj[field.name])) {
obj[field.name] = [obj[field.name]];
}
obj[field.name].push(field.value);
} else {
obj[field.name] = field.value;
}
});
return obj;
}
// 使用
var formData = serializeObject($('form'));
// {name: "John", email: "john@example.com", hobbies: ["reading", "sports"]}
動態表單
動態新增欄位
var fieldIndex = 0;
$('#addField').on('click', function () {
fieldIndex++;
var $field = $('<div>', { class: 'field-group' })
.append(
$('<input>', {
type: 'text',
name: 'items[' + fieldIndex + ']',
placeholder: '項目 ' + fieldIndex,
})
)
.append(
$('<button>', {
type: 'button',
class: 'remove-field',
text: '移除',
})
);
$('#fields').append($field);
});
// 使用事件委派處理移除
$('#fields').on('click', '.remove-field', function () {
$(this).parent('.field-group').remove();
});
表單重設
// 原生 reset
$('form')[0].reset();
// jQuery 方式
$('form').trigger('reset');
// 自訂重設
function resetForm($form) {
// 重設輸入欄位
$form.find('input:text, input:password, textarea').val('');
// 取消勾選
$form.find('input:checkbox, input:radio').prop('checked', false);
// 重設 select 到第一個選項
$form.find('select').prop('selectedIndex', 0);
// 移除錯誤樣式
$form.find('.error').removeClass('error');
$form.find('.error-message').text('');
}
表單狀態管理
// 停用/啟用表單
function disableForm($form, disabled) {
$form.find(':input').prop('disabled', disabled);
}
// 啟用
disableForm($('form'), false);
// 停用
disableForm($('form'), true);
// 檢查表單是否有變更
var originalData = $('form').serialize();
$('form').on('change input', function () {
var currentData = $(this).serialize();
var hasChanges = currentData !== originalData;
$('#saveBtn').prop('disabled', !hasChanges);
});
// 離開頁面前提醒
$(window).on('beforeunload', function () {
if ($('form').serialize() !== originalData) {
return '您有未儲存的變更,確定要離開嗎?';
}
});
實用範例
即時字數統計
$('textarea').on('input', function () {
var maxLength = $(this).attr('maxlength') || 500;
var currentLength = $(this).val().length;
var remaining = maxLength - currentLength;
$('#charCount').text(currentLength + ' / ' + maxLength);
if (remaining < 20) {
$('#charCount').addClass('warning');
} else {
$('#charCount').removeClass('warning');
}
});
密碼強度檢查
$('#password').on('input', function () {
var password = $(this).val();
var strength = 0;
if (password.length >= 8) strength++;
if (password.length >= 12) strength++;
if (/[a-z]/.test(password)) strength++;
if (/[A-Z]/.test(password)) strength++;
if (/[0-9]/.test(password)) strength++;
if (/[^a-zA-Z0-9]/.test(password)) strength++;
var $meter = $('#strengthMeter');
$meter.removeClass('weak medium strong');
if (strength <= 2) {
$meter.addClass('weak').text('弱');
} else if (strength <= 4) {
$meter.addClass('medium').text('中');
} else {
$meter.addClass('strong').text('強');
}
});
全選/取消全選
$('#selectAll').on('change', function () {
var isChecked = $(this).prop('checked');
$('input[name="items"]').prop('checked', isChecked);
updateSelectAllState();
});
$('input[name="items"]').on('change', function () {
updateSelectAllState();
});
function updateSelectAllState() {
var total = $('input[name="items"]').length;
var checked = $('input[name="items"]:checked').length;
$('#selectAll').prop({
checked: checked === total,
indeterminate: checked > 0 && checked < total,
});
$('#selectedCount').text('已選擇 ' + checked + ' 項');
}
表單步驟導覽
var currentStep = 1;
var totalSteps = 3;
function showStep(step) {
$('.step').hide();
$('#step' + step).show();
// 更新進度指示器
$('.progress-step').removeClass('active completed');
for (var i = 1; i <= totalSteps; i++) {
if (i < step) {
$('.progress-step:nth-child(' + i + ')').addClass('completed');
} else if (i === step) {
$('.progress-step:nth-child(' + i + ')').addClass('active');
}
}
// 更新按鈕狀態
$('#prevBtn').prop('disabled', step === 1);
$('#nextBtn').text(step === totalSteps ? '送出' : '下一步');
}
$('#nextBtn').on('click', function () {
if (validateStep(currentStep)) {
if (currentStep < totalSteps) {
currentStep++;
showStep(currentStep);
} else {
// 送出表單
$('form').trigger('submit');
}
}
});
$('#prevBtn').on('click', function () {
if (currentStep > 1) {
currentStep--;
showStep(currentStep);
}
});
function validateStep(step) {
var $step = $('#step' + step);
var isValid = true;
$step.find('[required]').each(function () {
if (!$(this).val().trim()) {
$(this).addClass('error');
isValid = false;
} else {
$(this).removeClass('error');
}
});
return isValid;
}