Việc xác thực biểu mẫu HTML chưa được tận dụng đúng mức
(expressionstatement.com)- Biểu mẫu HTML vốn đã có các tính năng xác thực native như
required,type="email",pattern,maxlength,setCustomValidity, nhưng trên thực tế mức độ sử dụng vẫn thấp so với phạm vi tính năng setCustomValidity, công cụ mạnh nhất, có thể xử lý logic xác thực tùy ý và các trường hợp phức tạp, nhưng vì chỉ được cung cấp dưới dạng phương thức DOM nên không thật sự phù hợp với các component khai báo- Trong môi trường như React, để xác thực giá trị ban đầu cho đúng,
useRef,useLayoutEffect,onChangedễ bị đan xen với nhau, và cùng một logic xác thực dễ bị lặp lại ở lần render ban đầu và trong handler thay đổi - Nếu có một abstraction dạng thuộc tính như
custom-validity, các phép xác thực có phụ thuộc giữa trạng thái và input, chẳng hạn kiểm tra bất đồng bộ tên người dùng có bị trùng hay không hoặc xác nhận mật khẩu, có thể được biểu diễn tại một nơi - Mức độ áp dụng thấp của xác thực biểu mẫu native gần với vấn đề tính dễ dùng của API hơn là thiếu tính năng; nếu một API khai báo được đưa vào đặc tả HTML, khả năng tận dụng sẽ lớn hơn
Những gì xác thực biểu mẫu HTML đã cung cấp
- Cách đơn giản nhất để chặn input rỗng là thêm thuộc tính
required - Có thể đặt ràng buộc cho input theo ba cách chính
- Dùng kiểu input như
type="email",type="number",type="url" - Dùng thuộc tính ràng buộc như
pattern,maxlength - Dùng phương thức DOM
setCustomValiditycủa input
- Dùng kiểu input như
setCustomValiditylà phương tiện mạnh nhất vì có thể xử lý logic xác thực tùy ý và các trường hợp phức tạp- Hai cách đầu có thể được khai báo bằng thuộc tính HTML, nhưng
setCustomValiditykhác ở chỗ cần gọi phương thức - Tài liệu tham khảo về khác biệt giữa thuộc tính DOM và property được liên kết tại Attributes vs Properties
Điểm khiến setCustomValidity trở nên bất tiện
setCustomValiditykhông có thuộc tính HTML tương ứng, mà chỉ được lộ ra dưới dạng phương thức- Khi truyền vào một chuỗi, input chuyển sang trạng thái invalid, và trình duyệt hiển thị chuỗi đó làm lý do xác thực thất bại
- Khi truyền vào chuỗi rỗng, input sẽ ở trạng thái valid miễn là không có ràng buộc nào khác
- Để tự triển khai hành vi như
required, cần gọisetCustomValiditymỗi khi giá trị thay đổi - Tuy nhiên, ban đầu input ở trạng thái valid, nên nếu nhấn nút submit ngay sau khi reset component, việc gửi biểu mẫu có thể được cho qua
- Lý do là dù giá trị rỗng, mã xác thực chưa được chạy trước khi sự kiện thay đổi xảy ra
- Để xử lý cả giá trị ban đầu, cùng một phép xác thực cũng phải được chạy tại thời điểm component mount
Boilerplate tăng lên trong component khai báo
- Khi xử lý cả xác thực giá trị ban đầu, mã nhanh chóng trở nên lặp lại và bất tiện
- Đặc biệt phát sinh ba vấn đề
- Logic xác thực bị lặp lại trong handler
onChangevà giai đoạn render ban đầu - Mã xác thực ban đầu nằm tách khỏi phần tử input, làm giảm tính kết dính và tạo rủi ro chỉ sửa một phía
- Tổ hợp
useRef,useLayoutEffect,onChangetrở nên quá mức khi số lượng input tăng lên, và càng gây rối hơn khi chỉ một số input dùngcustomValidity
- Logic xác thực bị lặp lại trong handler
- Sự phức tạp này nổi bật khi xử lý một API mệnh lệnh thuần túy trong component khai báo
CustomValiditykhông có thuộc tính input nào để đặt giá trị theo kiểu khai báo như các thuộc tính xác thực native- Nếu API rườm rà, mức độ áp dụng có thể thấp dù tính năng mạnh; đây là nguyên nhân cốt lõi khiến xác thực biểu mẫu native chưa được tận dụng nhiều
Mảnh ghép còn thiếu: thuộc tính custom-validity
- Hình thái cần thiết là một thuộc tính khai báo như
custom-validity, có thể chỉ định trực tiếp trên input - Trong framework khai báo, thuộc tính này cho phép đặt trạng thái xác thực gần phần tử input
- Hiện tại
HTML Specchưa cócustom-validitythực sự - Có thể mô phỏng hành vi này bằng một triển khai ở userland cho mục đích demo
- Một triển khai hoàn thiện hơn được cung cấp làm ví dụ component dùng cho production
Xác thực bất đồng bộ và phụ thuộc giữa các input
- Việc xác thực trong ứng dụng thực tế có thể phức tạp hơn kiểm tra cục bộ
- Input tên người dùng phải kiểm tra với server xem tên đó đã được dùng hay chưa, và trong lúc đang request, biểu mẫu không nên ở trạng thái valid
- Ví dụ dùng
requiredđể chặn input rỗng, đồng thời dùngcustomValidityđể đưa input về trạng thái invalid tùy theo trạng thái loading và kết quả phản hồi - Phần triển khai gồm hai phần
- Quản lý trạng thái request kiểm tra tính duy nhất của tên người dùng bằng
useQuerycủareact-query - Dùng component
<Input />tùy chỉnh có thể nhận propcustomValidity
- Quản lý trạng thái request kiểm tra tính duy nhất của tên người dùng bằng
- Cách này có thể biểu diễn toàn bộ luồng xác thực bất đồng bộ, gồm trạng thái loading, lỗi và thành công, trong một thuộc tính duy nhất
- Ví dụ bổ sung là một biểu mẫu yêu cầu nhập lại mật khẩu đã nhập, xử lý xác thực các trường input phụ thuộc lẫn nhau
Kết luận
setCustomValiditylà một công cụ mạnh có thể xử lý nhiều nhu cầu xác thực khác nhau- Điều quyết định mức độ sử dụng thực tế không chỉ là sự tồn tại của tính năng, mà còn là API giúp dùng tính năng đó dễ dàng đến đâu
- Một abstraction khai báo như
custom-validitygiúp sử dụng xác thực biểu mẫu native một cách tự nhiên hơn - Có thể kỳ vọng một ngày nào đó tính năng như vậy sẽ được đưa vào đặc tả HTML dưới dạng native
1 bình luận
Ý kiến trên Hacker News
Lần cuối tôi kiểm tra, các trình duyệt web hiện nay vẫn không thể tùy biến giao diện của thông báo kiểm tra hợp lệ HTML tích hợp sẵn https://stackoverflow.com/questions/5328883/how-do-i-style-t...
Nếu Chrome và Firefox ít nhất còn tuân theo hướng dẫn UI của nền tảng OS để trông như thông báo do hệ thống hiển thị, kiểu tooltip
title="", thì đã đỡ hơn; nhưng Chrome lại dùng biểu tượng vàng/cam, nền trắng chữ đen và bong bóng thoại bo góc cố định, nên xung đột nghiêm trọng với thẩm mỹ của dự án hiện tạiChrome trước đây từng cho phép tùy biến thông báo kiểm tra hợp lệ bằng bộ chọn giả phần tử có tiền tố nhà cung cấp, nhưng sau khi gỡ tính năng đó thì không mang trở lại nữa. Nó bị đưa vào danh sách những điều bực mình lặt vặt kiểu “cho tôi combobox HTML native đi”, “tại sao đến giờ vẫn là cái hộp Ctrl+click khó dùng chứ không phải danh sách checkbox”
validitynative của input rồi render theo ý muốn thật ra khá dễVấn đề thực sự là khó đăng ký theo dõi chính xác các thay đổi trạng thái hợp lệ này. Có các sự kiện về tính hợp lệ, nhưng chúng không phải lúc nào cũng phát sinh. Nếu thay đổi trạng thái form bằng chương trình như gọi
form.reset()hoặcinput.value = '...', các sự kiện này sẽ không được kích hoạtTôi nghĩ đây là một chủ đề hay để điều tra riêng và đề xuất cho nền tảng web
Thứ lớn nhất, dễ triển khai nhất nhưng vẫn ít được dùng là sử dụng các giá trị thuộc tính
typecụ thể nhưemail,number,urlTrên mobile, nó có thể bật bàn phím tối ưu và cải thiện trải nghiệm người dùng đáng kể
type=number, thay vào đó dùngtype=text inputmode=numeric. Nó không kèm các nút mũi tên mà đa số người dùng không cần khi nhập số, và trên iOS bàn phím cũng tốt hơntypedate. Mỗi thư viện frontend lại có widget ngày tháng riêng, và nhiều cái trông rất tệ trên màn hình nhỏChỉ vì một widget đó mà phải thêm JS và CSS, một số còn phụ thuộc vào jQuery. Tất nhiên cũng có những widget có rất nhiều tùy chọn, nhưng với chi phí rất nhỏ, bạn có thể có một widget trông ổn ở mọi nơi, giống native, thường đáp ứng được yêu cầu, rồi có thể quên nó đi mà không phải lo cập nhật hay CDN
Input thông thường còn miễn cưỡng làm được, còn các input đặc biệt hỗ trợ ít sự kiện hơn
Sản phẩm của tôi không vượt qua được kiểm toán khả năng tiếp cận vì dùng xác thực form HTML native, và khuyến nghị chính thức là hãy triển khai một lớp xác thực riêng. Tôi cũng đồng ý với khuyến nghị đó
Xác thực HTML native có rất nhiều khiếm khuyết, và nói thật, tùy biến giao diện còn chưa phải nỗi lo lớn nhất. Dù vậy, nó cũng gần như chiếc đinh cuối cùng đóng vào quan tài
Ví dụ, nếu muốn hiển thị nhiều lỗi cho mỗi trường cùng lúc thì cách duy nhất là nối chuỗi. Kiểu như “Cần có số. Cần có ký hiệu. Phải dài hơn 10 ký tự”, vừa tệ về trải nghiệm người dùng vừa không tốt cho khả năng tiếp cận. Vì trong accessibility tree, không thể điều hướng qua chuỗi đã bị nối như vậy. Đây không phải vấn đề của từng implementation, mà là vấn đề của chính đặc tả. Người dùng không thấy vui khi phải chơi trò đập chuột chũi với lỗi xác thực, nên cần có khả năng hiển thị nhiều lỗi cùng lúc
Việc phụ thuộc vào trình duyệt cũng tệ. Bạn không kiểm soát được, mà implementation nhìn chung cũng rất dở. Chrome hiển thị popup khi focus; nó là popup, trông như modal, và không thể hiển thị tất cả lỗi form cùng lúc, nên bản thân nó đã không tốt cho khả năng tiếp cận. Không hiển thị thông tin quan trọng bằng popup là điều cơ bản trong accessibility, nhưng trình duyệt cũng không có nhiều cách khác nếu không can thiệp vào tài liệu thật
Các lỗi ở cấp toàn bộ form không thuộc về một trường cụ thể vẫn cần xác thực tùy chỉnh. Ví dụ lỗi kiểu “Trường A và B không thể cùng tồn tại”; đã vậy thì tốt hơn là có một cách xác thực nhất quán
Nếu có input tùy chỉnh không khớp với các kiểu input native, để giữ tính nhất quán bạn phải đặt thêm input ẩn, và việc này cũng phá vỡ accessibility. Muốn sửa thì cuối cùng cũng khó ngang với việc tự xây một hệ thống xác thực thân thiện với khả năng tiếp cận
API
customValiditymang tính mệnh lệnh và khó dùng. Trừ khi bạn muốn những thông báo tệ hại như “Trường này không hợp lệ”, gần như không có lựa chọn nào khác ngoài dùng custom validity. Vì vậy xác thực form HTML rất tệDù vậy, tôi vẫn nghĩ cơ chế xác thực native có giá trị để tận dụng. Không nhất thiết phải dùng tooltip xác thực native cho thông báo lỗi; có thể tự đọc
ValidityStatetừinput.validity, rồi render thông báo theo cách mình muốn và hiển thị nhiều lỗi nếu cầnTrình duyệt có thể được cải thiện, và nếu dùng cách tiếp cận chuẩn hóa thì ta có thể hưởng lợi từ các cải tiến đó. Kết luận kiểu “nếu không dùng popup thì accessibility bị hỏng” nghe có vẻ lạ, nhưng nếu cần đáp ứng kiểm toán thì có thể phải đi theo hướng đó
Cũng có kỹ thuật để xử lý lỗi không thuộc về một trường cụ thể. Trong dự án của tôi, chúng tôi dùng component
HiddenValidationInput, tạo ra một input chỉ đọc không hiển thị để render các lỗi tùy chỉnh không thuộc về trường nào khác. Chỉ cần render có điều kiện nên thực tế khá dễ dùngTôi hoàn toàn đồng ý rằng API
customValiditymang tính mệnh lệnh và rườm rà. Tôi đã viết một bài về đúng phần đó, và hy vọng phần này cũng sẽ được cải thiện theo thời gianThiết kế thô kệch và kém accessibility của Chrome là vấn đề của Chrome, nên tôi sẽ gửi bug report
“Đặc tả” ở đây có phải là cái này không? https://html.spec.whatwg.org/multipage/dom.html#concept-elem...
Tôi không thấy phần nào định nghĩa cách hiển thị thông báo xác thực. Thậm chí cũng không có nội dung nói rằng bắt buộc phải có thông báo
Nói thật, những người viết các đặc tả như thế này có vẻ xa rời thực tế và không thực sự dùng những thứ họ đã đặc tả. Nó hoạt động với những thứ rất đơn giản, nhưng khi form bắt đầu phát triển, cuối cùng bạn sẽ nhận ra tốt hơn là tự viết tất cả
Một khi đã bắt đầu dùng JS, làm mọi thứ bằng code sẽ dễ hơn nhiều so với việc loay hoay với các thuộc tính xác thực
Nếu checkbox có nhãn, tôi mong nhãn được gắn thuộc tính
forđể có thể bật/tắt checkbox bằng cách bấm vào nhãn. Cá nhân tôi thấy đây là một trong những điều khó chịu nhất, có thể chỉ mình tôi vậy thôiinputbằnglabelcũng hoạt động. Tôi không hiểu vì sao mọi người có xu hướng tách hai thứ này raVà tôi cũng không hiểu vì sao trình duyệt lại bắt đầu tách chúng ra. Theo tôi, không phải văn bản chứa checkbox và radio, mà checkbox và radio nên chứa văn bản
Có một ví dụ đơn giản không dùng React ở đây
https://developer.mozilla.org/en-US/docs/Web/API/HTMLObjectE...
Tính năng kiểm tra hợp lệ của form HTML rất tuyệt. Chỉ có điều có một cái bẫy rất lớn
Nó không hoạt động trên Firefox cho Android
https://bugzilla.mozilla.org/show_bug.cgi?id=1510450
Không đáng để bỏ qua chuẩn chỉ vì chưa tới 1% người dùng không xem được thông báo lỗi của control được đánh dấu màu đỏ. Càng nhiều website dùng tính năng này, áp lực buộc Mozilla sửa trình duyệt càng lớn
Đây không phải là một API kiểu WebMIDI do Chrome hay Safari triển khai trước khi chuẩn hóa, mà vốn là một phần của đặc tả HTML5 ban đầu
Điều đó càng đúng nếu nghĩ đến việc người ta dành ít thời gian đến mức nào để giúp những người dùng phần mềm hỗ trợ tiếp cận có thể sử dụng site đúng cách. Nếu chưa sẵn sàng nói đến cả UC Browser, tôi nghĩ vấn đề kiểu này không đáng được nhắc nhiều
Dù vậy, việc có thể dùng uBlock Origin cùng các tiện ích mở rộng khác vẫn là một lợi thế rất mạnh
[1] https://bugzilla.mozilla.org/show_bug.cgi?id=1884282
[2] https://bugzilla.mozilla.org/show_bug.cgi?id=1897707
Nếu việc kiểm tra hợp lệ hoàn toàn chưa được triển khai thì có khi còn đỡ hơn; còn cái này thì thật sự là thảm họa. Tôi phát hiện ra sau một lần debug dài và đau đớn vài năm trước, và tôi chắc mình không phải người đầu tiên hay cuối cùng gặp chuyện này
Trước khi tìm kiếm, tôi không thể tưởng tượng mình đã bỏ sót điều gì, và khá sốc. Tự làm phần kiểm tra hợp lệ thì cũng được, nhưng cái này phải hoạt động mới đúng
Cần cẩn thận để không dùng quá đà
Gần đây tôi cố xin hoàn tiền trên Groupon. Lý do là doanh nghiệp nơi tôi mua Groupon đã đổi ban điều hành mới và không chấp nhận Groupon của tôi
Form có điều kiện “tối thiểu 15 từ”, nhưng làm thế nào cũng không vượt qua được kiểm tra hợp lệ, nên cuối cùng tôi phải inspect HTML
\wlà ký tự từ,\blà ranh giới từ,\slà khoảng trắng, và dấu câu thì đúng nghĩa là hoàn toàn không được phéppatternthì hay, nhưng không đủVí dụ, ví dụ “nhập lại mật khẩu” có thể triển khai bằng thuộc tính
patternmà không cầnsetCustomValidity. Muốn vậy phải xây dựng động biểu thức chính quy từ giá trị nhập đầu tiênTôi không so sánh các cách giải vì sợ bài viết quá dài, nhưng điểm chính là dùng
customValiditygiúp việc kiểm tra hợp lệ rõ ràng và dễ đọc hơn nhiều. Ở đây một API tốt hơn tạo ra khác biệt lớnRàng buộc “tối thiểu 15 từ” cũng có thể được diễn đạt dễ nhìn hơn nhiều, kiểu như
value.split(/\s+/).length >= 15Việc ít được dùng là có lý do. Nhiều framework và thư viện cung cấp chức năng kiểm tra hợp lệ vững chắc và có thể tùy biến style, một số còn rất tinh vi và mở rộng được. Nếu không cần tự làm khổ mình thì tốt nhất đừng tự hành hạ bản thân
curl, hoặc công cụ khác không có sẵn cùng kiểu kiểm tra hợp lệ cho formKhông thể tin rằng client đã kiểm tra hợp lệ, hay đã kiểm tra đúng, nên backend vẫn phải kiểm tra dữ liệu nhập và có khả năng hiển thị lại form kèm lỗi kiểm tra hợp lệ cho tất cả các trường
Kiểm tra hợp lệ ở frontend chỉ nhằm giúp người dùng. Nhưng nếu muốn styling hoặc kích hoạt từ backend khi submit, cuối cùng vẫn phải tự triển khai phần styling
Có quá nhiều cạm bẫy, và để hỗ trợ dễ dàng các phép kiểm tra phức tạp hơn thì cuối cùng vẫn dùng thư viện
Hơn nữa, khi dùng thư viện, tùy trường hợp còn có khả năng chia sẻ một phần mã kiểm tra hợp lệ giữa frontend và backend
Đặc biệt bài này trông như đang né một vấn đề bằng
useLayoutEffect, mà đó không phải việc nên làm qua loaMột điều tôi không thích ở kiểm tra hợp lệ của form HTML là nó chạy ngay từ lúc tải trang. Ví dụ, nếu gắn style trạng thái lỗi vào đây, form sẽ được tải lên trong tình trạng đầy lỗi ngay từ đầu, trông có thể gây áp lực cho người dùng
:user-invalidgiúp tránh điều này ở mức nào đó. Tuy nhiên nó có phần thiếu linh hoạt, nên tùy use case có thể vẫn chưa đủhttps://developer.mozilla.org/en-US/docs/Web/CSS/:user-inval...
Có thể dùng script để né, nhưng đến lúc đó thì tính năng này cũng chẳng giúp được gì nhiều. Tôi nghĩ đây chính là lý do lớn nhất khiến nó không được áp dụng rộng rãi hơn