This is an automated email from the ASF dual-hosted git repository.
lukaszlenart pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/struts-site.git
The following commit(s) were added to refs/heads/main by this push:
new c6b187fb0 WW-5704 docs: required on radio/file is judged against the
bound value (#336)
c6b187fb0 is described below
commit c6b187fb0765746b575bd01c160a1aed923390e9
Author: Lukasz Lenart <[email protected]>
AuthorDate: Mon Sep 14 16:45:59 2026 +0200
WW-5704 docs: required on radio/file is judged against the bound value
(#336)
Companion to apache/struts#1939.
Co-authored-by: Claude Opus 5 (1M context) <[email protected]>
---
source/core-developers/client-side-validation.md | 15 ++++++++++++++-
1 file changed, 14 insertions(+), 1 deletion(-)
diff --git a/source/core-developers/client-side-validation.md
b/source/core-developers/client-side-validation.md
index 381827a8c..5432c9b14 100644
--- a/source/core-developers/client-side-validation.md
+++ b/source/core-developers/client-side-validation.md
@@ -67,7 +67,7 @@ an `int` or `double` validator is attached to it.
| Validator | Emits | Condition |
|---|---|---|
| `requiredstring` | `required` | on text-entry controls (`text`, `search`,
`tel`, `password`, `email`, `url`) and `textarea` |
-| `required` | `required` | only on `radio` and `file` |
+| `required` | `required` | only on `radio` and `file`, and only while the
bound property holds a value the validator would reject (null, an empty array
or an empty collection) |
| `stringlength` | `minlength` / `maxlength` | on text-entry or `textarea`,
and only if the validator has `trim="false"`; each attribute is added only if
actually configured |
| `regex` | `pattern` | on text-entry controls only, and only if
`caseSensitive="true"`, `trim="false"`, the regex is ECMAScript-safe (see
below), and the validator is not an `email` or `creditcard` validator (both
extend `RegexFieldValidator` but carry grammars the browser does not share) |
| `int`, `short`, `long` | `min` / `max` | only when the control is already
`type="number"` or `type="range"` |
@@ -91,6 +91,14 @@ Only `radio` and `file` controls omit their parameter
entirely when left empty,
control types where plain `required` agrees with the server — which is why the
table above emits `required`
for the `required` validator on those two types alone.
+Even there, one more check is needed. An omitted parameter leaves the property
at whatever it already
+holds, and that is the value the page is rendering. A `private int priority`
bound to a `radio` list of
+1, 2, 3 renders with nothing checked (0 is not in the list), yet the server
sees a non-null `Integer` and
+accepts the empty submit; a `file` property that `prepare()` loaded from an
existing entity — the ordinary
+edit flow — is accepted the same way. So `required` is emitted only while the
bound value is one the
+validator itself would reject: null, an empty array or an empty collection. A
property that is null when the
+page renders but populated only on the submit request is the one case this
cannot see.
+
**Both `minlength`/`maxlength` and `pattern` need `trim="false"`, which is not
the default.** Both
`StringLengthFieldValidator.trim` and `RegexFieldValidator.trim` default to
`true`, so the server measures
or matches the field's *trimmed* value while the HTML attribute constrains the
*raw* one. A `stringlength`
@@ -161,6 +169,11 @@ escape hatch for every limitation described above: the
framework's own mapping s
conservative, but nothing stops an application from replacing it with one that
fits its own validators and
locales.
+A provider receives the field's validators, the kind of control being
rendered, the object the validators
+run against (the action, or the visited object under a `visitor` validator)
and the field's current value
+as the tag resolved it — the last is what lets the default implementation
judge `required` against the bound
+property.
+
Two things a provider cannot do. It cannot change an input's `type` — the
templates have already written
it by the time the constraint map renders, so a `type` entry is discarded;
treating an `email` validator
as `type="email"` needs a template override instead. And it cannot override an
attribute the developer set