Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions source/.htaccess
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ RedirectMatch \/2.*\/(.*)? http://struts.apache.org/$1
# page downloads.html was renamed to releases.html
RedirectMatch \/downloads /releases

# page pure-java-script-client-side-validation was folded into client-side-validation as a
# deprecated section, see WW-5694
RedirectMatch permanent \/core\-developers\/pure\-java\-script\-client\-side\-validation(\.html)?$ /core-developers/client-side-validation#pure-javascript-client-side-validation-deprecated

# always points to the most recent announce-YYYY page, no manual update needed
{% assign announcements = site.pages | where_exp: "page", "page.path contains 'announce-'" | sort: "path" -%}
RedirectMatch \/announce.html(#a[0-9]+)? {{ announcements.last.url }}$1
Expand Down
5 changes: 3 additions & 2 deletions source/core-developers/ajax-client-side-validation.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ parent:

## Description

AJAX-based client side validation improves upon [Pure JavaScript Client Side Validation](pure-java-script-client-side-validation)
by using a combination of JavaScript, DOM manipulation, and remote server communication. Unlike the pure client side
AJAX-based client side validation improves upon [Pure JavaScript Client Side Validation](client-side-validation#pure-javascript-client-side-validation-deprecated)
(deprecated since 7.4.0 [WW-5694](https://issues.apache.org/jira/browse/WW-5694), removed in 8.0.0
[WW-5696](https://issues.apache.org/jira/browse/WW-5696)) by using a combination of JavaScript, DOM manipulation, and remote server communication. Unlike the pure client side
implementation, AJAX-based validation communicates with the server. This means all your validation rules that worked
when submitting a form will still work within the browser.

Expand Down
177 changes: 141 additions & 36 deletions source/core-developers/client-side-validation.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,57 +14,162 @@ parent:

## Basics

The framework adds support for client-side validation on top of the standard validation framework.
Since Struts 7.4.0, the [html5 theme](../tag-developers/html5-theme) can derive HTML5 constraint-validation
attributes (`required`, `minlength`, `maxlength`, `pattern`, `min`, `max`) directly from a field's
server-side [validators](validation), so the browser rejects obviously-invalid input before the form is
even submitted. This replaces the older, generated-JavaScript validator used by the _xhtml theme_ and
_css_xhtml theme_, which is now deprecated — see [Pure JavaScript Client Side Validation
(deprecated)](#pure-javascript-client-side-validation-deprecated) below.

Client-side validation can be enabled on a per-form basis by specifying `validate="true"` in the _form_ tag.
There is also [AJAX Client Side Validation](ajax-client-side-validation), which runs the full server-side
validation stack (including visitor validators and `validate()`) over AJAX and is unaffected by any of this.

```jsp
<s:form name="test" action="javascriptValidation" validate="true">
...
</s:form>
```
## HTML5 Constraint Validation

If a `name` for the form is not given, the action mapping name will be used as the form name. Otherwise, a correct
`action` and `namespace` attributes must be provided to the `<saf:form>` tag.
### Enabling it

## Referencing "submitProfile" in the "/user" namespace
HTML5 constraint validation is off by default. Turn it on with the `struts.ui.html5.constraints` constant:

```jsp
<s:form namespace="/user" action="submitProfile" validate="true">
...
</s:form>
```properties
struts.ui.html5.constraints=true
```

It only has an effect on fields rendered with the `html5` theme (see [Using the HTML5
theme](../tag-developers/html5-theme#using-the-html5-theme)). There is no per-form opt-in attribute —
unlike the deprecated JavaScript validator, this feature does not use `<s:form validate="true">`. Once the
constant is on and a field's theme is `html5`, its validators are consulted automatically.

> The constant defaults to `false` so existing `html5`-theme forms keep rendering unchanged. The default is
> expected to flip to `true` in a future major release, tracked by
> [WW-5696](https://issues.apache.org/jira/browse/WW-5696).

### The governing rule: never false-reject

The mapping is deliberately conservative. A constraint is emitted only when the browser cannot reject
input the server would have accepted. If the browser rejected something the server allows, the user would
be stuck with a form that will not submit and no explanation why. Being conservative simply costs a field
its client-side check — that's a harmless, quiet failure mode, so the mapping always chooses it over the
alternative.

The clearest consequence of this rule: **Struts never sets or changes an input's `type`.** A field stays
whatever `type` the developer gave it. In particular:

- Switching a field to `type="number"` would reject a value like `1234,50`, which the framework's
locale-aware numeric conversion happily accepts in a comma-decimal locale.
- The browsers' `email` and `url` input grammars don't match `EmailValidator` and `UrlValidator`.

So `min`/`max` range constraints are only ever added to a control the developer *already* made numeric
(`type="number"` or `type="range"`) — Struts will never promote a plain text field into one just because
an `int` or `double` validator is attached to it.

### Mapping table

| Validator | Emits | Condition |
|---|---|---|
| `requiredstring` | `required` | on text-entry controls (`text`, `search`, `tel`, `password`, `email`, `url`) and `textarea` |
| `required` | `required` | only on `radio` and `file` |
| `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"` |
| `double` | `min` / `max` | same as above; only inclusive bounds are emitted — exclusive bounds have no HTML equivalent and are omitted |
| `date` | — | nothing yet; temporal `min`/`max` is deferred to a future release |
| `email`, `url`, `creditcard` | — | never emitted |
| `fieldexpression`, `expression`, `conversion`, visitor validators | — | never emitted |
| any validator carrying a message | `data-msg-<validatorType>` | always added, including for validators that emit no constraint attribute at all |

Two of these conditions are easy to miss and sharply limit how often `required`, `minlength`/`maxlength`,
and `pattern` actually show up:

**`required` is split across two validators, and they don't behave alike.** `requiredstring` fails on
null, empty, and (by default) blank values, so it is strictly stricter than the browser's `required` — safe
to emit on any text-entry control. Plain `required`, however, only fails on a null value, an empty array,
or an empty collection. That means an empty text input (which submits `""`, not nothing), a `select` with
an empty-valued option, and an **unticked checkbox** (`CheckboxInterceptor` substitutes the parameter
`"false"` for it) all pass server-side validation while a browser `required` attribute would block them.
Only `radio` and `file` controls omit their parameter entirely when left empty, so those are the only two
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.

**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`
validator with `maxLength="4"` accepts `"abcd "` — it trims to four characters, which is within the limit —
but a browser enforcing `maxlength="4"` would stop the user typing the fifth character at all. Likewise, a
`regex` of `[a-z]+` accepts `"abc "` server-side (it trims to `"abc"` first) while the browser, matching the
raw value, blocks it. Because of this, `minlength`/`maxlength` and `pattern` are only ever emitted
for validators explicitly configured with `trim="false"` — which most existing `stringlength` and `regex`
validators are not. In practice, expect both to show up rarely until applications start setting
`trim="false"` deliberately for fields where it's safe.

**ECMAScript-safe** means the regex uses only constructs that mean the same thing in Java's regex engine
and in the browser's: literals, `\d`/`\w` and their negations, character classes without POSIX or Unicode
property syntax, grouping, alternation, anchors, and bounded quantifiers. Notably, **`\s` and `\S` are
excluded** — Java's `\s` is ASCII-only by default while ECMAScript's `\s` covers the wider Unicode
whitespace set, so a pattern like `^\S+$` would accept a value containing a non-breaking space server-side
and reject it in the browser. Any regex using a construct outside this allowlist simply gets no `pattern`
attribute at all — it is never rejected loudly, it just quietly doesn't get a client-side check.

### `data-msg-*` attributes

Every validator carrying a message — even one that emits no HTML constraint attribute at all — adds a
`data-msg-<validatorType>` attribute (for example `data-msg-email`, `data-msg-regex`) holding the
validator's fully resolved, internationalized message. **Struts ships no JavaScript that reads these.**
They exist purely as a hook: an application can write its own script to read `data-msg-*` and show
whichever messages it wants, in whatever way it wants, including for validators (like `email` or
`creditcard`) that never get a native browser check.

### `requiredLabel` is unrelated to the `required` attribute

This is a common point of confusion: the `requiredLabel` tag attribute only controls whether a visual
marker (usually `*`) is drawn next to a field's label. It has no connection to the HTML `required`
attribute described above, and setting `requiredLabel="true"` does not make a field required in the
browser — the two are decided completely independently.

### Extension point: `HtmlConstraintProvider`

The mapping above is implemented by `StrutsHtmlConstraintProvider`, the default implementation of the
`HtmlConstraintProvider` interface, registered under the `struts.htmlConstraintProvider` constant:

```properties
struts.htmlConstraintProvider=struts
```

Technically, the form's action attribute can refer to a "path" that includes the namespace and action as a URI.
But, client-side validation **requires** that the action name and namespeact to be set separately.
An application that wants a less conservative mapping — for example, treating an `email` validator as
`type="email"`, or emitting `pattern` for case-insensitive regexes by rewriting them — can register its own
`HtmlConstraintProvider` implementation under this constant instead of the default. This is the escape
hatch for every limitation described above: the framework's own mapping stays deliberately conservative,
but nothing stops an application from replacing it with one that fits its own validators and locales.

## Pure JavaScript Client Side Validation (deprecated) {#pure-javascript-client-side-validation-deprecated}

> **Deprecated since Struts 7.4.0 ([WW-5694](https://issues.apache.org/jira/browse/WW-5694)), removed in
> Struts 8.0.0 ([WW-5696](https://issues.apache.org/jira/browse/WW-5696)).** New applications should use
> the [html5 theme's constraint validation](#html5-constraint-validation) described above instead.

## Won't work with client-side validation!
The `<s:form validate="true">` attribute enables an older client-side validation mechanism, used by the
_xhtml theme_ and _css_xhtml theme_. It uses 100% client-side JavaScript, generated from the same
validation configuration used server-side, to try to reject bad input before the form is submitted:

```jsp
<s:form action="/user/submitProfile.action" validate="true">
<s:form name="test" action="javascriptValidation" validate="true">
...
</s:form>

```

All the usual [validation configuration](validation) steps apply to client-side validation. Client-side validation
uses the same validation rules as server-side validation. If server-side validation doesn't work, then client-side
validation won't work either.
If a `name` for the form is not given, the action mapping name is used as the form name. Otherwise, a
correct `action` and `namespace` attribute must be provided to the `<s:form>` tag — client-side validation
requires the action name and namespace to be resolvable separately, so a form whose `action` is given as a
full URI (for example `<s:form action="/user/submitProfile.action" validate="true">`) will not get
client-side validation, even though the form still works.

## The left hand doesn't know ...

> The required attribute on many _Struts Tags_ is not integrated with client-side validation! The tag attribute is used
> by certain themes (like xhtml) to put a visual marker (usually '*') next to the field. The tag doesn't know
> if the validation system actually "requires" the field or not.
Because the validation logic is repeated in generated JavaScript, only a subset of validators is
supported (`required`, `requiredstring`, `stringlength`, `regex`, `email`, `url`, `int`, `double`), it is
not available for visitor validators at all, and — being a separate implementation of each validator's
logic — some values the JavaScript accepts may still be rejected server-side, or vice versa. This is one of
the reasons it is being replaced: the html5 theme's constraint validation above is derived directly from
the real validators, rather than reimplementing them in JavaScript.

## Example

See [Client Validation example](client-validation-example) for a complete example of client-side validation.

## Client Side Validation Types

There are two styles of client side validation.

|[Pure JavaScript Client Side Validation](pure-java-script-client-side-validation)|Used by the _xhtml theme_ and _css_xhtml theme_|
|-----------------------------------------------------|--------------------------------------------------|
|[AJAX Client Side Validation](ajax-client-side-validation)|Use to used by the _ajax theme_ |
See [Client Validation example](client-validation-example) for a complete, though now-deprecated, example
of the JavaScript-based client-side validation described above.
6 changes: 6 additions & 0 deletions source/core-developers/client-validation-example.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ parent:

# Client Validation Example

> **Deprecated since Struts 7.4.0 ([WW-5694](https://issues.apache.org/jira/browse/WW-5694)), removed in
> Struts 8.0.0 ([WW-5696](https://issues.apache.org/jira/browse/WW-5696)).** This example walks through the
> generated-JavaScript client-side validator (`<s:form validate="true">`) used by the _xhtml theme_ and
> _css_xhtml theme_. New applications should use the [html5 theme's constraint
> validation](client-side-validation#html5-constraint-validation) instead.

Let's create a Client-Side validation workflow, step by step. The `validate` attribute is set to `true`.

> Note: Some themes do not support client-side validation.
Expand Down
50 changes: 0 additions & 50 deletions source/core-developers/pure-java-script-client-side-validation.md

This file was deleted.

Loading