Validation is to be used only when editing, therefore only the WebAddress>>buildEdit method is modified. For the exercise the email form is added, the validation code is copied from a previous post on validation:

WebAddress>>buildEdit
        | field |
[...]
        self newRow.
        self newCell addTextBold: 'Email'.
        self newCell addTextBold: ' : '.
        field := self newCell addInputFieldAspect: #email for: address.
        field
                onChangePost;
                validIfTrue: [ :value | value includes: $@ ];
                errorText: 'Email invalide'.
        self newCell add: field errorElement.

The email field is checked for validity with the block, value contains the user input in the input field. #onChangePost and #errorElement work together: the first one set AJAX code to post change to the server once the field is changed and validated (mouse clic somewhere else or Tab), the latter puts the error message whenever there is one, once a post from the client.

This view renders as: AjaxValidation.png

Next in our AddressApp view, we add the errors block above the input fields:

ADemoAddressApp>>viewEdit
        | html |
        html := WebElement new.
        html addTextH1: 'Add/Edit the address'.
        self inError ifTrue: [html add: self errorReport].
[...]

Our actionEdit handles the validation and collects validation errors. Here it is, unmodified:

ADemoAddressApp>>actionEdit
        self context form isValid ifFalse: 
                [self showError: self context form collectErrorTexts.
                ^ self redirectToView: #edit  ].
        self observee changeToPreferredUrl.
        self redirectToView: #main

Collected validation errors render as follow in the view: CollectedErrorsValidation.png

The changes were minor. I guess (and expect) it works identically with several Widgets, each with its own validation.

One point itching me: once you validated a form with a faulty field, the next view shows the form with the value from the model, we lost the invalid data as a source of information for the user. Any idea how to solve that?

That's all folks!