Nối mã của bạn vào biểu mẫu
Ba sự kiện DOM mà một biểu mẫu phát ra trên trang đã xuất bản — nghe được, và chặn được — để mã bạn tự viết thêm được luật của riêng cửa hàng.
Có những luật mà không một ô chọn nào trong trình sửa đặt hộ bạn được, vì chúng là luật của cửa hàng bạn, không phải luật về hình dạng một câu trả lời: chỉ nhận đặt bàn trước ít nhất hai tiếng, chặn mười tên miền email dùng một lần mà bạn đã chán, hỏi kho trước khi nhận đơn. Trang này nói cách viết chúng.
Chỗ để mã là phần tử Mã tùy chỉnh (en: Custom code) — kéo từ bảng phần tử lên bất kỳ đâu trên trang, hoặc đặt trong một phần chung. Biểu mẫu ở đây là Biểu mẫu; nếu thứ bạn cần chỉ là "ô này phải là số" thì nhóm Định dạng trong đó làm xong rồi, đừng viết mã cho nó.
Ba sự kiện
Trên trang đã xuất bản, mỗi biểu mẫu phát ra ba sự kiện DOM:
| Sự kiện | Phát ra khi |
|---|---|
wb:form:submit |
Mọi luật của nền tảng đã qua, và trước khi gửi đi |
wb:form:success |
Máy chủ đã nhận |
wb:form:error |
Máy chủ từ chối |
Đây là CustomEvent thường, nổi bọt lên từ chính khối biểu mẫu, nên bạn nghe ở
document là đủ — không cần biến toàn cục, không cần chờ bundle của chúng tôi
tải xong, không phụ thuộc thứ tự script:
document.addEventListener('wb:form:success', (e) => {
gtag('event', 'generate_lead', { form: e.detail.formId });
});
Chúng tôi cố ý không làm một window.WB. Người viết đoạn mã trên là người
chúng tôi chưa từng gặp, nên API phải là thứ họ đã biết sẵn. Đây cũng là quy ước
mà bộ lọc sản phẩm (wb:filter) đã dùng.
e.detail.formId cho biết biểu mẫu nào vừa phát — nó là giá trị trong thuộc
tính data-wb-form của khối, và nó đáng để kiểm nếu một trang có hơn một biểu
mẫu.
Chặn một lần gửi
wb:form:submit hủy được. Đó là nửa mà Định dạng không lo nổi:
document.addEventListener('wb:form:submit', (e) => {
const { values, reject } = e.detail;
if (values.email.endsWith('@mailinator.com')) {
reject('email', 'Bạn dùng giúp mình một email thật nhé.');
}
});
values được đánh khoá bằng ID trường — chính cái ô đầu tiên trên thẻ
Nâng cao của mỗi trường, và cũng là tên cột trong file xuất. Đặt ID trường
cho tử tế rồi hẵng viết mã: đổi nó về sau là đổi cả khoá ở đây.
reject(<ID trường>, <lời nhắn>) làm hai việc: hủy lần gửi, và hiện lời
nhắn ngay cạnh ô đó, bằng đúng cách mà một lỗi của nền tảng vẫn hiện. Điều
này quan trọng hơn vẻ ngoài của nó. Gọi e.preventDefault() một mình cũng chặn
được, nhưng nó chặn trong im lặng — khách bấm nút và không có gì xảy ra,
đúng kiểu hỏng tệ nhất mà một trang có thể mắc. preventDefault() vẫn dùng
được, nhưng khi đó sự im lặng là lựa chọn của bạn chứ không phải tai nạn của
API. Đưa một ID trường không tồn tại vào reject thì lời nhắn hiện ở khung
thông báo chung của biểu mẫu, chứ không bị nuốt mất.
Trường tải tệp không nằm trong values — giá trị của một ô chọn tệp là một
đường dẫn giả (C:\fakepath\cv.pdf ở mọi trình duyệt), lưu nó xuống là lưu một
lời nói dối. Thay vào đó e.detail.files là danh sách tên các trường đang
mang tệp, theo thứ tự khách chọn; một trường ba tệp xuất hiện ba lần. Đủ để
biết có tệp hay không và có bao nhiêu, không đủ để đọc nội dung tệp.
Bạn nghe và chặn được, nhưng không sửa được câu trả lời
values là một bản chụp. Ghi vào nó không đổi được thứ sẽ gửi đi, và đó là cố
ý: một phản hồi lưu trong hệ thống mà không phải thứ khách đã gõ là một vụ
khiếu nại không ai dựng lại được, và khách chưa từng đồng ý cho việc đó. Cần đổi
một giá trị thì đổi ở chỗ khách nhìn thấy được — sửa trường, sửa tùy chọn, hoặc
xử lý sau khi phản hồi đã về tới bạn.
Và không có gì ở đây là kiểm tra thật
Mã này chạy trên trang của một người lạ, trên máy của họ, trong một trình duyệt họ mở được devtools. Máy chủ vẫn quyết lại toàn bộ luật của chính nó với mọi lần gửi. Luật bạn viết ở đây là lịch sự với một khách thật thà — hệt như luật của chính nền tảng chạy ở phía trình duyệt. Thứ thật sự chặn được người cố tình nằm ở phía bạn, sau khi phản hồi đã tới.
Sau khi gửi
wb:form:success phát ra ngay khi máy chủ nhận, trước mọi việc chuyển
trang. Biểu mẫu có thể được cài để chuyển sang trang cảm ơn, và nếu sự kiện
phát sau đó thì nửa số lần đo chuyển đổi của bạn sẽ mất vì trang đã đi. detail
mang formId và values.
wb:form:error phát ra cho mọi lần gửi không thành công, mang theo
status và fields — lý do của từng trường, đúng bằng những từ máy chủ dùng
(required, bad_format, too_many_files…).
document.addEventListener('wb:form:error', (e) => {
const { status, fields, code } = e.detail;
console.warn('form refused', status, code ?? fields);
});
Hai trường hợp không nêu trường nào, và cả hai đều vẫn phát sự kiện — nếu không thì bạn đếm thiếu:
- Từ chối ở mức cả biểu mẫu.
fieldsrỗng vàcodemang mã của máy chủ — hiện cócart_emptyvàcart_line_unavailabletrên biểu mẫu đặt hàng lấy nguyên giỏ. Không có gì khách gõ là sai, nên không có ô nào để đánh dấu. - Yêu cầu chưa từng tới được máy chủ — khách mất mạng giữa chừng.
statuslà 0,fieldsrỗng, không cócode. Đó là hình dạng duy nhấtfetchcó khi không hề có câu trả lời nào để báo.
Một mảnh dán được
<script>
document.addEventListener('wb:form:submit', (e) => {
const { formId, values, reject } = e.detail;
if (formId !== 'dat-ban') return;
const when = new Date(values.thoi_gian);
if (when.getTime() - Date.now() < 2 * 60 * 60 * 1000) {
reject('thoi_gian', 'Bạn đặt trước ít nhất 2 tiếng giúp mình nhé.');
}
});
</script>
Dán vào một khối Mã tùy chỉnh, đặt trên chính trang có biểu mẫu hoặc trong một phần chung nếu biểu mẫu xuất hiện ở nhiều trang. Nó chỉ chạy trên trang đã xuất bản, không chạy trên canvas của trình sửa — nên hãy thử bằng Xem trước (en: Preview) chứ đừng đợi thấy gì trong lúc dựng.
Cập nhật 5/9/2026