6. Convalida JavaScript lato client
Nel capitolo precedente abbiamo esaminato la convalida lato server. Torniamo all’architettura di un’applicazione Spring MVC:
![]() |
BD
Finora, le pagine inviate al client non contenevano JavaScript. Ora affrontiamo questa tecnologia che ci consentirà, in una prima fase, di effettuare validazioni lato client. Il principio è il seguente:
- è il JavaScript che invia i valori al server web;
- e quindi, prima di questo POST, può verificare la validità dei dati e impedire l’esecuzione di POST se questi sono non validi;
Utilizzeremo il modulo che abbiamo convalidato lato server. Ora offriremo la possibilità di convalidarlo sia lato client che lato server.
Nota: l’argomento è complesso. Il lettore non interessato a questo tema può passare direttamente al paragrafo 7.
6.1. Le funzionalità del progetto
Presentiamo alcune schermate del progetto per illustrarne le funzionalità. La pagina iniziale viene generata con il codice URL [http://localhost:8080/js01.html]
![]() |
Le validazioni sono state implementate su entrambi i fronti: client e server. Poiché il POST avviene solo se i valori sono stati considerati validi sul lato client, le validazioni sul lato server hanno sempre esito positivo. È stato quindi fornito un link per disattivare le validazioni lato client. In questa modalità, si ritorna al funzionamento che abbiamo già esaminato. Ecco un esempio:
123 ![]() |
- in [1], i valori inseriti;
- in [2], i messaggi di errore relativi agli inserimenti;
- in [3], un riepilogo degli errori con, per ciascuno di essi:
- il nome del campo convalidato,
- il codice di errore,
- il messaggio predefinito relativo a tale codice di errore;
Ora abilitiamo la convalida lato client:
![]() |
- in [1], i valori inseriti. Si può notare che gli inserimenti errati hanno uno stile particolare;
- in [2], i messaggi di errore associati ai dati inseriti in modo errato. Sono identici a quelli generati dal server;
- in [3-4] non c'è più nulla perché, finché ci sono inserimenti errati, la richiesta POST al server non viene inviata;
6.2. Convalida lato server
6.2.1. Configurazione
Iniziamo creando un nuovo progetto Maven denominato [springmvc-validation-client]:
![]() |
Modifichiamo il progetto come segue:
![]() |
La classe [Config] configura il progetto. È identica a quella presente nei progetti precedenti:
package istia.st.springmvc.config;
import java.util.Locale;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ResourceBundleMessageSource;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.i18n.CookieLocaleResolver;
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
import org.thymeleaf.spring4.SpringTemplateEngine;
import org.thymeleaf.spring4.templateresolver.SpringResourceTemplateResolver;
@Configuration
@ComponentScan({ "istia.st.springmvc.controllers", "istia.st.springmvc.models" })
@EnableAutoConfiguration
public class Config extends WebMvcConfigurerAdapter {
@Bean
public MessageSource messageSource() {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
messageSource.setBasename("i18n/messages");
return messageSource;
}
@Bean
public LocaleChangeInterceptor localeChangeInterceptor() {
LocaleChangeInterceptor localeChangeInterceptor = new LocaleChangeInterceptor();
localeChangeInterceptor.setParamName("lang");
return localeChangeInterceptor;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(localeChangeInterceptor());
}
@Bean
public CookieLocaleResolver localeResolver() {
CookieLocaleResolver localeResolver = new CookieLocaleResolver();
localeResolver.setCookieName("lang");
localeResolver.setDefaultLocale(new Locale("fr"));
return localeResolver;
}
@Bean
public SpringResourceTemplateResolver templateResolver() {
SpringResourceTemplateResolver templateResolver = new SpringResourceTemplateResolver();
templateResolver.setPrefix("classpath:/templates/");
templateResolver.setSuffix(".xml");
templateResolver.setTemplateMode("HTML5");
templateResolver.setCacheable(true);
templateResolver.setCharacterEncoding("UTF-8");
return templateResolver;
}
@Bean
SpringTemplateEngine templateEngine(SpringResourceTemplateResolver templateResolver) {
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setTemplateResolver(templateResolver);
return templateEngine;
}
}
La classe [Main] è la classe eseguibile del progetto:
package istia.st.springmvc.main;
import istia.st.springmvc.config.Config;
import java.util.Arrays;
import org.springframework.boot.SpringApplication;
import org.springframework.context.ApplicationContext;
public class Main {
public static void main(String[] args) {
// si avvia l'applicazione
ApplicationContext context = SpringApplication.run(Config.class, args);
// viene visualizzato l'elenco dei bean individuati da Spring
System.out.println("Liste des beans Spring");
String[] beanNames = context.getBeanDefinitionNames();
Arrays.sort(beanNames);
for (String beanName : beanNames) {
System.out.println(beanName);
}
}
}
- riga 13, Spring Boot viene avviato con il file di configurazione [Config];
- righe 15-20: a titolo di esempio, mostriamo come visualizzare l’elenco degli oggetti gestiti da Spring. Ciò può essere utile se a volte si ha l’impressione che Spring non gestisca uno dei nostri componenti. È un modo per verificarlo. È anche un modo per verificare l’autoconfigurazione effettuata da Spring Boot. Sulla console si ottiene un elenco simile al seguente:
Abbiamo evidenziato gli oggetti definiti nella classe [Config].
6.2.2. Il modello del modulo
Continuiamo l’esplorazione del progetto:
![]() |
La classe [Form01] è quella che riceverà i valori inviati. È la seguente:
package istia.st.springmvc.models;
import java.util.Date;
import javax.validation.constraints.AssertFalse;
import javax.validation.constraints.AssertTrue;
import javax.validation.constraints.DecimalMax;
import javax.validation.constraints.DecimalMin;
import javax.validation.constraints.Future;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Past;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
import org.hibernate.validator.constraints.Email;
import org.hibernate.validator.constraints.Length;
import org.hibernate.validator.constraints.NotBlank;
import org.hibernate.validator.constraints.Range;
import org.hibernate.validator.constraints.URL;
import org.springframework.format.annotation.DateTimeFormat;
public class Form01 {
// valori inviati
@NotNull
@AssertFalse
private Boolean assertFalse;
@NotNull
@AssertTrue
private Boolean assertTrue;
@NotNull
@Future
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date dateInFuture;
@NotNull
@Past
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date dateInPast;
@NotNull
@Max(value = 100)
private Integer intMax100;
@NotNull
@Min(value = 10)
private Integer intMin10;
@NotNull
@NotBlank
private String strNotEmpty;
@NotNull
@Size(min = 4, max = 6)
private String strBetween4and6;
@NotNull
@Pattern(regexp = "^\\d{2}:\\d{2}:\\d{2}$")
private String hhmmss;
@NotNull
@Email
@NotBlank
private String email;
@NotNull
@Length(max = 4, min = 4)
private String str4;
@Range(min = 10, max = 14)
@NotNull
private Integer int1014;
@NotNull
@DecimalMax(value = "3.4")
@DecimalMin(value = "2.3")
private Double double1;
@NotNull
private Double double2;
@NotNull
private Double double3;
@URL
@NotBlank
private String url;
// convalida lato client
private boolean clientValidation = true;
// locale
private String lang;
...
}
Ritroviamo alcuni validatori già incontrati in precedenza. Introdurremo inoltre il concetto di validazione specifica. Si tratta di una validazione che non può essere formalizzata con un validatore predefinito. In questo caso, richiederemo che [double1+double2] rientri nell’intervallo [10,13].
6.2.3. Il controllore
Il controllore [JsController] è il seguente:
![]() |
package istia.st.springmvc.controllers;
import istia.st.springmvc.models.Form01;
...
@Controller
public class JsController {
@RequestMapping(value = "/js01", method = RequestMethod.GET, produces = "text/html; charset=UTF-8")
public String js01(Form01 formulaire, Locale locale, Model model) {
setModel(formulaire, model, locale, null);
return "vue-01";
}
...
// preparazione del modello della vista vista-01
private void setModel(Form01 formulaire, Model model, Locale locale, String message) {
...
}
}
- riga 9, l'azione [/js01];
- riga 10: viene istanziato un oggetto di tipo [Form01] e inserito automaticamente nel modello, associato alla chiave [form01];
- riga 10: la locale e il modello vengono inseriti nei parametri;
- riga 11: con queste informazioni, si prepara il modello;
- riga 12: viene visualizzata la vista [vue-01.xml];
Il metodo [setModel] è il seguente:
// preparazione del modello della vista vista-01
private void setModel(Form01 formulaire, Model model, Locale locale, String message) {
// si gestiscono solo le impostazioni locali fr-FR, en-US
String language = locale.getLanguage();
String country = null;
if (language.equals("fr")) {
country = "FR";
formulaire.setLang("fr_FR");
}
if (language.equals("en")) {
country = "US";
formulaire.setLang("en_US");
}
model.addAttribute("locale", String.format("%s-%s", language, country));
// eventuale messaggio
if (message != null) {
model.addAttribute("message", message);
}
}
- lo scopo del metodo [setModel] è quello di inserire nel modello:
- informazioni sulla lingua,
- il messaggio passato come ultimo parametro;
- riga 14: si inseriscono nel modello le informazioni relative alle impostazioni locali (lingua, paese);
- righe 16-18: si inserisce nella locale l'eventuale messaggio passato come parametro;
- righe 8, 12: le informazioni relative alle impostazioni locali vengono memorizzate anche nel modulo [Form01]. Il codice JavaScript utilizzerà queste informazioni;
I valori inseriti nel modulo [vue-01.xml] verranno inviati all’azione successiva [/js02]:
@RequestMapping(value = "/js02", method = RequestMethod.POST, produces = "text/html; charset=UTF-8")
public String js02(@Valid Form01 formulaire, BindingResult result, RedirectAttributes redirectAttributes, Locale locale, Model model) {
Form01Validator validator = new Form01Validator(10, 13);
validator.validate(formulaire, result);
...
}
- riga 2: l’annotazione [@Valid Form01 formulaire] fa sì che i valori inviati vengano sottoposti ai validatori della classe [Form01]. Sappiamo che esiste una validazione specifica [double1+double2] nell’intervallo [10,13]. Quando si arriva alla riga 3, tale validazione non è stata eseguita;
- riga 3: si crea il seguente oggetto [Form01Validator]:
![]() |
package istia.st.springmvc.validators;
import istia.st.springmvc.models.Form01;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;
public class Form01Validator implements Validator {
// l'intervallo di convalida
private double min;
private double max;
// costruttore
public Form01Validator(double min, double max) {
this.min = min;
this.max = max;
}
@Override
public boolean supports(Class<?> classe) {
return Form01.class.equals(classe);
}
@Override
public void validate(Object form, Errors errors) {
// oggetto convalidato
Form01 form01 = (Form01) form;
// il valore di [double1]
Double double1 = form01.getDouble1();
if (double1 == null) {
return;
}
// il valore di [double2]
Double double2 = form01.getDouble2();
if (double2 == null) {
return;
}
// [double1+double2]
double somme = double1 + double2;
// convalida
if (somme < min || somme > max) {
errors.rejectValue("double2", "form01.double2", new Double[] { min, max }, null);
}
}
}
- riga 8: per implementare una convalida specifica, creiamo una classe che implementa l’interfaccia Spring [Validator]. Questa interfaccia ha due metodi: [supports] alla riga 21 e [validate] alla riga 26;
- righe 21-23: il metodo [supports] riceve un oggetto di tipo [Class]. Deve restituire true per indicare che supporta questa classe, false in caso contrario;
- riga 22: specifichiamo che la classe [Form01Validator] accetta solo oggetti di tipo [Form01];
- righe 15-18: ricordiamo che vogliamo implementare il vincolo [double1+double2] nell’intervallo [10,13]. anziché limitarci a questo intervallo, verificheremo il vincolo [double1+double2] nell’intervallo [min, max]. Ecco perché abbiamo un costruttore con questi due parametri;
- riga 26: il metodo [validate] viene chiamato con un'istanza dell'oggetto convalidato, quindi in questo caso un'istanza di [Form01], e con la raccolta degli errori attualmente noti [Errors errors]. Se la convalida effettuata dal metodo [validate] fallisce, deve creare un nuovo elemento nella collezione [Errors errors];
- riga 43: la convalida non è andata a buon fine. Si aggiunge un elemento alla collezione [Errors errors] con il metodo [Errors.rejectValue], i cui parametri sono i seguenti:
- parametro 1: solitamente il nome del campo errato. In questo caso sono stati testati i campi [double1, double2]. È possibile inserire uno dei due,
- il messaggio di errore associato o, più precisamente, la sua chiave nei file dei messaggi esternalizzati:
[messages_fr.properties]
form01.double2=[double2+double1] doit être dans l''intervalle [{0},{1}]
[messages_en.properties]
form01.double2=[double2+double1] must be in [{0},{1}
Si tratta di messaggi configurati con {0} e {1}. È quindi necessario fornire due valori a questo messaggio. È ciò che fa il terzo parametro del metodo [Errors.rejectValue].
- Il quarto parametro è un messaggio predefinito per l’errore;
Torniamo all'azione [/js02]:
@RequestMapping(value = "/js02", method = RequestMethod.POST, produces = "text/html; charset=UTF-8")
public String js02(@Valid Form01 formulaire, BindingResult result, RedirectAttributes redirectAttributes, Locale locale, Model model) {
Form01Validator validator = new Form01Validator(10, 13);
validator.validate(formulaire, result);
if (result.hasErrors()) {
StringBuffer buffer = new StringBuffer();
for (ObjectError error : result.getAllErrors()) {
buffer.append(String.format("[name=%s,code=%s,message=%s]", error.getObjectName(), error.getCode(), error.getDefaultMessage()));
}
setModel(formulaire, model, locale, buffer.toString());
return "vue-01";
} else {
redirectAttributes.addFlashAttribute("form01", formulaire);
return "redirect:/js01.html";
}
}
- riga 4: il validatore [Form01Validator] viene eseguito con i seguenti parametri:
- parametro 1: l’oggetto in fase di validazione,
- parametro 2: l’elenco degli errori di tale oggetto. Si tratta dell’oggetto [BindingResult result] passato come parametro dell’azione. Se la convalida fallisce, a questo oggetto verrà aggiunto un ulteriore errore;
- riga 5: si verifica se ci sono errori di convalida;
- righe 7-10: si scorre l'elenco degli errori per memorizzare, per ciascuno di essi:
- il nome dell'oggetto convalidato,
- il codice di errore,
- il messaggio di errore predefinito;
- riga 10: con queste informazioni, si costruisce il modello della vista [vue-01.xml]. Questa volta è presente un messaggio, ovvero la versione concatenata e abbreviata dei vari messaggi di errore;
- righe 12-15: se tutti i valori inviati sono validi, si reindirizza il client all’azione [/js01] inserendo i valori inviati come attributi Flash;
6.2.4. La vista
La vista [vue-01.xml] è complessa. Ne presenteremo solo una piccola parte:
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Spring 4 MVC</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<link rel="stylesheet" href="/css/form01.css" />
<script type="text/javascript" src="/js/jquery/jquery-1.10.2.min.js"></script>
...
</head>
<body>
<!-- titolo -->
<h3>
<span th:text="#{form01.title}"></span>
<span th:text="${locale}"></span>
</h3>
<!-- menu -->
<p>
...
</p>
<!-- modulo -->
<form action="/someURL" th:action="@{/js02.html}" method="post" th:object="${form01}" name="form" id="form">
<table>
<thead>
<tr>
<th class="col1" th:text="#{form01.col1}">Contrainte</th>
<th class="col2" th:text="#{form01.col2}">Saisie</th>
<th class="col3" th:text="#{form01.col3}">Validation client</th>
<th class="col4" th:text="#{form01.col4}">Validation serveur</th>
</tr>
</thead>
<tbody>
<!-- obbligatorio -->
<tr>
<td class="col1">required</td>
<td class="col2">
<input type="text" th:field="*{strNotEmpty}" data-val="true" th:attr="data-val-required=#{NotNull}" />
</td>
<td class="col3">
<span class="field-validation-valid" data-valmsg-for="strNotEmpty" data-valmsg-replace="true"></span>
</td>
<td class="col4">
<span th:if="${#fields.hasErrors('strNotEmpty')}" th:errors="*{strNotEmpty}" class="error">Donnée erronée</span>
</td>
</tr>
...
</tbody>
</table>
<p>
<!-- pulsante di convalida -->
<input type="submit" th:value="#{form01.valider}" value="Valider" onclick="javascript:postForm01()" />
</p>
</form>
<!-- messaggio dei validatori lato server -->
<br/>
<fieldset class="fieldset">
<legend>
<span th:text="#{server.error.message}"></span>
</legend>
<span th:text="${message}" class="error"></span>
</fieldset>
</body>
</html>
Questa pagina utilizza una serie di messaggi presenti nei file di messaggi esternalizzati:
[messages_fr.properties]
form01.title=Formulaire - Validations côté client - locale=
form01.col1=Contrainte
form01.col2=Saisie
form01.col3=Validation client
form01.col4=Validation serveur
form01.valider=Valider
server.error.message=Erreurs détectées par les validateurs côté serveur
[messages_en.properties]
form01.title=Form - Client side validation - locale=
form01.col1=Constraint
form01.col2=Input
form01.col3=Client validation
form01.col4=Server validation
form01.valider=Validate
server.error.message=Errors detected by the validators on the server side
Torniamo al codice della pagina:
- riga 8: numerose importazioni di librerie JavaScript che in questo caso possiamo ignorare;
- riga 14: visualizza la stringa di localizzazione inserita nel modello dal server;
- riga 59: visualizza il messaggio inserito nel modello dal server;
Il codice delle righe 33-44 è nuovo. Analizziamolo:
<!-- obbligatorio -->
<tr>
<td class="col1">required</td>
<td class="col2">
<input type="text" th:field="*{strNotEmpty}" data-val="true" th:attr="data-val-required=#{NotNull}" />
</td>
<td class="col3">
<span class="field-validation-valid" data-valmsg-for="strNotEmpty" data-valmsg-replace="true"></span>
</td>
<td class="col4">
<span th:if="${#fields.hasErrors('strNotEmpty')}" th:errors="*{strNotEmpty}" class="error">Donnée erronée</span>
</td>
</tr>
Forse la cosa più semplice è dare un'occhiata al codice HTML generato da questo segmento Thymeleaf:
<!-- obbligatorio -->
<tr>
<td class="col1">required</td>
<td class="col2">
<input type="text" data-val="true" data-val-required="Le champ est obligatoire" id="strNotEmpty" name="strNotEmpty" value="" />
</td>
<td class="col3">
<span class="field-validation-valid" data-valmsg-for="strNotEmpty" data-valmsg-replace="true"></span>
</td>
<td class="col4">
</td>
</tr>
Utilizzeremo, sul lato client, una libreria di validazione denominata [jquery.validate]. Tutti gli attributi [data-x] sono destinati a essa. Quando la validazione sul lato client sarà disabilitata, questi attributi non verranno utilizzati. Pertanto, per il momento, è superfluo comprenderli. Possiamo semplicemente soffermarci sulla seguente riga Thymeleaf:
<input type="text" th:field="*{strNotEmpty}" data-val="true" th:attr="data-val-required=#{NotNull}" />
che genera la seguente riga HTML:
<input type="text" data-val="true" data-val-required="Le champ est obligatoire" id="strNotEmpty" name="strNotEmpty" value="" />
In questo caso, si presenta una difficoltà nel generare l’attributo [data-val-required="Le champ est obligatoire"]. Infatti, il valore associato all’attributo proviene dai file di messaggi esternalizzati. Si è quindi costretti a ricorrere a un'espressione Thymeleaf per ottenerlo. Si tratta della seguente espressione: [th:attr="data-val-required=#{NotNull}"]. Questa espressione viene valutata e il suo valore inserito così com'è nel tag HTML generato. Si chiama [th:attr] perché viene utilizzato per generare attributi non predefiniti in Thymeleaf. Abbiamo riscontrato attributi predefiniti [th:text, th:value, th:class, ...], ma non esiste alcun attributo [th:data-val-required].
6.2.5. Il foglio di stile
Sopra si incontrano classi CSS come [class="field-validation-valid"]. Alcune di queste classi sono utilizzate dalla libreria JavaScript di validazione. Sono definite nel seguente file [form01.css]:
![]() |
@CHARSET "UTF-8";
/*stili personalizzati*/
body {
background-image: url("/images/standard.jpg");
}
.col1 {
background: lightblue;
}
.col2 {
background: Cornsilk;
}
.col3 {
background: AliceBlue;
}
.col4 {
background: Lavender;
}
.error {
color: red;
}
.fieldset{
background: Lavender;
}
/* Stili per gli helper di convalida
-----------------------------------------------------------*/
.field-validation-error {
color: #f00;
}
.field-validation-valid {
display: none;
}
.input-validation-error {
border: 1px solid #f00;
background-color: #fee;
}
.validation-summary-errors {
font-weight: bold;
color: #f00;
}
.validation-summary-valid {
display: none;
}
6.3. Convalida lato client
6.3.1. Nozioni di base su jQuery e JavaScript
La validazione lato client viene effettuata tramite JavaScript. Ci avvarremo del framework jQuery, che offre numerose funzioni che facilitano lo sviluppo in JavaScript. Presentiamo le nozioni di base su jQuery necessarie per comprendere gli script di questo capitolo e di quelli successivi.
Creiamo un file statico HTML [JQuery-01.html] che inseriamo in una cartella [static / vues]:
![]() |
Questo file avrà il seguente contenuto:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>JQuery-01</title>
<script type="text/javascript" src="/js/jquery-1.11.1.min.js"></script>
</head>
<body>
<h3>Rudiments de JQuery</h3>
<div id="element1">
Elément 1
</div>
</body>
</html>
- riga 6: importazione di jQuery;
- righe 10-12: un elemento della pagina con id [element1]. Ora sperimenteremo con questo elemento.
Dobbiamo scaricare il file [jquery-1.11.1.min.js]. Troveremo l’ultima versione di jQuery in URL [http://jquery.com/download/]:

Il file scaricato va inserito nella cartella [static / js]:
![]() |
Fatto ciò, si richiederà la visualizzazione statica [jQuery-01.html] con Chrome [1-2]:
![]() |
Con Google Chrome, digitare [Ctrl-Maj-I] per visualizzare gli strumenti di sviluppo [3]. La scheda [Console] [4] consente di eseguire codice JavaScript. Di seguito forniamo alcuni comandi JavaScript da digitare e ne forniamo una spiegazione.
JS | risultato |
|
: restituisce la collezione di tutti gli elementi con id [element1], quindi normalmente una collezione di 0 o 1 elemento poiché non è possibile avere due id identici in una pagina HTML. | ![]() |
|
: assegna il testo [blabla] a tutti gli elementi della collezione. Ciò ha l’effetto di modificare il contenuto visualizzato dalla pagina | ![]() |
|
nasconde gli elementi della collezione. Il testo [blabla] non viene più visualizzato. | ![]() |
|
: visualizza nuovamente la collezione. Questo ci permette di vedere che l'elemento con id [element1] ha l'attributo CSS style='display: none;', che fa sì che l'elemento sia nascosto. | |
|
: visualizza gli elementi della collezione. Il testo [blabla] ricompare. È l'attributo CSS style='display: block;' che garantisce questa visualizzazione. | ![]() |
|
: assegna un attributo a tutti gli elementi della collezione. L’attributo in questo caso è [style] e il suo valore è [color: red]. Il testo [blabla] diventa rosso. | ![]() |
![]() | |
![]() |
Si noti che il codice URL del browser non è cambiato durante tutte queste operazioni. Non c'è stato alcun scambio con il server web. Tutto avviene all'interno del browser. Ora visualizziamo il codice sorgente della pagina:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>JQuery-01</title>
<script type="text/javascript" src="/js/jquery-1.11.1.min.js"></script>
</head>
<body>
<h3>Rudiments de JQuery</h3>
<div id="element1">
Elément 1
</div>
</body>
</html>
Questo è il testo iniziale. Non riflette in alcun modo le modifiche apportate all’elemento nelle righe 10-12. È importante tenerlo presente quando si esegue il debug di JavaScript. Spesso, quindi, è inutile visualizzare il codice sorgente della pagina visualizzata.
Sappiamo già abbastanza per comprendere gli script jS che seguiranno.
6.3.2. Le librerie di validazione jS
Utilizzeremo le librerie dell’ecosistema jQuery. Attorno a jQuery gravitano diversi progetti che, a loro volta, danno origine a librerie. Utilizzeremo la libreria di validazione [jquery.validate.unobstrusive] creata da Microsoft e donata alla fondazione jQuery. D'ora in poi la chiameremo libreria di validazione MS o, più semplicemente, libreria MS. Per ottenerla è necessario un ambiente Microsoft Visual Studio. Non ho trovato altri modi per ottenerla. È possibile utilizzare una versione gratuita del tipo [Visual Studio Community] [http://www.visualstudio.com/en-us/news/vs2013-community-vs.aspx] (dicembre 2014). Il lettore che non fosse interessato a seguire la procedura descritta di seguito può recuperare questa libreria e quelle su cui si basa negli esempi presenti sul sito di questo documento.
Si crea un progetto console con Visual Studio [1-4]:
|
![]() |
- in [5], il progetto console;
- in [6-7]: si aggiungeranno i pacchetti [NuGet] al progetto. [NuGet] è una funzione di Visual Studio che consente di scaricare librerie in formato DLL, ma anche librerie jS.
![]() |
- in [9-10], effettuate una ricerca con la parola chiave [jQuery];
- in [11-13], scaricate nell’ordine indicato le librerie jS necessarie per la convalida lato client;
- in [14], scaricate anche la libreria [Microsoft jQuery Unobtrusive Ajax] che useremo prossimamente;
![]() |
- in [15-16], effettuate una ricerca dei pacchetti con la parola chiave [globalize];
- per [17], scaricate la libreria [jQuery.Validation.Globalize];
![]() |
Questi vari download hanno installato una serie di librerie jS nella cartella [Scripts] del progetto [18]. Non sono tutte utili. Ogni file è presente in due copie:
- [js]: la versione leggibile della libreria;
- [min.js]: la versione non leggibile, detta «minificata» (minified), della libreria. Non è realmente illeggibile. Si tratta di testo. Ma non è comprensibile. È la versione da utilizzare in produzione poiché questo file è più piccolo della versione corrispondente [js] e quindi migliora la velocità degli scambi client/server;
Le versioni [min.map] non sono indispensabili. Nella cartella [cultures], è possibile conservare solo le impostazioni locali gestite dall’applicazione.
Con Esplora risorse di Windows, si copiano questi file nella cartella [static / js / jquery] del progetto [springmvc-validation-client] e si mantengono solo i file utili [20]:
![]() |
In [21], si mantengono solo due impostazioni locali:
- [fr-FR]: il francese della Francia;
- [en-US]: l'inglese di USA;
6.3.3. Importazione delle librerie di convalida jS
Per poter essere utilizzate, queste librerie devono essere importate dalla vista [vue-01.xml]:
<head>
<title>Spring 4 MVC</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<link rel="stylesheet" href="/css/form01.css" />
<script type="text/javascript" src="/js/jquery/jquery-2.1.1.min.js"></script>
<script type="text/javascript" src="/js/jquery/jquery.validate.min.js"></script>
<script type="text/javascript" src="/js/jquery/jquery.validate.unobtrusive.min.js"></script>
<script type="text/javascript" src="/js/jquery/globalize/globalize.js"></script>
<script type="text/javascript" src="/js/jquery/globalize/cultures/globalize.culture.fr-FR.js"></script>
<script type="text/javascript" src="/js/jquery/globalize/cultures/globalize.culture.en-US.js"></script>
<script type="text/javascript" src="/js/client-validation.js"></script>
<script type="text/javascript" src="/js/local.js"></script>
<script th:inline="javascript">
/*<![CDATA[*/
var culture = [[${locale}]];
Globalize.culture(culture);
/*]]>*/
</script>
</head>
- riga 11: l'importazione di un file jS di cui non abbiamo ancora parlato;
- righe 13-18: uno script jS interpretato da Thymelaf. Gestisce le impostazioni locali sul lato client;
6.3.4. Gestione delle impostazioni locali sul lato client
La localizzazione lato client viene effettuata dal seguente script jS:
<script th:inline="javascript">
/*<![CDATA[*/
var culture = [[${locale}]];
Globalize.culture(culture);
/*]]>*/
</script>
- righe 3-4: codice jS in cui si trova l’espressione Thymeleaf [[${locale}]]. Si noti la sintassi particolare di questa espressione, dovuta al fatto che è scritta in JavaScript. L’espressione [[${locale}]] verrà sostituita dal valore della chiave [locale] del modello della vista;
Il risultato nel flusso HTML generato da queste righe è il seguente:
<script>
/*<![CDATA[*/
var culture = 'en-US';
Globalize.culture(culture);
/*]]>*/
</script>
Le righe 3-4 definiscono la lingua lato client. Ne gestiamo solo due, [fr-FR] e [en-US]. Questo è il motivo per cui abbiamo importato solo due file di lingua:
<script type="text/javascript" src="/js/jquery/globalize/cultures/globalize.culture.fr-FR.js"></script>
<script type="text/javascript" src="/js/jquery/globalize/cultures/globalize.culture.en-US.js"></script>
La lingua da utilizzare sul lato client viene impostata sul lato server. Torniamo al codice sul lato server:
@RequestMapping(value = "/js01", method = RequestMethod.GET, produces = "text/html; charset=UTF-8")
public String js01(Form01 formulaire, Locale locale, Model model) {
setModel(formulaire, model, locale, null);
return "vue-01";
}
// preparazione del modello della vista vista-01
private void setModel(Form01 formulaire, Model model, Locale locale, String message) {
// si gestiscono solo le impostazioni locali fr-FR, en-US
String language = locale.getLanguage();
String country = null;
if (language.equals("fr")) {
country = "FR";
formulaire.setLang("fr_FR");
}
if (language.equals("en")) {
country = "US";
formulaire.setLang("en_US");
}
model.addAttribute("locale", String.format("%s-%s", language, country));
...
}
- riga 20: la locale [fr-FR] o [en-US] viene inserita nel modello della vista [vue-01.xml] (riga 4). Si noti una fonte di complicazioni. Mentre una locale francese è indicata come [fr-FR] sul lato client, sul lato server è indicata come [fr_FR]. Questo è il motivo per cui, alle righe 14 e 18, viene memorizzata in questa forma nell’oggetto [Form01 formulaire] che riceve i valori inviati;
Si noti il seguente punto importante. Lo script
<script>
/*<![CDATA[*/
var culture = 'en-US';
Globalize.culture(culture);
/*]]>*/
</script>
modifica l'impostazione culturale del client in base a quella locale trasmessa dal server. Ciò non internazionalizza i messaggi visualizzati dalla pagina, ma modifica solo il modo in cui vengono interpretate alcune informazioni che dipendono dall'impostazione culturale di un paese. Con l'impostazione di lingua [fr_FR], il numero reale [12,78] è valido, mentre non lo è con l'impostazione di lingua [en-US]. È quindi necessario scrivere [12.78]. Allo stesso modo, la data [12/01/2014] è valida nella cultura [fr-FR], mentre nella cultura [en-US] occorre scrivere [01/12/2014]. I file nella cartella [jquery / globalize] gestiscono questo tipo di problemi:
![]() |
L'internazionalizzazione dei messaggi di errore è gestita esclusivamente a livello di server. Vedremo che la pagina HTML / jS contiene messaggi di errore corrispondenti alle impostazioni locali gestite dal server: in francese per la locale [fr_FR] e in inglese per la locale [en_US].
6.3.5. I file dei messaggi
La vista [vue-01.xml] utilizza i seguenti messaggi internazionalizzati:
![]() |
[messages_fr.properties]
NotNull=Le champ est obligatoire
NotEmpty=La donnée ne peut être vide
NotBlank=La donnée ne peut être vide
typeMismatch=Format invalide
Future.form01.dateInFuture=La date doit être postérieure ou égale à celle d''aujourd'hui
Past.form01.dateInPast=La date doit être antérieure ou égale à celle d''aujourd'hui
Min.form01.intMin10=La valeur doit être supérieure ou égale à 10
Max.form01.intMax100=La valeur doit être inférieure ou égale à 100
Size.form01.strBetween4and6=La chaîne doit avoir entre 4 et 6 caractères
Length.form01.str4=La chaîne doit avoir quatre caractères exactement
Email.form01.email=Adresse mail invalide
URL.form01.url=URL invalide
Range.form01.int1014=La valeur doit être dans l''intervalle [10,14]
AssertTrue=Seule la valeur True est acceptée
AssertFalse=Seule la valeur False est acceptée
Pattern.form01.hhmmss=Tapez l''heure sous la forme hh:mm:ss
form01.hhmmss.pattern=^\\d{2}:\\d{2}:\\d{2}$
DateInvalide.form01=Date invalide
form01.str4.pattern=^.{4,4}$
form01.int1014.max=14
form01.int1014.min=10
form01.strBetween4and6.pattern=^.{4,6}$
form01.intMax100.value=100
form01.intMin10.value=10
form01.double1.min=2.3
form01.double1.max=3.4
Range.form01.double1=La valeur doit être dans l'intervalle [2,3-3,4]
form01.title=Formulaire - Validations côté client - locale=
form01.col1=Contrainte
form01.col2=Saisie
form01.col3=Validation client
form01.col4=Validation serveur
form01.valider=Valider
form01.double2=[double2+double1] doit être dans l''intervalle [{0},{1}]
form01.double3=[double3+double1] doit être dans l''intervalle [{0},{1}]
locale.fr=Français
locale.en=English
client.validation.true=Activer la validation client
client.validation.false=Inhiber la validation client
DecimalMin.form01.double1=Le nombre doit être supérieur ou égal à 2,3
DecimalMax.form01.double1=Le nombre doit être inférieur ou égal à 3,4
server.error.message=Erreurs détectées par les validateurs côté serveur
[messages_en.properties]
NotNull=Field is required
NotEmpty=Field can''t be empty
NotBlank=Field can''t be empty
typeMismatch=Invalid format
Future.form01.dateInFuture=Date must be greater or equal to today''s date
Past.form01.dateInPast=Date must be lower or equal today''s date
Min.form01.intMin10=Value must be higher or equal to 10
Max.form01.intMax100=Value must be lower or equal to 100
Size.form01.strBetween4and6=String must have between 4 and 6 characters
Length.form01.str4=String must be exactly 4 characters long
Email.form01.email=Invalid mail address
URL.form01.url=Invalid URL
Range.form01.int1014=Value must be in [10,14]
AssertTrue=Only value True is allowed
AssertFalse=Only value False is allowed
Pattern.form01.hhmmss=Time must follow the format hh:mm:ss
form01.hhmmss.pattern=^\\d{2}:\\d{2}:\\d{2}$
DateInvalide.form01=Invalid Date
form01.str4.pattern=^.{4,4}$
form01.int1014.max=14
form01.int1014.min=10
form01.strBetween4and6.pattern=^.{4,6}$
form01.intMax100.value=100
form01.intMin10.value=10
form01.double1.min=2.3
form01.double1.max=3.4
Range.form01.double1=Value must be in [2.3,3.4]
form01.title=Form - Client side validation - locale=
form01.col1=Constraint
form01.col2=Input
form01.col3=Client validation
form01.col4=Server validation
form01.valider=Validate
form01.double2=[double2+double1] must be in [{0},{1}]
form01.double3=[double3+double1] must be in [{0},{1}]
locale.fr=Français
locale.en=English
client.validation.true=Activate client validation
client.validation.false=Inhibate client validation
DecimalMin.form01.double1=Value must be greater or equal to 2.3
DecimalMax.form01.double1=Value must be lower or equal to 3.4
server.error.message=Errors detected by the validators on the server side
Il file [messages.properties] è una copia del file dei messaggi in inglese. Di conseguenza, qualsiasi localizzazione diversa da [fr] utilizzerà i messaggi in inglese. Si ricorda che il file [messages_fr.properties] viene utilizzato per tutte le impostazioni locali [fr_XX], quali [fr_CA] o [fr_FR].
La vista [vue-01.xml] utilizza le chiavi di questi messaggi. Se desidera conoscere il valore associato a tali chiavi, il lettore è invitato a tornare a questo paragrafo per scoprirlo.
6.3.6. Modifica delle impostazioni locali
La vista [vue-01.xml] presenta quattro collegamenti:
<body>
<!-- titolo -->
<h3>
<span th:text="#{form01.title}"></span>
<span th:text="${locale}"></span>
</h3>
<!-- menu -->
<p>
<a id="locale_fr" href="javascript:setLocale('fr_FR')">
<span th:text="#{locale.fr}"></span>
</a>
<a id="locale_en" href="javascript:setLocale('en_US')">
<span style="margin-left:30px" th:text="#{locale.en}"></span>
</a>
<a id="clientValidationTrue" href="javascript:setClientValidation(true)">
<span style="margin-left:30px" th:text="#{client.validation.true}"></span>
</a>
<a id="clientValidationFalse" href="javascript:setClientValidation(false)">
<span style="margin-left:30px" th:text="#{client.validation.false}"></span>
</a>
</p>
<!-- modulo -->
<form action="/someURL" th:action="@{/js02.html}" method="post" th:object="${form01}" name="form" id="form">
...
alcuni dei quali sono riportati di seguito [1]:
![]() |
Esaminiamo i due link che consentono di cambiare l'impostazione locale in francese o in inglese:
<a id="locale_fr" href="javascript:setLocale('fr_FR')">
<span th:text="#{locale.fr}"></span>
</a>
<a id="locale_en" href="javascript:setLocale('en_US')">
<span style="margin-left:30px" th:text="#{locale.en}"></span>
</a>
Cliccando su questi link viene eseguito uno script jS presente nel file [local.js] [2]. In entrambi i casi, viene chiamata una funzione jS [setLocale]:
// impostazioni locali
function setLocale(locale) {
// si aggiorna la locale
lang.val(locale);
// si invia il modulo - ciò non attiva i validatori del client - ecco perché non è stata disabilitata la convalida lato client
document.form.submit();
}
Per comprendere la riga 4 è necessaria una premessa. La vista [vue-01.xml] contiene un campo nascosto denominato [lang]:
<input type="hidden" th:field="*{lang}" th:value="*{lang}" value="true" />
che corrisponde a un campo [lang] in [Form01]:
// locale
private String lang;
I campi nascosti sono utili quando si desidera arricchire i valori inviati. Il JavaScript consente di assegnare loro un valore e tale valore viene inviato come un normale inserimento effettuato dall’utente. Il codice HTML generato da Thymeleaf è il seguente:
<input type="hidden" value="en_US" id="lang" name="lang" />
Il valore del parametro [value] è quello del campo [Form01.lang] al momento della generazione di HTML. È importante notare l’identificatore jS del nodo [id="lang"]. Questo identificatore viene utilizzato dalla seguente funzione []:
// variabili globali
var lang;
// documento pronto
$(document).ready(function() {
// riferimenti globali
lang = $("#lang");
});
// locale
function setLocale(locale) {
// si aggiorna la variabile locale
lang.val(locale);
// si invia il modulo - per un motivo sconosciuto ciò non attiva i validatori del client
// ecco perché non è stata disabilitata la convalida
document.form.submit();
}
- righe 5-8: la funzione jS [$(document).ready(f)] è una funzione che viene eseguita quando il browser ha caricato l’intero documento inviato dal server. Il suo parametro è una funzione. Si utilizza la funzione jS [$(document).ready(f)] per inizializzare l’ambiente jS del documento caricato;
- riga 7: l’espressione [$("#lang")] è un’espressione jQuery. Il suo valore è un riferimento al nodo DOM dell’attributo [id='lang'];
- riga 2: le variabili dichiarate al di fuori di una funzione sono globali rispetto alle funzioni. In questo caso, ciò significa che la variabile [lang] inizializzata in [$(document).ready()] è nota anche nella funzione [setLocale] della riga 11;
- riga 13: modifica l'attributo [value] del nodo identificato da [lang]. Se lang è uguale a [xx_XX], allora il tag HTML del nodo diventa:
<input type="hidden" value="xx_XX" id="lang" name="lang" />
Il codice JavaScript consente di modificare il valore degli elementi di DOM (Document Object Model).
- riga 16: [document] indica il DOM. [document.form] indica il primo modulo trovato in questo documento. Un documento HTML può contenere più tag <form> e quindi più moduli. In questo caso ne abbiamo solo uno. [document.form.submit] invia questo modulo come se l'utente avesse cliccato su un pulsante con l'attributo [type='submit']. A quale azione vengono inviati i valori del modulo? Per scoprirlo, occorre esaminare il tag [form] del modulo in [vue-01.xml]:
<!-- modulo -->
<form action="/someURL" th:action="@{/js02.html}" method="post" th:object="${form01}" name="form" id="form">
L'azione che riceverà i valori inviati è quella indicata dall'attributo [th:action]. Si tratterà quindi dell'azione [/js02.html]. Si ricorda che in questo nome il suffisso [.html] verrà rimosso e, alla fine, sarà l’azione [/js02] a essere eseguita. È importante comprendere che il nuovo valore [xx_XX] del nodo [lang] verrà inviato nella forma [lang=xx_XX]. Tuttavia, abbiamo configurato la nostra applicazione per intercettare il parametro [lang] e interpretarlo come una modifica delle impostazioni locali. Pertanto, sul lato server, le impostazioni locali diventeranno [xx_XX]. Esaminiamo l’azione [/js02] che verrà eseguita:
@RequestMapping(value = "/js02", method = RequestMethod.POST, produces = "text/html; charset=UTF-8")
public String js02(@Valid Form01 formulaire, BindingResult result, RedirectAttributes redirectAttributes, Locale locale, Model model) {
Form01Validator validator = new Form01Validator(10, 13);
validator.validate(formulaire, result);
if (result.hasErrors()) {
StringBuffer buffer = new StringBuffer();
for (ObjectError error : result.getAllErrors()) {
buffer.append(String.format("[name=%s,code=%s,message=%s]", error.getObjectName(), error.getCode(),
error.getDefaultMessage()));
}
setModel(formulaire, model, locale, buffer.toString());
return "vue-01";
} else {
redirectAttributes.addFlashAttribute("form01", formulaire);
return "redirect:/js01.html";
}
}
// preparazione del modello della vista vista-01
private void setModel(Form01 formulaire, Model model, Locale locale, String message) {
// si gestiscono solo le impostazioni locali fr-FR, en-US
String language = locale.getLanguage();
String country = null;
if (language.equals("fr")) {
country = "FR";
formulaire.setLang("fr_FR");
}
if (language.equals("en")) {
country = "US";
formulaire.setLang("en_US");
}
model.addAttribute("locale", String.format("%s-%s", language, country));
...
}
- riga 2: l’azione [/js02] riceverà la nuova impostazione locale [xx_XX] incapsulata nel parametro [Locale locale]:
- righe 5-12: se alcuni dei valori inviati non sono validi, verrà visualizzata la vista [vue-01.xml] con messaggi di errore che utilizzano la nuova locale [xx_XX]. Inoltre, la riga 11 fa sì che la variabile [locale=xx-XX] venga inserita nel modello. Sul lato client, questo valore verrà utilizzato per aggiornare le impostazioni locali sul lato client. Ne abbiamo descritto il processo;
- righe 14-15: se i valori inviati sono tutti validi, si verifica un reindirizzamento all’azione successiva [/js01]:
@RequestMapping(value = "/js01", method = RequestMethod.GET, produces = "text/html; charset=UTF-8")
public String js01(Form01 formulaire, Locale locale, Model model) {
setModel(formulaire, model, locale, null);
return "vue-01";
}
- riga 2, viene inserita la nuova impostazione locale [xx_XX];
- riga 3: il metodo [setModel] imposterà quindi la cultura del client su [xx-XX];
Ora osserviamo l’influenza delle impostazioni locali nella vista [vue-01.xml]. Per il momento non l’abbiamo presentata nella sua interezza poiché conta più di 300 righe. Tuttavia, la maggior parte delle righe consiste nella ripetizione di una sequenza simile alla seguente:
<!-- obbligatorio -->
<tr>
<td class="col1">required</td>
<td class="col2">
<input type="text" th:field="*{strNotEmpty}" data-val="true" th:attr="data-val-required=#{NotNull}" />
</td>
<td class="col3">
<span class="field-validation-valid" data-valmsg-for="strNotEmpty" data-valmsg-replace="true"></span>
</td>
<td class="col4">
<span th:if="${#fields.hasErrors('strNotEmpty')}" th:errors="*{strNotEmpty}" class="error">Donnée erronée</span>
</td>
</tr>
Questo codice visualizza il seguente frammento [1]:
![]() |
Il messaggio di errore [2] deriva dall’attributo [th:attr="data-val-required=#{NotNull}"] della riga 5. [#{NotNull}] è un messaggio localizzato. A seconda delle impostazioni di localizzazione sul lato server, la riga 5 genera il tag:
<input type="text" data-val="true" data-val-required="Field is required" id="strNotEmpty" name="strNotEmpty" />
oppure il tag:
<input type="text" data-val="true" data-val-required="Le champ est obligatoire" id="strNotEmpty" name="strNotEmpty" />
Gli attributi [data-x] vengono gestiti dalla libreria di validazione jS.
In definitiva, va ricordato che i due link per la modifica delle impostazioni locali:
- provocano un POST dei valori inseriti;
- modificano le impostazioni locali sia sul lato server che sul lato client;
- generano una pagina HTML che include i messaggi di errore destinati alla libreria di validazione jS e che tali messaggi sono nella lingua delle impostazioni locali selezionate;
6.3.7. Il POST dei valori inseriti
Esaminiamo il pulsante [Valider] che invia i valori inseriti nella vista [vue-01.xml]. Il suo codice HTML è il seguente:
<!-- pulsante di conferma -->
<input type="submit" value="Valider" onclick="javascript:postForm01()" />
Se JavaScript è attivo nel browser, cliccando sul pulsante si attiverà l'esecuzione del metodo [postForm01]. Se questa funzione restituisce il valore booleano [False], allora il metodo submit non verrà eseguito. Se restituisce un altro valore, allora verrà eseguito. Questa funzione si trova nel file [local.js]:
![]() |
Viene importata dalla vista [vue-01.xml] tramite la riga 6 riportata di seguito:
<head>
<title>Spring 4 MVC</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<link rel="stylesheet" href="/css/form01.css" />
...
<script type="text/javascript" src="/js/local.js"></script>
</head>
In questo file si trova il codice seguente:
// variabili globali
var formulaire;
var clientValidation;
var double1;
var double2;
var double3;
...
$(document).ready(function() {
// riferimenti globali
formulaire = $("#form");
clientValidation = $("#clientValidation");
double1 = $("#double1");
double2 = $("#double2");
double3 = $("#double3");
...
});
....
// invio del modulo
function postForm01() {
...
}
- righe 8-16: la funzione jS [$(document).ready(f)] è una funzione che viene eseguita quando il browser ha caricato l'intero documento inviato dal server. Il suo parametro è una funzione. Si utilizza la funzione jS [$(document).ready(f)] per inizializzare l’ambiente jS del documento caricato;
- righe 10-14: per comprendere queste righe, è necessario esaminare sia il codice Thymeleaf che il codice HTML generato;
Il codice Thymeleaf in questione è il seguente:
<form action="/someURL" th:action="@{/js02.html}" method="post" th:object="${form01}" name="form" id="form">
...
<input type="text" th:field="*{double1}" th:value="*{double1}" ... />
...
<input type="text" th:field="*{double2}" th:value="*{double2}" />
...
<input type="text" th:field="*{double3}" th:value="*{double3}" ... />
...
<input type="hidden" th:field="*{clientValidation}" th:value="*{clientValidation}" value="true" />
che genera il seguente codice HTML:
<form action="/js02.html" method="post" name="form" id="form">
...
<input type="text" id="double1" name="double1" .../>
....
<input type="text" value="" id="double2" name="double2" />
...
<input value="" id="double3" name="double3" .../>
...
<input type="hidden" value="false" id="clientValidation" name="clientValidation" />
Ogni attributo [th:field='x'] genera due attributi: HTML, [name='x'] e [id='x']. L'attributo [name] è il nome dei valori inviati. Pertanto, la presenza degli attributi [name='x'] e [value='y'] per un tag HTML <input type='text'> inserirà la stringa x=y nei valori inviati name1=val1&name2=val2&... L'attributo [id='x'] viene invece utilizzato dal JavaScript. Serve a identificare un elemento del DOM (Document Object Model). Il documento HTML caricato viene infatti trasformato in un albero JavaScript denominato DOM, in cui ogni nodo è identificato dal proprio attributo [id].
Torniamo al codice della funzione [$(document).ready()]:
// variabili globali
var formulaire;
var clientValidation;
var double1;
var double2;
var double3;
...
$(document).ready(function() {
// riferimenti globali
formulaire = $("#form");
clientValidation = $("#clientValidation");
double1 = $("#double1");
double2 = $("#double2");
double3 = $("#double3");
...
});
....
// invio del modulo
function postForm01() {
...
}
- riga 10: l’espressione [$("#form")] è un’espressione jQuery. Il suo valore è un riferimento al nodo di DOM con attributo [id='form '];
- righe 10-14: si recuperano i riferimenti a cinque nodi di DOM;
- righe 2-6: le variabili dichiarate al di fuori di una funzione sono globali rispetto alle funzioni. In questo caso, ciò significa che le variabili [formulaire, clientValidation , double1, double2, double3] inizializzate in [$(document).ready()] saranno note anche nella funzione [postForm01] alla riga 19;
Ora esaminiamo la funzione [postForm01]:
// invio del modulo
function postForm01() {
// modalità di convalida lato client
var validationActive = clientValidation.val() === "true";
if (validationActive) {
// si cancellano gli errori dal server
clearServerErrors();
// convalida del modulo
if (!formulaire.validate().form()) {
// nessun invio
return false;
}
}
// valori reali in formato anglosassone
var value1 = double1.val().replace(",", ".");
double1.val(value1);
var value2 = double2.val().replace(",", ".");
double2.val(value2);
var value3 = double3.val().replace(",", ".");
double3.val(value3);
// si lascia che l'invio avvenga
return true;
}
Ricordiamo che questa funzione jS viene eseguita prima della funzione [submit] del modulo. Se restituisce il valore booleano [false] (riga 11), l'invio non avrà luogo. Se restituisce un altro valore (riga 22), l'invio avrà luogo.
- Il codice rilevante è quello delle righe 4-12;
- riga 4: si recupera il valore del campo nascosto [clientValidation]. Questo valore è 'true' se la convalida lato client deve essere attivata, 'false' in caso contrario;
- riga 6: in caso di convalida lato client, si cancellano i messaggi di errore del server che potrebbero essere presenti perché l’utente ha appena cambiato le impostazioni locali;
- riga 9: ricordiamo che la variabile [formulaire] rappresenta il nodo del tag HTML <form>, ovvero il modulo. Quest'ultimo presenta dei validatori jS che non abbiamo ancora illustrato e che saranno oggetto dei paragrafi seguenti. L'espressione [formulaire.validate().form()] forza l'esecuzione di tutti i validatori jS presenti nel modulo. Il suo valore è [true] se i valori verificati sono tutti validi, [false] in caso contrario;
- riga 11: viene restituito il valore [false] se almeno uno dei valori verificati non è valido. Ciò impedirà l’invio del [submit] dal modulo al server;
- righe 15-20: gli identificatori [double1, double2, double3] rappresentano i tre numeri reali del modulo. A seconda della cultura, il valore inserito è diverso. Con la cultura [fr-FR], si inserisce [10,37], mentre con la cultura [en-US] si inserisce [10.37]. Questo per quanto riguarda l’inserimento. Con la cultura [fr-FR], il valore inviato per [double1] apparirà come [double1=10,37]. Una volta arrivato sul server, il valore [10,37] verrà rifiutato poiché il server si aspetta [10.37], il formato predefinito dei numeri reali in Java. Pertanto, alle righe 15-20, si sostituisce la virgola con il punto nel valore inserito per questi numeri;
- riga 15: l'espressione [double1.val()] restituisce la stringa inserita per il nodo [double1]. L'espressione [double1.val().replace(",", ".")] sostituisce in questa stringa le virgole con i punti. Il risultato è una stringa [value1];
- riga 16: l'istruzione [double1.val(value1)] assegna questo valore [value1] al nodo [double1].
Tecnicamente, se l’utente ha digitato [10,37] al posto del valore effettivo [double1], in seguito alle istruzioni precedenti il nodo [double1] avrà il valore [10.37] e il valore che verrà inviato sarà [param1=val1&double1=10.37¶m2=val2], valore che sarà accettato dal server;
- riga 22: si restituisce il valore [true] affinché venga eseguito il [submit] del modulo;
Si noti che la funzione jS [postForm01]:
- esegue tutti i validatori jS del modulo se la convalida lato client è attivata e impedisce l’invio del [submit] dal modulo al server se uno dei valori inseriti è stato dichiarato non valido;
- consente l'esecuzione del validatore [submit] sia perché la convalida lato client non è attivata, sia perché è attivata e tutti i valori inseriti sono validi;
Rimane l’istruzione della riga [3]:
// si cancellano gli errori del server
clearServerErrors();
La funzione [clearServerErrors] ha lo scopo di cancellare i messaggi presenti nella colonna 4 della vista [vue-01.xml]:
![]() |
Nello screenshot qui sopra, abbiamo cliccato sul link [English]. Abbiamo notato che ciò ha provocato un POST dei valori inseriti senza che venissero attivati i validatori jS. Al ritorno del POST, la colonna [Server Validation] viene popolata con eventuali messaggi di errore. Se ora si fa clic sul pulsante [Validate] [2] con i validatori jS attivati [3], allora la colonna [Client Validation] [4] si riempirà di messaggi. Se non si interviene, i messaggi presenti nella colonna [Server Validation] rimarranno lì, creando confusione poiché, in caso di errori rilevati dai validatori jS, il server non viene interpellato. Per evitare ciò, si cancella la colonna [Server Validation] nella funzione [postForm01]. È la funzione [] che svolge questo compito:
function clearServerErrors() {
// si cancellano i messaggi di errore del server
$(".error").each(function(index) {
$(this).text("");
});
}
Una particolarità dei messaggi di errore è che appartengono tutti alla classe [error]. Ad esempio, per la prima riga della tabella in [vue-01.html]:
<span th:if="${#fields.hasErrors('strNotEmpty')}" th:errors="*{strNotEmpty}" class="error">Donnée erronée</span>
E questi sono gli unici nodi di DOM che hanno questa classe. Utilizziamo questa proprietà nella funzione [clearServerErrors]:
function clearServerErrors() {
// si cancellano i messaggi di errore del server
$(".error").each(function(index) {
$(this).text("");
});
}
- riga 3: l'espressione [$(".error")] restituisce la collezione dei nodi del DOM che hanno la classe [error];
- riga 3: l'espressione [$(".error").each(function(index){f}] esegue la funzione [f] per ciascuno dei nodi della collezione. Essa riceve un parametro [index], che qui non viene utilizzato e che rappresenta il numero del nodo nella collezione;
- riga 4: l'espressione [$(this)] indica il nodo corrente nell'iterazione. Si tratta di un tag HTML <span>. L'espressione [$(this).text("")] assegna la stringa vuota al testo visualizzato dal tag <span>;
Esamineremo ora diversi validatori jS.
6.3.8. Validatore [required]
Esaminiamo il primo elemento del modulo:
![]() |
La riga [1] viene generata dalla seguente sequenza della vista [vue-01.xml]:
<!-- obbligatorio -->
<tr>
<td class="col1">required</td>
<td class="col2">
<input type="text" th:field="*{strNotEmpty}" data-val="true" th:attr="data-val-required=#{NotNull}" />
</td>
<td class="col3">
<span class="field-validation-valid" data-valmsg-for="strNotEmpty" data-valmsg-replace="true"></span>
</td>
<td class="col4">
<span th:if="${#fields.hasErrors('strNotEmpty')}" th:errors="*{strNotEmpty}" class="error">Donnée erronée</span>
</td>
</tr>
Queste righe riguardano il campo [strNotEmpty] del modulo [Form01]:
@NotNull
@NotBlank
private String strNotEmpty;
I vincoli [1-2] impongono che il campo [strNotEmpty] sia una stringa esistente [NotNull], non vuota e non costituita esclusivamente da spazi [NotBlank]. Si desidera riprodurre questo vincolo sul lato client utilizzando JavaScript.
Esaminiamo le righe 5 e 8. La riga 11 non presenta alcun problema. Visualizza il messaggio di errore relativo al campo [strNotEmpty]. Cominciamo dalla riga 5:
<input type="text" th:field="*{strNotEmpty}" data-val="true" th:attr="data-val-required=#{NotNull}" />
A partire da questo codice, Thymeleaf genererà il seguente tag:
<input type="text" data-val="true" data-val-required="Field is required" id="strNotEmpty" name="strNotEmpty" value="x" />
- l’attributo [data-val='true'] è utilizzato dalle librerie di validazione jQuery. La sua presenza indica che il valore del nodo è soggetto a validazione;
- l'attributo [data-val-X='msg'] fornisce due informazioni. [X] è il nome del validatore, [msg] è il messaggio di errore associato a un valore non valido del nodo su cui opera il validatore. Si tratta solo di un'informazione. Ciò non comporta la visualizzazione del messaggio di errore;
- [required] è un validatore riconosciuto dalla libreria di validazione [jquery.validate.unobstrusive] di Microsoft. Non è necessario definirlo. Non sarà sempre così in seguito;
- i tag [data-x] vengono ignorati da HTML5. Sono utili solo se è presente del codice JavaScript per gestirli;
Esaminiamo ora la riga 8:
<span class="field-validation-valid" data-valmsg-for="strNotEmpty" data-valmsg-replace="true"></span>
Serve a visualizzare il messaggio di errore del validatore [required]. In caso di errore, la libreria di validazione jS sostituirà dinamicamente la riga HTML della tabella con il seguente codice:
<tr>
<td class="col1">required</td>
<td class="col2">
<input type="text" data-val="true" data-val-required="Le champ est obligatoire" id="strNotEmpty" name="strNotEmpty" value="" aria-required="true" aria-invalid="true" aria-describedby="strNotEmpty-error" class="input-validation-error">
</td>
<td class="col3">
<span class="field-validation-error" data-valmsg-for="strNotEmpty" data-valmsg-replace="true">
<span id="strNotEmpty-error" class="">Le champ est obligatoire</span>
</span>
</td>
<td class="col4">
<span class="error"></span>
</td>
</tr>
</tr>
- riga 4: la classe del nodo [strNotEmpty] è cambiata. È diventata [input-validation-error], il che fa sì che il campo errato venga evidenziato in rosso;
- riga 7: la classe del nodo [span] è cambiata. È diventata [field-validation-error], il che farà visualizzare il testo del nodo [span] in rosso;
- riga 8: il [span], che prima era vuoto, ora contiene il testo [Le champ est obligatoire]. Questo testo proviene dal tag [data-val-required="Le champ est obligatoire"] della riga 4;
- riga 7: per visualizzare il messaggio di errore del nodo [strNotEmpty] della riga 4, è necessario utilizzare nella riga 7 gli attributi [data-valmsg-for="strNotEmpty"] e [data-valmsg-replace="true"];
6.3.9. Validatore [assertfalse]
![]() |
La riga [1] viene generata dalla seguente sequenza della vista [vue-01.xml]:
<!-- obbligatorio, assertfalse -->
<tr>
<td class="col1">required, assertfalse</td>
<td class="col2">
<input type="radio" th:field="*{assertFalse}" value="true" data-val="true"
th:attr="data-val-required=#{NotNull},data-val-assertfalse=#{AssertFalse}" />
<label th:for="${#ids.prev('assertFalse')}">true</label>
<input type="radio" th:field="*{assertFalse}" value="false" data-val="true"
th:attr="data-val-required=#{NotNull},data-val-assertfalse=#{AssertFalse}" />
<label th:for="${#ids.prev('assertFalse')}">false</label>
</td>
<td class="col3">
<span class="field-validation-valid" data-valmsg-for="assertFalse" data-valmsg-replace="true"></span>
</td>
<td class="col4">
<span th:if="${#fields.hasErrors('assertFalse')}" th:errors="*{assertFalse}" class="error">Donnée erronée</span>
</td>
</tr>
Queste righe riguardano il campo [assertFalse] del modulo [Form01]:
@NotNull
@AssertFalse
private Boolean assertFalse;
Si desidera riprodurre questo vincolo sul lato client con JavaScript. Le righe 12-17 sono ormai standard:
- righe 12-14: in caso di errore nel campo [assertFalse], visualizzano il messaggio contenuto nell’attributo [data-val-assertfalse] della riga 6 oppure quello contenuto nell’attributo [data-val-required] della stessa riga. Si ricorda che questi messaggi sono localizzati, ovvero nella lingua precedentemente scelta dall’utente o in francese se non ha effettuato alcuna scelta;
- righe 5-10: visualizzano i pulsanti di opzione con validatori JavaScript che vengono attivati non appena l’utente clicca su uno di essi.
Entrambi i pulsanti sono strutturati allo stesso modo. Esaminiamo il primo:
<input type="radio" th:field="*{assertFalse}" value="true" data-val="true" th:attr="data-val-required=#{NotNull},data-val-assertfalse=#{AssertFalse}" />
Una volta elaborata da Thymeleaf, questa riga diventa la seguente:
<input type="radio" value="true" data-val="true" data-val-required="Le champ est obligatoire" data-val-assertfalse="Seule la valeur False est acceptée" id="assertFalse1" name="assertFalse" />
Abbiamo dei validatori [data-val="true"]. Ce ne sono due. Un validatore denominato [required] [data-val-required="Le champ est obligatoire"] e un altro denominato [assertfalse] [data-val-assertfalse="Seule la valeur False est acceptée"]. Ricordiamo che il valore dell’attributo [data-val-X] è il messaggio di errore del validatore X.
Abbiamo visto il validatore [required]. La novità in questo caso è che è possibile associare più validatori a un valore inserito. Se il validatore [required] è riconosciuto dalla libreria di validazione MS (Microsoft), lo stesso non vale per il validatore [assertFalse]. Impareremo quindi a creare un nuovo validatore. Ne creeremo diversi e li inseriremo in un file [client-validation.js]:
![]() |
Questo file, come gli altri, viene importato dalla vista [vue-01.xml] (riga 6 qui sotto):
<head>
<title>Spring 4 MVC</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<link rel="stylesheet" href="/css/form01.css" />
...
<script type="text/javascript" src="/js/client-validation.js"></script>
...
</head>
L'aggiunta del validatore [assertfalse] consiste semplicemente nella creazione delle due seguenti funzioni jS:
// -------------- assertfalse
$.validator.addMethod("assertfalse", function(value, element, param) {
return value === "false";
});
$.validator.unobtrusive.adapters.add("assertfalse", [], function(options) {
options.rules["assertfalse"] = options.params;
options.messages["assertfalse"] = options.message.replace("''", "'");
});
In tutta onestà, non sono uno specialista di JavaScript, un linguaggio che per me rimane ancora piuttosto oscuro. Le sue basi sono semplici, ma le librerie costruite su di esse sono spesso molto complesse. Per scrivere le righe di codice sopra riportate, mi sono ispirato a codici trovati su Internet. È stato il link [http://jsfiddle.net/LDDrk/] a indicarmi la strada da seguire. Se è ancora disponibile, invito il lettore a consultarlo poiché è completo e include un esempio funzionante. Mostra come creare un nuovo validatore e mi ha permesso di creare tutti quelli di questo capitolo. Torniamo al codice:
- righe 2-4: definiscono il nuovo validatore. La funzione [$.validator.addMethod] accetta come primo parametro il nome del validatore e come secondo parametro una funzione che lo definisce;
- riga 2: la funzione ha tre parametri:
- [value]: il valore da validare. La funzione deve restituire [true] se il valore è valido, [false] in caso contrario,
- [element]: elemento HTML a cui appartiene il valore da convalidare,
- [param]: un oggetto contenente i valori associati ai parametri di un validatore. Non abbiamo ancora introdotto questo concetto. In questo caso il validatore [assertFalse] non ha parametri. È possibile stabilire se il valore [value] sia valido senza ricorrere a informazioni aggiuntive. La situazione sarebbe diversa se si dovesse verificare che il valore [value] fosse un numero reale compreso nell’intervallo [min, max]. In quel caso, dovremmo conoscere [min] e [max]. Questi due valori sono detti parametri del validatore;
- righe 6-9: una funzione necessaria alla libreria di validazione MS. La funzione [$.validator.unobtrusive.adapters.add] richiede come primo parametro il nome del validatore, come secondo parametro l’array dei parametri del validatore e come terzo parametro una funzione;
- il validatore [assertFalse] non ha parametri. Per questo motivo il secondo parametro è un array vuoto;
- la funzione ha un solo parametro, un oggetto [options] che contiene informazioni sull’elemento da convalidare e per il quale è necessario definire due nuove proprietà: [rules] e [messages];
- riga 7: si definiscono le regole [rules] per il validatore [assertFalse]. Queste regole costituiscono i parametri del validatore [assertFalse], gli stessi del parametro [param] della riga 2. Tali parametri si trovano in [options.params];
- riga 8: definiscono il messaggio di errore del validatore [assertFalse]. Questo si trova in [options.message]. Si riscontra la seguente difficoltà con i messaggi di errore. Nei file dei messaggi si troverà il seguente messaggio:
Range.form01.int1014=La valeur doit être dans l''intervalle [10,14]
Il doppio apostrofo è necessario per Thymeleaf, che lo interpreta come un apostrofo singolo. Se si inserisce un apostrofo singolo, Thymeleaf non lo visualizza. Ora questi messaggi serviranno anche come messaggi di errore per la libreria di validazione MS. Tuttavia, il codice JavaScript visualizzerà entrambi gli apostrofi. Alla riga 8, sostituiamo quindi il doppio apostrofo del messaggio di errore con un singolo apostrofo.
Per capire meglio cosa succede, possiamo aggiungere del codice di log jS:
// log
var logs = {
assertfalse : true
}
// -------------- assertfalse
$.validator.addMethod("assertfalse", function(value, element, param) {
// log
if (logs.assertfalse) {
console.log(jSON.stringify({
"[assertfalse] value" : value
}));
console.log("[assertfalse] element");
console.log(element);
console.log(jSON.stringify({
"[assertfalse] param" : param
}));
}
// verifica validità
return value === "false";
});
$.validator.unobtrusive.adapters.add("assertfalse", [], function(options) {
// log
if (logs.assertfalse) {
console.log(jSON.stringify({
"[assertfalse] options.params" : options.params
}));
console.log(jSON.stringify({
"[assertfalse] options.message" : options.message
}));
console.log(jSON.stringify({
"[assertfalse] options.messages" : options.messages
}));
}
// codice
options.rules["assertfalse"] = options.params;
options.messages["assertfalse"] = options.message.replace("''", "'");
});
Questo codice utilizza la libreria jSON JSON3 [http://bestiejs.github.io/json3/]. Se si attivano i log (riga 3), nella console vengono visualizzati i seguenti messaggi:
Al caricamento iniziale della pagina, si ottengono i seguenti log:
È stata eseguita la funzione jS [$.validator.unobtrusive.adapters.add]. Si ricava quanto segue:
- [options.params] è un oggetto vuoto poiché il validatore [assertFalse] non ha parametri;
- [options.message] è il messaggio di errore che abbiamo creato per il validatore [assertFalse] nell’attributo [data-val-assertFalse];
- [options.messages] è un oggetto che contiene gli altri messaggi di errore dell'elemento validato. Qui ritroviamo il messaggio di errore che abbiamo inserito nell'attributo [data-val-required];
Ora inseriamo un valore errato nel campo [assertFalse] e proviamo a convalidare:
Si ottengono quindi i seguenti log:
![]() |
Qui si notano le seguenti informazioni:
- il valore testato è [true] (riga 118);
- l'elemento HTML sottoposto a test è il pulsante di opzione con id [assertFalse1] (riga 122);
- il validatore [assertFalse] non ha parametri (riga 123);
Ecco fatto. Cosa possiamo trarre da tutto questo?
Per un validatore X jS, dobbiamo definire:
- nel tag HTML da validare, l’attributo [data-val-X='msg'] che definisce sia il validatore X che il relativo messaggio di errore;
- due funzioni jS da inserire nel file [client-validation.js]:
- [$.validator.addMethod("X", function(value, element, param)],
- [$.validator.unobtrusive.adapters.add("X", [param1, param2], function(options)];
Successivamente, faremo riferimento a quanto già realizzato per questo primo validatore e ci limiteremo a presentare le novità.
6.3.10. Validatore [asserttrue]
Questo validatore è ovviamente analogo al validatore [assertFalse].
![]() |
La riga [1] viene generata dalla seguente sequenza della vista [vue-01.xml]:
<!-- obbligatorio, asserttrue -->
<tr>
<td class="col1">asserttrue</td>
<td class="col2">
<select th:field="*{assertTrue}" data-val="true" th:attr="data-val-asserttrue=#{AssertTrue}">
<option value="true">True</option>
<option value="false">False</option>
</select>
</td>
<td class="col3">
<span class="field-validation-valid" data-valmsg-for="assertTrue" data-valmsg-replace="true"></span>
</td>
<td class="col4">
<span th:if="${#fields.hasErrors('assertTrue')}" th:errors="*{assertTrue}" class="error">Donnée erronée</span>
</td>
</tr>
Queste righe riguardano il campo [assertTrue] del modulo [Form01]:
@NotNull
@AssertTrue
private Boolean assertTrue;
Non vi sono novità nelle righe da 1 a 16. Esse utilizzano un validatore [asserrtrue] che deve essere definito nel file [client-validation.js]:
// -------------- asserttrue
$.validator.addMethod("asserttrue", function(value, element, param) {
return value === "true";
});
$.validator.unobtrusive.adapters.add("asserttrue", [], function(options) {
options.rules["asserttrue"] = options.params;
options.messages["asserttrue"] = options.message.replace("''", "'");
});
6.3.11. Validatori [date] e [past]
![]() |
La riga [1] viene generata dalla seguente sequenza della vista [vue-01.xml]:
<!-- obbligatorio, data, passata -->
<tr>
<td class="col1">required, date, past</td>
<td class="col2">
<input type="date" th:field="*{dateInPast}" th:value="*{dateInPast}" data-val="true"
th:attr="data-val-required=#{NotNull},data-val-date=#{DateInvalide.form01},data-val-past=#{Past.form01.dateInPast}" />
</td>
<td class="col3">
<span class="field-validation-valid" data-valmsg-for="dateInPast" data-valmsg-replace="true"></span>
</td>
<td class="col4">
<span th:if="${#fields.hasErrors('dateInPast')}" th:errors="*{dateInPast}" class="error">Donnée erronée</span>
</td>
</tr>
Queste righe riguardano il campo [dateInPast] del modulo [Form01]:
@NotNull
@Past
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date dateInPast;
La riga relativa ai validatori della data è la seguente:
<input type="date" th:field="*{dateInPast}" th:value="*{dateInPast}" data-val="true" th:attr="data-val-required=#{NotNull},data-val-date=#{DateInvalide.form01},data-val-past=#{Past.form01.dateInPast}" />
In essa sono presenti tre validatori [data-val-X]: required, date, past. Dobbiamo definire in [client-validation.js] le funzioni associate a questi due nuovi validatori:
logs.date = true;
// -------------- data
$.validator.addMethod("date", function(value, element, param) {
// validità
var valide = Globalize.parseDate(value, "yyyy-MM-dd") != null;
// log
if (logs.date) {
console.log(jSON.stringify({
"[date] value" : value,
"[date] valide" : valide
}));
}
// risultato
return valide;
});
$.validator.unobtrusive.adapters.add("date", [], function(options) {
options.rules["date"] = options.params;
options.messages["date"] = options.message.replace("''", "'");
});
e
logs.past = true;
// -------------- passato
$.validator.addMethod("past", function(value, element, param) {
// validità
var valide = value <= new Date().toISOString().substring(0, 10);
// log
if (logs.past) {
console.log(jSON.stringify({
"[past] value" : value,
"[past] valide" : valide
}));
}
// risultato
return valide;
});
$.validator.unobtrusive.adapters.add("past", [], function(options) {
options.rules["past"] = options.params;
options.messages["past"] = options.message.replace("''", "'");
});
Prima di spiegare il codice, diamo un’occhiata ai log quando si inserisce una data successiva a quella odierna:
La prima cosa da notare è che la data da convalidare viene fornita come stringa di caratteri nel formato [aaaa-mm-jj]. Ciò spiega le righe seguenti:
var valide = Globalize.parseDate(value, "yyyy-MM-dd") != null;
La libreria [globalize.js] fornisce la funzione [Globalize.parseDate] sopra riportata. Il primo parametro è la data come stringa di caratteri e il secondo il suo formato. Il risultato è un puntatore null se la data non è valida, altrimenti la data risultante.
La validità del validatore [past] viene verificata dal codice seguente:
var valide = value <= new Date().toISOString().substring(0, 10);
Ecco, su una console, la valutazione dell’espressione [new Date().toISOString().substring(0, 10)]:
![]() |
La stringa [value] deve precedere alfabeticamente la stringa [new Date().toISOString().substring(0, 10)] per essere valida.
Si noti che la versione di Chrome utilizzata fornisce la data nel formato [yyyy-mm-dd]. Per un browser in cui ciò non avvenisse, sarebbe necessario indicare esplicitamente all’utente di utilizzare questo formato di immissione.
6.3.12. Validatore [future]
![]() |
La riga [1] è generata dalla seguente sequenza della vista [vue-01.xml]:
<!-- richiesto, data, futuro -->
<tr>
<td class="col1">required, date, future</td>
<td class="col2">
<input type="date" th:field="*{dateInFuture}" th:value="*{dateInFuture}" data-val="true" th:attr="data-val-required=#{NotNull},data-val-date=#{DateInvalide.form01},data-val-future=#{Future.form01.dateInFuture}" />
</td>
<td class="col3">
<span class="field-validation-valid" data-valmsg-for="dateInFuture" data-valmsg-replace="true"></span>
</td>
<td class="col4">
<span th:if="${#fields.hasErrors('dateInFuture')}" th:errors="*{dateInFuture}" class="error">Donnée erronée</span>
</td>
</tr>
Queste righe riguardano il campo [dateInFuture] del modulo [Form01]:
@NotNull
@Future
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date dateInFuture;
- alla riga 5, compare un nuovo validatore [data-val-future];
Questo validatore è ovviamente molto simile al validatore [past]. Le due funzioni da aggiungere in [client-validation.js] sono le seguenti:
// -------------- futuro
$.validator.addMethod("future", function(value, element, param) {
var now = new Date().toISOString().substring(0, 10);
return value > now;
});
$.validator.unobtrusive.adapters.add("future", [], function(options) {
options.rules["future"] = options.params;
options.messages["future"] = options.message.replace("''", "'");
});
6.3.13. Validatori [int] e [max]
![]() |
La riga [1] viene generata dalla seguente sequenza della vista [vue-01.xml]:
<!-- obbligatorio, int, max(100) -->
<tr>
<td class="col1">required, int, max(100)</td>
<td class="col2">
<input type="text" th:field="*{intMax100}" th:value="*{intMax100}" data-val="true" th:attr="data-val-required=#{NotNull},data-val-int=#{typeMismatch},data-val-max=#{Max.form01.intMax100},data-val-max-value=#{form01.intMax100.value}" />
</td>
<td class="col3">
<span class="field-validation-valid" data-valmsg-for="intMax100" data-valmsg-replace="true"></span>
</td>
<td class="col4">
<span th:if="${#fields.hasErrors('intMax100')}" th:errors="*{intMax100}" class="error">Donnée erronée</span>
</td>
</tr>
Queste righe riguardano il campo [intMax100] del modulo [Form01]:
@NotNull
@Max(value = 100)
private Integer intMax100;
Alla riga 5 sono presenti due nuovi validatori: [int] e [max]. Quest’ultimo presenta un parametro: il valore massimo. Esaminiamo il codice HTML generato dalla riga 5:
<!-- obbligatorio, int, max(100) -->
<tr>
<td class="col1">required, int, max(100)</td>
<td class="col2">
<input type="text" data-val="true" data-val-int="Format invalide" data-val-max-value="100" data-val-required="Le champ est obligatoire" data-val-max="La valeur doit être inférieure ou égale à 100" value="" id="intMax100" name="intMax100" />
</td>
<td class="col3">
<span class="field-validation-valid" data-valmsg-for="intMax100" data-valmsg-replace="true"></span>
</td>
<td class="col4">
</td>
</tr>
Ricordiamo il significato dei diversi attributi di [data-X]:
- [data-val="true"] indica che all'elemento HTML sono associati dei validatori;
- [data-val-required] introduce il validatore [required] con il relativo messaggio;
- [data-val-int] introduce il validatore [int] con il relativo messaggio;
- [data-val-max] introduce il validatore [max] con il relativo messaggio;
- [data-val-max-value="100"] introduce un parametro denominato [value] per il validatore [max]. [100] è il valore di questo parametro. È la prima volta che ci imbattiamo nel concetto di parametri di un validatore.
Il file [client-validation.js] viene arricchito con il seguente validatore [int]:
logs.int = true;
// -------------- int
$.validator.addMethod("int", function(value, element, param) {
// validità
valide = /^\s*[-\+]?\s*\d+\s*$/.test(value);
// log
if (logs.int) {
console.log(jSON.stringify({
"[int] value" : value,
"[int] valide" : valide,
}));
}
// risultato
return valide;
});
$.validator.unobtrusive.adapters.add("int", [], function(options) {
options.rules["int"] = options.params;
options.messages["int"] = options.message.replace("''", "'");
});
- riga 5: si utilizza un'espressione regolare per verificare che la stringa [value] rappresenti effettivamente un numero intero. Questo può essere con segno;
Ecco alcuni esempi di log:
Il validatore [max] viene aggiunto come segue in [client-validation.js]
// -------------- max da utilizzare in combinazione con [int] o [number]
logs.max = true;
$.validator.addMethod("max", function(value, element, param) {
// log
if (logs.max) {
console.log(jSON.stringify({
"[max] value" : value,
"[max] param" : param
}));
}
// validità
var val = Globalize.parseFloat(value);
if (isNaN(val)) {
// log
if (logs.max) {
console.log(jSON.stringify({
"[max] valide" : true
}));
}
// risultato
return true;
}
var max = Globalize.parseFloat(param.value);
var valide = val <= max;
// log
if (logs.max) {
console.log(jSON.stringify({
"[max] valide" : valide
}));
}
// risultato
return valide;
});
$.validator.unobtrusive.adapters.add("max", [ "value" ], function(options) {
options.rules["max"] = options.params;
options.messages["max"] = options.message.replace("''", "'");
});
Esamineremo subito il caso del parametro [value] del validatore [max] introdotto dall’attributo [data-val-max-value="100"].
- alla riga 35, il parametro [value] è integrato nel secondo parametro della funzione [$.validator.unobtrusive.adapters.add];
- riga 3, l'oggetto [param] non sarà più vuoto, ma conterrà {"value":100};
Per comprendere il codice delle righe da 3 a 33, è necessario sapere che quando sono presenti più validatori sullo stesso elemento HTML:
- non si conosce l'ordine di esecuzione dei validatori;
- l’esecuzione dei validatori si interrompe non appena un validatore dichiara l’elemento non valido. È quindi il messaggio di errore di quest’ultimo che viene associato all’elemento non valido;
Analizziamo il codice:
- riga 12: si verifica che si tratti di un numero. Se il validatore [int] è stato eseguito prima del validatore [max], ciò è necessariamente vero poiché un valore non valido interrompe l’esecuzione dei validatori;
- righe 13-22: se non si ha un numero, significa che il validatore [int] non è stato ancora eseguito. Si indica quindi che il valore verificato è valido per consentire al validatore [int] di svolgere il proprio lavoro e dichiarare l'elemento non valido con il proprio messaggio di errore;
- righe 23-24: calcola la validità di [value];
Ecco alcuni log:
Valore inserito | log |
| |
| |
|
6.3.14. Validatore [min]
![]() |
La riga [1] è generata dalla seguente sequenza della vista [vue-01.xml]:
<!-- obbligatorio, int, min(10) -->
<tr>
<td class="col1">required, int, min(10)</td>
<td class="col2">
<input type="text" th:field="*{intMin10}" th:value="*{intMin10}" data-val="true" th:attr="data-val-required=#{NotNull},data-val-int=#{typeMismatch},data-val-min=#{Min.form01.intMin10},data-val-min-value=#{form01.intMin10.value}" />
</td>
<td class="col3">
<span class="field-validation-valid" data-valmsg-for="intMin10" data-valmsg-replace="true"></span>
</td>
<td class="col4">
<span th:if="${#fields.hasErrors('intMin10')}" th:errors="*{intMin10}" class="error">Donnée erronée</span>
</td>
</tr>
Queste righe riguardano il campo [intMin10] del modulo [Form01]:
@NotNull
@Min(value = 10)
private Integer intMin10;
La riga 5 introduce un nuovo validatore [min] [data-val-int=#{typeMismatch}] con un parametro [value] [data-val-min-value=#{form01.intMin10.value}"]. Si tratta di un caso analogo al validatore [max]. Si aggiunge in [client-validation.js] il seguente codice:
logs.min = true;
//-------------- min da utilizzare in combinazione con [int] o [number]
$.validator.addMethod("min", function(value, element, param) {
// log
if (logs.min) {
console.log(jSON.stringify({
"[min] value" : value,
"[min] param" : param
}));
}
// validità
var val = Globalize.parseFloat(value);
if (isNaN(val)) {
// log
if (logs.min) {
console.log(jSON.stringify({
"[min] valide" : true
}));
}
// risultato
return true;
}
var min = Globalize.parseFloat(param.value);
var valide = val >= min;
// log
if (logs.min) {
console.log(jSON.stringify({
"[min] valide" : valide
}));
}
// risultato
return valide;
});
$.validator.unobtrusive.adapters.add("min", [ "value" ], function(options) {
options.rules["min"] = options.params;
options.messages["min"] = options.message.replace("''", "'");
});
Ecco alcuni log di esecuzione:
Valore inserito | log |
| |
| |
|
6.3.15. Validatore [regex]
![]() |
La riga [1] è generata dalla seguente sequenza della vista [vue-01.xml]:
<!-- obbligatorio, regex -->
<tr>
<td class="col1">required, regex</td>
<td class="col2">
<input type="text" th:field="*{strBetween4and6}" th:value="*{strBetween4and6}" data-val="true" th:attr="data-val-required=#{NotNull},data-val-regex=#{Size.form01.strBetween4and6}, data-val-regex-pattern=#{form01.strBetween4and6.pattern}" />
</td>
<td class="col3">
<span class="field-validation-valid" data-valmsg-for="strBetween4and6" data-valmsg-replace="true"></span>
</td>
<td class="col4">
<span th:if="${#fields.hasErrors('strBetween4and6')}" th:errors="*{strBetween4and6}" class="error">Donnée erronée</span>
</td>
</tr>
Queste righe riguardano il campo [strBetween4and6] del modulo [Form01]:
@NotNull
@Size(min = 4, max = 6)
private String strBetween4and6;
La riga 5 genera il seguente HTML:
<input type="text" data-val="true" data-val-required="Le champ est obligatoire" data-val-regex="La chaîne doit avoir entre 4 et 6 caractères" data-val-regex-pattern="^.{4,6}$" value="" id="strBetween4and6" name="strBetween4and6" />
Questo tag introduce il validatore [regex] [data-val-regex="La chaîne doit avoir entre 4 et 6 caractères"] con il suo parametro [pattern] [data-val-regex-pattern="^.{4,6}$"]. Il parametro [pattern] è l'espressione regolare che il valore da validare deve soddisfare. In questo caso l’espressione regolare verifica che la stringa contenga da 4 a 6 caratteri qualsiasi. Il validatore [regex] è predefinito nella libreria di validazione MS. Non è quindi necessario aggiungere nulla nel file [client-validation.js].
6.3.16. Validatore [email]
![]() |
La riga [1] viene generata dalla seguente sequenza della vista [vue-01.xml]:
<!-- obbligatorio, email -->
<tr>
<td class="col1">required, email</td>
<td class="col2">
<input type="text" th:field="*{email}" th:value="*{email}" data-val="true" th:attr="data-val-required=#{NotNull},data-val-email=#{Email.form01.email}" />
</td>
<td class="col3">
<span class="field-validation-valid" data-valmsg-for="email" data-valmsg-replace="true"></span>
</td>
<td class="col4">
<span th:if="${#fields.hasErrors('email')}" th:errors="*{email}" class="error">Donnée erronée</span>
</td>
</tr>
Queste righe riguardano il campo [email] del modulo [Form01]:
@NotNull
@Email
@NotBlank
private String email;
La riga 5 genera la seguente riga HTML:
<input type="text" data-val="true" data-val-required="Le champ est obligatoire" data-val-email="Adresse mail invalide" value="" id="email" name="email" />
Questo tag introduce il validatore [email] [data-val-email="Adresse mail invalide"]. Il validatore [email] è predefinito nella libreria di validazione MS. Non è quindi necessario aggiungere nulla nel file [client-validation.js].
6.3.17. Validatore [range]
![]() |
La riga [1] viene generata dalla seguente sequenza della vista [vue-01.xml]:
<!-- obbligatorio, int, intervallo (10,14) -->
<tr>
<td class="col1">required, int, range (10,14)</td>
<td class="col2">
<input type="text" th:field="*{int1014}" th:value="*{int1014}" data-val="true" th:attr="data-val-required=#{NotNull},data-val-int=#{typeMismatch}, data-val-range=#{Range.form01.int1014},data-val-range-max=#{form01.int1014.max},data-val-range-min=#{form01.int1014.min}" />
</td>
<td class="col3">
<span class="field-validation-valid" data-valmsg-for="int1014" data-valmsg-replace="true"></span>
</td>
<td class="col4">
<span th:if="${#fields.hasErrors('int1014')}" th:errors="*{int1014}" class="error">Donnée erronée</span>
</td>
</tr>
Queste righe riguardano il campo [int1014] del modulo [Form01]:
@Range(min = 10, max = 14)
@NotNull
private Integer int1014;
La riga 5 genera la seguente riga HTML:
<input type="text" data-val="true" data-val-range-max="14" data-val-range="La valeur doit être dans l''intervalle [10,14]" data-val-int="Format invalide" data-val-required="Le champ est obligatoire" data-val-range-min="10" value="" id="int1014" name="int1014" />
Questo tag introduce un nuovo validatore [range] [data-val-range="La valeur doit être dans l''intervalle [10,14]"] che ha due parametri [min] [data-val-range-min="10"] e [max] [data-val-range-max="14"].
Nel file [client-validation.js], definiamo il validatore [range] nel modo seguente:
// -------------- intervallo da utilizzare in combinazione con [int] o [number]
logs.range=true
$.validator.addMethod("range", function(value, element, param) {
// log
if (logs.range) {
console.log(jSON.stringify({
"[range] value" : value,
"[range] param" : param
}));
}
// validità
var val = Globalize.parseFloat(value);
if (isNaN(val)) {
// log
if (logs.min) {
console.log(jSON.stringify({
"[range] valide" : true
}));
}
// completato
return true;
}
var min = Globalize.parseFloat(param.min);
var max = Globalize.parseFloat(param.max);
var valide = val >= min && val <= max;
// log
if (logs.range) {
console.log(jSON.stringify({
"[range] valide" : valide
}));
}
// completato
return valide;
});
$.validator.unobtrusive.adapters.add("range", [ "min", "max" ], function(options) {
options.rules["range"] = options.params;
options.messages["range"] = options.message.replace("''", "'");
});
È molto simile ai validatori [min] e [max] già esaminati.
Ecco alcuni esempi di log:
Valore inserito | log |
| |
| |
|
6.3.18. Validatore [number]
![]() |
La riga [1] è generata dalla seguente sequenza della vista [vue-01.xml]:
<!-- doppio1: obbligatorio, numero, intervallo (2,3; 3,4) -->
<tr>
<td class="col1">double1 : required, number, range (2.3,3.4)</td>
<td class="col2">
<input type="text" th:field="*{double1}" th:value="*{double1}" data-val="true"
th:attr="data-val-required=#{NotNull},data-val-number=#{typeMismatch},data-val-range=#{Range.form01.double1},data-val-range-max=#{form01.double1.max},data-val-range-min=#{form01.double1.min}" />
</td>
<td class="col3">
<span class="field-validation-valid" data-valmsg-for="double1" data-valmsg-replace="true"></span>
</td>
<td class="col4">
<span th:if="${#fields.hasErrors('double1')}" th:errors="*{double1}" class="error">Donnée erronée</span>
</td>
</tr>
Queste righe riguardano il campo [double1] del modulo [Form01]:
@NotNull
@DecimalMax(value = "3.4")
@DecimalMin(value = "2.3")
private Double double1;
La riga 5 genera la seguente riga HTML:
<input type="text" data-val="true" data-val-number="Format invalide" data-val-range-max="3.4" data-val-range="La valeur doit être dans l'intervalle [2,3-3,4]" data-val-required="Le champ est obligatoire" data-val-range-min="2.3" value="" id="double1" name="double1" />
Il tag introduce un nuovo validatore [number] con l'attributo [data-val-number="Format invalide"]. Questo validatore è definito come segue nel file [client-validation.js]:
// -------------- numero
logs.number = true;
$.validator.addMethod("number", function(value, element, param) {
var valide = !isNaN(Globalize.parseFloat(value));
// log
if (logs.number) {
console.log(jSON.stringify({
"[number] value" : value,
"[number] valide" : valide
}));
}
// risultato
return valide;
});
$.validator.unobtrusive.adapters.add("number", [], function(options) {
options.rules["number"] = options.params;
options.messages["number"] = options.message.replace("''", "'");
});
Ecco alcuni esempi di log:
Valore inserito | log |
È noto che i numeri reali dipendono dalla cultura. Qui sopra ci troviamo nel formato [fr-FR]. Quando si digita [2.5] (notazione anglosassone), il numero viene accettato. La colpa è di [Globalize.parseFloat] che accetta entrambe le notazioni:
Passiamo all’inglese e inseriamo [+2,5] e [+2.5]. I log sono i seguenti:
Valore inserito | log |
C'è un problema con [2,5]. È stato dichiarato come valore reale valido, mentre si dovrebbe scrivere [2.5]. La colpa è di [Globalize.parseFloat]:
Nel codice sopra riportato, [Globalize.parseFloat] ignora la virgola e considera il numero come 25. Nella cultura [en-US], un numero reale può contenere un punto decimale e delle virgole, che a volte vengono utilizzate per separare le migliaia.
È possibile migliorare il risultato nel modo seguente:
// -------------- numero
logs.number = true;
$.validator.addMethod("number", function(value, element, param) {
// si gestiscono esclusivamente le colture [fr-FR] e [en-US]
var pattern_fr_FR = /^\s*[-+]?[0-9]*\,?[0-9]+\s*$/;
var pattern_en_US = /^\s*[-+]?[0-9]*\.?[0-9]+\s*$/;
var culture = Globalize.culture().name;
// controllo di validità
var valide;
if (culture === "fr-FR") {
valide = pattern_fr_FR.test(value);
} else if (culture === "en-US") {
valide = pattern_en_US.test(value);
} else {
valide = !isNaN(Globalize.parseFloat(value));
}
// log
if (logs.number) {
console.log(jSON.stringify({
"[number] value" : value,
"[number] culture" : culture,
"[number] valide" : valide
}));
}
// risultato
return valide;
});
- riga 5: l'espressione regolare di un numero reale per la cultura [fr-FR];
- riga 6: l'espressione regolare di un numero reale per la cultura [en-US];
- riga 7: il nome della cultura corrente. Nel nostro esempio, sarà una delle due culture sopra indicate;
- righe 9-16: il controllo di validità del valore inserito;
- riga 15: è stato previsto il caso in cui la cultura non sia né [fr-FR] né [en-US];
I log ora riportano quanto segue:
Cultura [fr-FR]
Valore inserito | log |
| |
| |
| |
|
Coltura [en-US]
Valore inserito | log |
| |
| |
|
6.3.19. Validatore [custom3]
![]() |
La riga [1] è generata dalla seguente sequenza della vista [vue-01.xml]:
<!-- double3: obbligatorio, numero, custom3 -->
<tr>
<td class="col1">double3 : required, number, custom3</td>
<td class="col2">
<input type="text" th:field="*{double3}" th:value="*{double3}" data-val="true" th:attr="data-val-required=#{NotNull},data-val-number=#{typeMismatch},data-val-custom3=${custom3.message},data-val-custom3-field=${custom3.otherFieldName},data-val-custom3-max=${custom3.max},data-val-custom3-min=${custom3.min}" />
</td>
<td class="col3">
<span class="field-validation-valid" data-valmsg-for="double3" data-valmsg-replace="true"></span>
</td>
<td class="col4">
<span th:if="${#fields.hasErrors('double3')}" th:errors="*{double3}" class="error">Donnée erronée</span>
</td>
</tr>
Queste righe riguardano il campo [double3] del modulo [Form01]:
@NotNull
private Double double3;
In questo caso si vuole esaminare un validatore che non convalidi più un valore inserito, ma una relazione tra due valori inseriti. In questo caso, si vuole che [double1+double3] rientri nell’intervallo [10,13].
La riga 5 genera la seguente riga HTML:
<input type="text" data-val="true" data-val-custom3-min="10.0" data-val-number="Invalid format"
data-val-custom3="[double3+double1] must be in [10,13]" data-val-custom3-max="13.0" data-val-custom3-field="double1" data-val-required="Field is required" value="" id="double3" name="double3" />
Questa riga introduce il nuovo validatore [custom3] dichiarato dall'attributo [data-val-custom3="[double3+double1] must be in [10,13]"]. Questo validatore presenta i seguenti parametri:
- [field] dichiarato dall'attributo [data-val-custom3-field="double1"]. Questo parametro indica il campo il cui valore contribuisce al calcolo della validità di [double3];
- [min] dichiarato dall'attributo [data-val-custom3-min="10.0"]. Questo parametro è il valore minimo dell'intervallo [min, max] in cui deve trovarsi [double1+double3];
- [max] dichiarato dall'attributo [data-val-custom3-max="13.0"]. Questo parametro è il valore massimo dell'intervallo [min, max] in cui deve trovarsi [double1+double3];
Questo validatore è gestito come segue in [client-validation.js]:
// -------------- custom3 utilizzato in combinazione con [number]
logs.custom3 = true;
$.validator.addMethod("custom3", function(value1, element, param) {
// secondo valore
var value2 = $("#" + param.field).val();
// log
if (logs.custom3) {
console.log(jSON.stringify({
"[custom3] value1" : value1,
"[custom3] param" : param,
"[custom3] value2" : value2
}))
}
// primo valore
var valeur1 = Globalize.parseFloat(value1);
if (isNaN(valeur1)) {
// si lascia che sia il validatore [number] a svolgere il lavoro
if (logs.custom3) {
console.log(jSON.stringify({
"[custom3] valide" : true
}))
}
return true;
}
// secondo valore
var valeur2 = Globalize.parseFloat(value2);
if (isNaN(valeur2)) {
// non è possibile eseguire il calcolo di validità
if (logs.custom3) {
console.log(jSON.stringify({
"[custom3] valide" : false
}))
}
return false;
}
// calcolo della validità
var min = Globalize.parseFloat(param.min);
var max = Globalize.parseFloat(param.max);
var somme = valeur1 + valeur2;
var valide = somme >= min && somme <= max;
// log
if (logs.custom3) {
console.log(jSON.stringify({
"[custom3] valide" : valide
}))
}
// risultato
return valide;
});
$.validator.unobtrusive.adapters.add("custom3", [ "field", "max", "min" ], function(options) {
options.rules["custom3"] = options.params;
options.messages["custom3"] = options.message.replace("''", "'");
});
Ecco alcuni esempi di log:
Valori inseriti [double1,double3] | log |
| |
| |
| |
|
6.3.20. Validatore [url]
![]() |
La riga [1] è generata dalla seguente sequenza della vista [vue-01.xml]:
<!-- obbligatorio, URL -->
<tr>
<td class="col1">required, url</td>
<td class="col2">
<input type="text" th:field="*{url}" th:value="*{url}" data-val="true" th:attr="data-val-required=#{NotNull},data-val-url=#{URL.form01.url}" />
</td>
<td class="col3">
<span class="field-validation-valid" data-valmsg-for="url" data-valmsg-replace="true"></span>
</td>
<td class="col4">
<span th:if="${#fields.hasErrors('url')}" th:errors="*{url}" class="error">Donnée erronée</span>
</td>
</tr>
Queste righe riguardano il campo [url] del modulo [Form01]:
@URL
@NotBlank
private String url;
La riga 5 genera la seguente riga HTML:
<input type="text" data-val="true" data-val-url="Invalid URL" data-val-required="Field is required" value="" id="url" name="url" />
Introduce il validatore [url] con l'attributo [data-val-url]. Questo validatore è predefinito nella libreria di validazione jQuery. Non c'è nulla da aggiungere in [client-validation.js].
6.3.21. Attivazione/disattivazione della convalida lato client
Finché la convalida lato client è attiva, la convalida lato server non viene mai visualizzata poiché i valori inviati raggiungono il server solo se sono stati dichiarati validi lato client. Per vedere la convalida lato server in azione è necessario disattivare la convalida lato client. La vista [vue-01.xml] offre due collegamenti per gestire questa attivazione/disattivazione:
<a id="clientValidationTrue" href="javascript:setClientValidation(true)">
<span style="margin-left:30px" th:text="#{client.validation.true}"></span>
</a>
<a id="clientValidationFalse" href="javascript:setClientValidation(false)">
<span style="margin-left:30px" th:text="#{client.validation.false}"></span>
</a>
Questi due link non sono visibili contemporaneamente:
![]() | ![]() |
La traduzione HTML di questi link è la seguente:
<a id="clientValidationTrue" href="javascript:setClientValidation(true)">
<span style="margin-left:30px">Activer la validation client</span>
</a>
<a id="clientValidationFalse" href="javascript:setClientValidation(false)">
<span style="margin-left:30px">Inhiber la validation client</span>
</a>
Lo script jS [setClientValidation] è definito nel file [local.js] (vedi sopra). Nella funzione [$(document).ready] di questo file vengono utilizzati i link di convalida:
// documento pronto
$(document).ready(function() {
// riferimenti globali
...
activateValidationTrue = $("#clientValidationTrue");
activateValidationFalse = $("#clientValidationFalse");
clientValidation = $("#clientValidation");
...
// collegamenti di convalida
// clientValidation è un campo nascosto impostato dal server
var validate = clientValidation.val();
setClientValidation2(validate === "true");
});
- riga 5: un riferimento al collegamento di attivazione della convalida lato client;
- riga 6: un riferimento al link di disattivazione della convalida lato client;
- riga 7: un riferimento a un campo nascosto del modulo che memorizza l'ultimo stato dell'attivazione sotto forma di valore booleano [true : validation client activée, false : validation client désactivée]. Questo campo si trova nella vista [vue-01.xml] nella forma seguente:
<input type="hidden" th:field="*{clientValidation}" th:value="*{clientValidation}" value="true" />
e corrisponde al campo [clientValidation] del modulo [Form01]:
// convalida client
private boolean clientValidation = true;
- riga 11: si recupera il valore del campo nascosto;
- riga 12: si chiama la seguente funzione [setClientValidation2]:
function setClientValidation2(activate) {
// collegamenti
if (activate) {
// la convalida lato client è attiva
activateValidationTrue.hide();
activateValidationFalse.show();
// si analizzano i validatori del modulo
$.validator.unobtrusive.parse(formulaire);
} else {
// la convalida lato client è disattivata
activateValidationFalse.hide();
activateValidationTrue.show();
// si disattivano i validatori del modulo
formulaire.data('validator', null);
}
}
- riga 1: il parametro [activate] è pari a [true] se è necessario attivare la convalida lato client, altrimenti è false;
- righe 5-6: il link di disattivazione è visibile, mentre quello di attivazione è nascosto;
- riga 8: affinché la convalida lato client sia operativa, è necessario analizzare il documento alla ricerca dei validatori [data-val-X]. Il parametro della funzione [$.validator.unobtrusive.parse] è l’identificativo jS del modulo da analizzare;
- righe 11-12: il link di attivazione è visibile, mentre quello di disattivazione è nascosto;
- riga 14: i validatori del modulo sono disattivati. Da questo momento in poi, è come se nel modulo non ci fossero validatori jS;
A cosa serve questa funzione [setClientValidation2]? Serve a gestire i POST. Poiché il campo [clientValidation] è un campo nascosto, viene inviato e ritorna con il modulo rinviato dal server. Si utilizza quindi il suo valore per ripristinare la convalida lato client così com'era prima del POST. Infatti, non c'è memoria jS tra le richieste. È quindi necessario che il server trasmetta nella nuova vista le informazioni che consentono di inizializzare il jS di quest’ultima. Ciò avviene solitamente nella funzione [$(document).ready].
Torniamo alla funzione [setClientValidation] che gestisce il clic sui link di attivazione/disattivazione della convalida lato client:
// convalida lato client
function setClientValidation(activate) {
// si gestisce l'attivazione/disattivazione della convalida lato client
setClientValidation2(activate);
// si memorizza la scelta dell'utente nel campo nascosto
clientValidation.val(activate ? "true" : "false");
// ulteriori modifiche
if (activate) {
// la convalida lato client è attiva
// si cancellano tutti i messaggi di errore dal server
clearServerErrors();
// si convalida il modulo
formulaire.validate().form();
} else {
// la convalida lato client è disattivata
// si cancellano tutti i messaggi di errore del client
clearClientErrors();
}
}
- riga 4: si utilizza la funzione [setClientValidation2] che abbiamo appena visto;
- riga 6: si memorizza la scelta dell’utente nel campo nascosto per recuperarla al ritorno della successiva POST;
- riga 11: se la convalida lato client è attiva, si cancellano i messaggi di errore dalla colonna [serveur] della vista. Abbiamo descritto la funzione [clearServerErrors] al paragrafo 6.3.7;
- riga 13: i validatori jS vengono eseguiti per visualizzare eventuali messaggi di errore nella colonna [client] della vista;
- riga 17: se la convalida da parte del cliente è disattivata, si cancellano i messaggi di errore dalla colonna [client] della vista. Esaminiamo nella console di sviluppo di Chrome il codice HTML di un elemento errato:
<td class="col2">
<input type="text" data-val="true" data-val-int="Format invalide" data-val-max-value="100" data-val-required="Le champ est obligatoire" data-val-max="La valeur doit être inférieure ou égale à 100" value="" id="intMax100" name="intMax100" aria-required="true" class="input-validation-error" aria-describedby="intMax100-error">
</td>
<td class="col3">
<span class="field-validation-error" data-valmsg-for="intMax100" data-valmsg-replace="true">
<span id="intMax100-error" class="">Le champ est obligatoire</span>
</span>
</td>
- alla riga 2, si vede che nella colonna 2 della tabella l'elemento errato ha lo stile [class="input-validation-error"];
- alla riga 5, si nota che nella colonna 3 della tabella il messaggio di errore ha lo stile [class="field-validation-error"];
Questo vale per tutti gli elementi errati. Queste due informazioni vengono utilizzate nella seguente funzione [clearClientErrors]:
// cancella gli errori del client
function clearClientErrors() {
// si cancellano i messaggi di errore del client
$(".field-validation-error").each(function(index) {
$(this).text("");
});
// si modifica la classe CSS degli inserimenti errati
$(".input-validation-error").each(function(index) {
$(this).removeClass("input-validation-error");
});
}
- righe 4-6: si cercano tutti gli elementi di DOM che appartengono alla classe [field-validation-error] e si cancella il testo che visualizzano. In questo modo vengono cancellati i messaggi di errore;
- righe 8-10: si cercano tutti gli elementi del DOM che appartengono alla classe [input-validation-error] e si rimuove tale classe. In questo modo l’elemento errato, che era stato evidenziato in rosso, riacquista il suo stile originale;


















































