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;
}