Chuyển đến nội dung chính (Skip to content)
Mục lục bài viết
Web Development Essentials

Web Development Essentials 032.4: Biểu mẫu HTML

Web Development Essentials 032.4: Biểu mẫu HTML

Tạo form có label, input và button rõ vai trò; phân biệt GET, POST, multipart, validation và trường được gửi; kiểm chứng bằng dữ liệu và tệp DEMO cục bộ.

Bấm gửi chưa cho biết server nhận được gì

Một biểu mẫu yêu cầu hỗ trợ hiển thị đủ thông tin nhưng server không nhận trường “Mã thiết bị”. Trường đó có id để gắn nhãn nhưng thiếu name. Ở một trường khác, checkbox chưa chọn nên không xuất hiện trong dữ liệu gửi. Muốn điều tra, bạn cần phân biệt giao diện, giá trị hiện tại và tập dữ liệu thực sự được tạo cho request.

Bài thuộc 032.4, tham chiếu PDF 030-100-vi, trang in 119–142, tương ứng trang vật lý 127–150. Bạn cần kiến thức HTML, URL và HTTP. Sau bài, bạn nên viết form có nhãn, chọn GET/POST theo ý nghĩa, nhận diện các loại input và kiểm chứng dữ liệu gửi trong môi trường riêng.

Nội dung được biên soạn mới, đối chiếu ngày 23/10/2026. Lab dùng dữ liệu DEMO, Chromium 151.0.7922.34 và server fixture Node.js 22.22.2 chỉ nghe loopback. Đây là bài về biểu mẫu và cách gửi dữ liệu, chưa triển khai hệ thống ticket, đăng nhập hoặc gửi thông báo thật.

Form cần đích, phương thức và cách mã hóa

Phần tử form nhóm các điều khiển phục vụ việc nhập và gửi dữ liệu. action là URL xử lý; nó không bắt buộc trỏ tới một tệp có phần mở rộng PHP hay JavaScript. method xác định phương thức mà browser dùng trong quá trình submit tương ứng.

GET phù hợp với truy vấn không yêu cầu thay đổi trạng thái, ví dụ tìm hướng dẫn bằng dữ liệu không nhạy cảm. Dữ liệu form được đưa vào URL query. POST gửi dữ liệu để server xử lý, thường qua body. Chọn phương thức theo hành vi và hợp đồng của endpoint, không đơn giản theo số trường hay độ dài văn bản.

Với POST, enctype mặc định là application/x-www-form-urlencoded; khi gửi file dùng multipart/form-data phù hợp. Browser tạo boundary cho multipart. POST không tự mã hóa dữ liệu, còn multipart không chứng minh file an toàn. Ứng dụng thật vẫn cần HTTPS, xác thực, phân quyền và kiểm tra phía server.

Id, name và value có ba vai trò

Id định danh phần tử trong tài liệu, phải duy nhất. Name là tên trường khi tạo dữ liệu form. Value là giá trị tương ứng theo loại điều khiển. Tài liệu input giải thích những trường hợp không được gửi, như điều khiển bị disabled hoặc checkbox chưa chọn.

Nhiều điều khiển có thể dùng cùng name có chủ đích. Radio trong cùng nhóm dùng chung name, nhưng mỗi input vẫn có id riêng. Checkbox cũng có thể lặp name để gửi nhiều giá trị; phía server cần đọc danh sách thay vì mặc định chỉ một chuỗi. Không áp dụng yêu cầu “name phải duy nhất” cho mọi form.

Readonly khác disabled. Một trường readonly phù hợp vẫn có thể được gửi, còn disabled thường bị loại khỏi dữ liệu form. Cả hai đều không phải cơ chế chứng minh giá trị đáng tin. Người kiểm soát client có thể sửa request, nên server phải quyết định trường nào được phép thay đổi.

Label, chỉ dẫn và nhóm trường

W3C WAI hướng dẫn gắn nhãn điều khiển bằng label liên kết rõ ràng. Khi dùng for, giá trị phải khớp id của input. Nhãn làm rõ mục đích và mở rộng vùng có thể bấm để tương tác, thay vì chỉ đặt một paragraph gần trường rồi mong công cụ đoán được quan hệ.

Placeholder là ví dụ tạm hiện khi trường rỗng, không thay nhãn và không được gửi như giá trị nếu người dùng chưa nhập. Đặt hướng dẫn về định dạng hoặc trường bắt buộc ở nơi người dùng còn đọc được sau khi bắt đầu gõ. Không chỉ dùng màu để báo một trường là bắt buộc.

Fieldset và legend giúp gom các lựa chọn liên quan, chẳng hạn mức ảnh hưởng của một yêu cầu. Nhóm rõ ràng có ích cho cả người nhìn và người dùng công nghệ hỗ trợ. Bài kiểm tra nhãn trong browser, chưa nhận rằng mọi cách đọc hoặc thao tác trên mọi công cụ đã được thử.

Kiểm tra nhãn, validation, dữ liệu được tạo và phần xử lý phía server riêng nhau.
Hình 1. Kiểm tra nhãn, validation, dữ liệu được tạo và phần xử lý phía server riêng nhau. Mở hình để xem chi tiết.

Chọn loại input theo dữ liệu cần thu

Các loại trong mục tiêu có những vai trò sau. Giao diện cụ thể vẫn phụ thuộc browser, hệ điều hành và thiết bị:

Type Mục đích và giới hạn cần nhớ
text Văn bản một dòng; phù hợp mã định danh không mang ý nghĩa số học
email Hỗ trợ kiểm tra dạng địa chỉ; không chứng minh hộp thư tồn tại
password Che cách hiển thị ký tự, không mã hóa giá trị gửi
number Số có ý nghĩa tính toán, có thể đặt min, max, step
date Ngày theo mô hình không có múi giờ; giao diện có thể theo locale
range Giá trị trong khoảng với điều khiển dạng thanh trượt hoặc tương tự
radio Một lựa chọn trong nhóm cùng name
checkbox Lựa chọn độc lập hoặc nhiều giá trị trong một nhóm
hidden Giá trị không hiển thị như trường nhập, vẫn có thể xem và sửa
file Chọn file để gửi theo phạm vi được phép

Đừng dùng number cho mọi chuỗi có chữ số, như mã thiết bị cần giữ số 0 đầu. Textarea là phần tử riêng có thẻ đóng, dùng cho văn bản nhiều dòng; rows và cols không phải giới hạn an toàn cho độ dài. Select dùng các option; selected xác định lựa chọn mặc định theo ngữ cảnh.

Không giả định mọi input có giá trị mặc định rỗng hoặc mọi browser cung cấp bộ chọn giống hệt nhau. Với ngày, giá trị máy đọc và cách hiển thị có thể khác. Khi viết kiểm tra, xác định giá trị cần gửi thay vì so một ảnh giao diện cố định.

Button, reset và trường ẩn

Button nên có type rõ. submit yêu cầu gửi form; button không tự submit; reset đưa các điều khiển về giá trị ban đầu. Reset không có nghĩa xóa sạch mọi dữ liệu: một input có mặc định DEMO sẽ trở lại mặc định đó. Trong form thật, nút reset dễ làm mất phần người dùng vừa nhập nên cần cân nhắc trước khi thêm.

hidden không phải một type hợp lệ để tạo button ẩn. Có input type="hidden", và có thuộc tính hidden với mục đích hiển thị khác. Tên giống nhau trong tài liệu không làm chúng thay thế nhau.

Một hidden field như mã phiên bản form chỉ là dữ liệu client gửi. Nó không tự chứng minh người gửi có quyền hoặc thay cho cơ chế bảo vệ CSRF. Nếu ứng dụng dùng token được server phát, server vẫn phải xác minh nó theo thiết kế và quản lý vòng đời phù hợp.

Validation hỗ trợ người nhập, server giữ ranh giới

Kiểm tra form phía client giúp phản hồi sớm khi thiếu trường required hoặc sai dạng email. Nó có thể bị bỏ qua bằng request được tạo theo cách khác. Server phải kiểm tra dữ liệu, quyền và quy tắc nghiệp vụ độc lập trước khi lưu hoặc thực hiện tác động.

Accept của input file là gợi ý cho việc chọn file, không phải bộ xác minh an toàn. Phía server cần giới hạn kích thước, định dạng và cách lưu theo thiết kế; đuôi .txt hoặc MIME client khai báo không tự là bằng chứng nội dung đúng.

Autocomplete có các token theo ý nghĩa dữ liệu. Không đưa quy tắc chung “mọi trường nhạy cảm phải off” thành bảo đảm; browser hoặc password manager còn có hành vi riêng. Bài này không thử tự điền và không yêu cầu nhập mật khẩu thật.

Một form có thể gửi nhiều giá trị cùng name, bỏ qua trường disabled và gửi cả trường readonly.
Hình 2. Một form có thể gửi nhiều giá trị cùng name, bỏ qua trường disabled và gửi cả trường readonly. Mở hình để xem chi tiết.

Lab: form DEMO và server phản hồi dữ liệu

Điều kiện lab: người quản trị chuẩn bị server chỉ nghe 127.0.0.1, phục vụ trang dưới đây, nhận GET /search và POST /tickets, rồi trả các trường đã nhận dưới dạng JSON. Fixture chỉ giữ dữ liệu trong bộ nhớ, không ghi database hay gửi email. Chỉ dùng tệp demo.txt tự tạo chứa DEMO-only, không chọn file công việc. Phần viết ứng dụng server sẽ được học ở nhánh Node.js.

Lưu forms.html bằng UTF-8:

<!doctype html>
<html lang="vi">
<head><meta charset="utf-8"><title>Biểu mẫu | Lab LPI</title></head>
<body>
<h1>Yêu cầu DEMO</h1>
<form action="/search" method="get" id="search">
  <label for="q">Từ khóa DEMO</label>
  <input type="search" id="q" name="q">
  <button type="submit">Tìm DEMO</button>
</form>
<form action="/tickets" method="post" enctype="multipart/form-data" id="ticket">
  <p><label for="subject">Chủ đề (bắt buộc)</label>
  <input type="text" id="subject" name="subject" required value="DEMO ban đầu"></p>
  <p><label for="email">Email giả (bắt buộc)</label>
  <input type="email" id="email" name="email" required></p>
  <fieldset><legend>Mức ảnh hưởng DEMO</legend>
    <input type="radio" id="normal" name="impact" value="normal" checked>
    <label for="normal">Thông thường</label>
    <input type="radio" id="high" name="impact" value="high">
    <label for="high">Cao</label>
  </fieldset>
  <fieldset><legend>Kênh DEMO, không gửi thông báo thật</legend>
    <input type="checkbox" id="mail" name="channel" value="mail" checked>
    <label for="mail">Mail DEMO</label>
    <input type="checkbox" id="chat" name="channel" value="chat">
    <label for="chat">Chat DEMO</label>
  </fieldset>
  <p><label for="notes">Mô tả giả</label>
  <textarea id="notes" name="notes" rows="3" cols="30">Chỉ có dữ liệu DEMO</textarea></p>
  <input type="hidden" name="formVersion" value="demo-v1">
  <p><label for="casecode">Mã DEMO</label>
  <input id="casecode" name="casecode" value="DEMO-49" readonly></p>
  <input name="debug" value="không gửi" disabled>
  <p><label for="attachment">Tệp DEMO tự tạo</label>
  <input type="file" id="attachment" name="attachment" accept="text/plain"></p>
  <button type="button" id="probe">Không gửi form</button>
  <button type="reset">Trả về mặc định</button>
  <button type="submit">Gửi DEMO</button>
</form>
</body>
</html>

Trước khi gửi, để email rỗng rồi bấm “Gửi DEMO”. Mong đợi browser không tạo POST và chỉ dẫn trường thiếu. Thử chuỗi không có dạng email, rồi sửa thành demo@example.invalid. Đây là địa chỉ dành cho ví dụ, không phải nơi nhận thư.

Sửa chủ đề, chọn mức Cao, đánh dấu thêm Chat DEMO và chọn tệp tự tạo. Bấm “Không gửi form”, mong đợi chưa có POST. Bấm reset và xem chủ đề trở lại “DEMO ban đầu”, radio trở về mặc định; các thay đổi chưa gửi mất đi. Sau đó điền lại và gửi đúng một lần vào fixture.

Kiểm tra tập dữ liệu thay vì đoán từ giao diện

Dùng công cụ mạng hoặc JSON fixture trả về để đối chiếu. Subject và email mang giá trị mới; impact chỉ có high; channel có cả mail và chat. Casecode readonly vẫn được gửi, debug disabled không được gửi. Tệp nhận ở server phải có đúng byte DEMO, không chỉ đúng tên.

FormData là API hữu ích để quan sát dữ liệu theo cơ chế form. Khi tên có nhiều giá trị, cần đọc đủ danh sách. Quan sát này không chứng minh ứng dụng server đã kiểm tra quyền hay chống xử lý trùng yêu cầu.

Kiểm chứng Chromium trên fixture Node.js 22.22.2 đã đạt GET query DEMO, nhãn đưa focus đúng, required/email chặn submit không hợp lệ, nút button không gửi, reset về mặc định và multipart gửi đúng trường cùng byte tệp. Harness cũng sửa hidden formVersion sang demo-v2 và server nhận giá trị đã sửa, minh họa nó không phải bí mật đáng tin.

Lab chưa kiểm tra server validation hoàn chỉnh, lưu trữ, CSRF, antivirus, TLS, trình đọc màn hình hoặc mọi type input trong bảng. Bạn đạt bài khi dự đoán được trường nào được gửi và giải thích vì sao browser validation chưa đủ. Giữ output DEMO và sang phần CSS để cải thiện trình bày mà vẫn giữ cấu trúc form.

Bài liên quan trong bộ LPI

Tìm hiểu thêm