Khi triển khai gửi biểu mẫu bằng jQuery AJAX, một lỗi phổ biến nhưng dễ bị bỏ sót là việc trình duyệt tự động gửi lại form theo phương thức GET mặc định thay vì thực thi yêu cầu AJAX. Điều này dẫn đến hai hiện tượng đồng thời: (1) hàm success hoặc error không được kích hoạt, và (2) dữ liệu xuất hiện trong thanh địa chỉ dưới dạng query string — dù mã JavaScript rõ ràng khai báo type: "POST".
Nguồn gốc vấn đề nằm ở hành vi mặc định của thẻ <form>: khi người dùng nhấn nút submit (kể cả nút <button> bên trong form), trình duyệt sẽ gửi yêu cầu HTTP theo thuộc tính method của form — nếu không khai báo, giá trị mặc định là GET. Trong trường hợp không ngăn chặn hành vi này, cả yêu cầu AJAX và yêu cầu form đều được thực thi song song — nhưng do yêu cầu form xảy ra nhanh hơn và gây reload trang, nên luồng AJAX bị gián đoạn hoặc bị hủy ngầm.
Dưới đây là phiên bản HTML và JavaScript đã được cải tiến để đảm bảo chỉ có yêu cầu AJAX được thực thi:
<form id="essaySubmissionForm" onsubmit="return false;">
<label for="titleInput">Tiêu đề</label>
<input type="text" class="form-control" name="title" id="titleInput" placeholder="Nhập tiêu đề">
<label for="contentArea">Nội dung</label>
<textarea class="form-control" name="content" id="contentArea" rows="15" style="resize: none;"></textarea>
<button type="button" id="submitEssayBtn">Gửi bài viết</button>
</form>
Lưu ý các điểm quan trọng trong cấu trúc trên:
- Thuộc tính
onsubmit="return false;"ngăn chặn hoàn toàn hành vi gửi form mặc định. - Nút gửi được đặt
type="button"thay vìtype="submit"để tránh kích hoạt form một cách gián tiếp. - Không sử dụng thuộc tính
actionhaymethod— vì chúng không cần thiết khi form chỉ đóng vai trò làm container cho dữ liệu.
Phần xử lý phía client được viết lại với jQuery như sau:
$(document).ready(function() {
$('#submitEssayBtn').on('click', function(e) {
e.preventDefault(); // Dự phòng thêm nếu onsubmit không đủ
const formData = $('#essaySubmissionForm').serialize();
$.ajax({
url: '/api/essays',
method: 'POST',
contentType: 'application/x-www-form-urlencoded',
data: formData,
dataType: 'json',
timeout: 10000
})
.done(function(response) {
console.log('Thành công:', response);
alert(`Đã lưu: ${response.message || 'Bài viết mới'}`);
})
.fail(function(xhr, status, error) {
console.error('Lỗi:', status, error);
if (xhr.responseJSON && xhr.responseJSON.error) {
alert('Lỗi hệ thống: ' + xhr.responseJSON.error);
} else {
alert('Gửi thất bại. Vui lòng kiểm tra kết nối.');
}
});
});
});
Các cải tiến trong đoạn mã trên bao gồm:
- Sử dụng
.done()và.fail()thay vìsuccess/error— phù hợp với phiên bản jQuery ≥ 1.8 và hỗ trợ xử lý promise rõ ràng hơn. - Thêm
e.preventDefault()như lớp bảo vệ thứ hai. - Chỉ định rõ
contentTypeđể đảm bảo server nhận đúng định dạng dữ liệu. - Thiết lập
timeoutnhằm tránh treo giao diện khi request chậm. - Xử lý chi tiết lỗi dựa trên phản hồi JSON từ server (nếu có), tăng khả năng chẩn đoán.
Một lựa chọn hiện đại hơn là dùng fetch thay vì jQuery AJAX — đặc biệt khi không cần hỗ trợ IE:
document.getElementById('submitEssayBtn').addEventListener('click', async function() {
const form = document.getElementById('essaySubmissionForm');
const params = new URLSearchParams(new FormData(form));
try {
const res = await fetch('/api/essays', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: params
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const result = await res.json();
console.log('Thành công:', result);
alert(result.message || 'Gửi bài viết thành công');
} catch (err) {
console.error('Lỗi gửi biểu mẫu:', err);
alert('Có lỗi xảy ra khi gửi dữ liệu.');
}
});