6. Validação em JavaScript no lado do cliente
No capítulo anterior, abordamos a validação no lado do servidor. Voltemos à arquitetura de uma aplicação Spring MVC:
![]() |
BD
Até agora, as páginas enviadas ao cliente não continham JavaScript. Abordaremos agora essa tecnologia, que nos permitirá, em um primeiro momento, realizar validações no lado do cliente. O princípio é o seguinte:
- é o JavaScript que envia os valores para o servidor web;
- e, portanto, antes desse POST, ele pode verificar a validade dos dados e impedir o POST caso estes sejam inválidos;
Vamos utilizar o formulário que validamos no lado do servidor. Agora, vamos oferecer a possibilidade de validá-lo tanto no lado do cliente quanto no lado do servidor.
Observação: o assunto é complexo. O leitor que não estiver interessado neste tema pode pular diretamente para o parágrafo 7.
6.1. As funcionalidades do projeto
Apresentamos algumas visualizações do projeto para demonstrar suas funcionalidades. A página inicial é gerada com o URL [http://localhost:8080/js01.html]
![]() |
As validações foram implementadas em ambos os lados: cliente e servidor. Como o POST só ocorre se os valores tiverem sido considerados válidos no lado do cliente, as validações no lado do servidor são sempre bem-sucedidas. Por isso, disponibilizamos um link para desativar as validações do lado do cliente. Quando se está nesse modo, retoma-se o modo de funcionamento que já estudamos. Aqui está um exemplo:
123 ![]() |
- em [1], os valores inseridos;
- em [2], as mensagens de erro relacionadas às entradas;
- em [3], um resumo dos erros com, para cada um deles:
- o nome do campo validado,
- o código de erro,
- a mensagem padrão desse código de erro;
Agora, vamos habilitar a validação no lado do cliente:
![]() |
- em [1], os valores inseridos. É possível notar que as entradas incorretas apresentam um estilo específico;
- em [2], as mensagens de erro associadas às entradas incorretas. Elas são idênticas às geradas pelo servidor;
- em [3-4], não há mais nada, pois, enquanto houver entradas incorretas, a transmissão de POST para o servidor não ocorre;
6.2. Validação no lado do servidor
6.2.1. Configuração
Começamos criando um novo projeto Maven, [springmvc-validation-client]:
![]() |
Desenvolvemos o projeto da seguinte maneira:
![]() |
A classe [Config] configura o projeto. Ela é idêntica à dos projetos anteriores:
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;
}
}
A classe [Main] é a classe executável do projeto:
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) {
// inicia-se a aplicação
ApplicationContext context = SpringApplication.run(Config.class, args);
// exibe-se a lista de beans encontrados pelo Spring
System.out.println("Liste des beans Spring");
String[] beanNames = context.getBeanDefinitionNames();
Arrays.sort(beanNames);
for (String beanName : beanNames) {
System.out.println(beanName);
}
}
}
- linha 13, o Spring Boot é iniciado com o arquivo de configuração [Config];
- linhas 15-20: para o exemplo, mostramos como exibir a lista de objetos gerenciados pelo Spring. Isso pode ser útil caso, às vezes, pareça que o Spring não está gerenciando um de nossos componentes. É uma forma de verificar isso. É também uma forma de verificar a autoconfiguração feita pelo Spring Boot. No console, obtemos uma lista semelhante à seguinte:
Destacamos os objetos definidos na classe [Config].
6.2.2. O modelo do formulário
Vamos continuar explorando o projeto:
![]() |
A classe [Form01] é a classe que receberá os valores postados. Ela é a seguinte:
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 {
// valores enviados
@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;
// validação pelo cliente
private boolean clientValidation = true;
// local
private String lang;
...
}
Encontramos aqui validadores que já conhecemos. Além disso, vamos introduzir o conceito de validação específica. Trata-se de uma validação que não pode ser formalizada com um validador predefinido. Aqui, vamos exigir que [double1+double2] esteja no intervalo [10,13].
6.2.3. O controlador
O controlador [JsController] é o seguinte:
![]() |
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";
}
...
// preparação do modelo da vista vista-01
private void setModel(Form01 formulaire, Model model, Locale locale, String message) {
...
}
}
- linha 9, a ação [/js01];
- linha 10: um objeto do tipo [Form01] é instanciado e inserido automaticamente no modelo, associado à chave [form01];
- linha 10: a localização e o modelo são inseridos nos parâmetros;
- linha 11: com essas informações, prepara-se o modelo;
- linha 12: exibe-se a visualização [vue-01.xml];
O método [setModel] é o seguinte:
// preparação do modelo da vista vista-01
private void setModel(Form01 formulaire, Model model, Locale locale, String message) {
// são suportadas apenas as configurações regionais fr-FR e 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));
// a mensagem eventual
if (message != null) {
model.addAttribute("message", message);
}
}
- o objetivo do método [setModel] é inserir no modelo:
- informações sobre a localidade,
- a mensagem passada como último parâmetro;
- linha 14: insere-se no modelo informações sobre a localização (idioma, país);
- linhas 16-18: insere-se na configuração regional a mensagem eventualmente passada como parâmetro;
- linhas 8, 12: as informações sobre a localização também são armazenadas no formulário [Form01]. O JavaScript utilizará essas informações;
Os valores inseridos no formulário [vue-01.xml] serão enviados para a ação [/js02] a seguir:
@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);
...
}
- linha 2: a anotação [@Valid Form01 formulaire] faz com que os valores enviados sejam submetidos aos validadores da classe [Form01]. Sabemos que existe uma validação específica [double1+double2] no intervalo [10,13]. Ao chegar à linha 3, essa validação ainda não foi realizada;
- linha 3: cria-se o seguinte objeto [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 {
// o intervalo de validação
private double min;
private double max;
// construtor
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) {
// objeto validado
Form01 form01 = (Form01) form;
// o valor de [double1]
Double double1 = form01.getDouble1();
if (double1 == null) {
return;
}
// o valor de [double2]
Double double2 = form01.getDouble2();
if (double2 == null) {
return;
}
// [double1+double2]
double somme = double1 + double2;
// validação
if (somme < min || somme > max) {
errors.rejectValue("double2", "form01.double2", new Double[] { min, max }, null);
}
}
}
- linha 8: para implementar uma validação específica, criamos uma classe que implementa a interface Spring [Validator]. Essa interface possui dois métodos: [supports] na linha 21 e [validate] na linha 26;
- linhas 21-23: o método [supports] recebe um objeto do tipo [Class]. Ele deve retornar true para indicar que suporta essa classe, ou false caso contrário;
- linha 22: afirmamos que a classe [Form01Validator] valida apenas objetos do tipo [Form01];
- linhas 15-18: lembremos que queremos implementar a restrição [double1+double2] no intervalo [10,13]. em vez de nos limitarmos a esse intervalo, vamos verificar a restrição [double1+double2] no intervalo [min, max]. É por isso que temos um construtor com esses dois parâmetros;
- linha 26: o método [validate] é chamado com uma instância do objeto validado, ou seja, neste caso, uma instância de [Form01], e com a coleção de erros atualmente conhecidos [Errors errors]. Se a validação realizada pelo método [validate] falhar, ele deve criar um novo elemento na coleção [Errors errors];
- linha 43: a validação falhou. Adiciona-se um elemento à coleção [Errors errors] com o método [Errors.rejectValue], cujos parâmetros são os seguintes:
- parâmetro 1: normalmente o nome do campo com erro. Aqui, testamos os campos [double1, double2]. É possível inserir um dos dois,
- a mensagem de erro associada ou, mais precisamente, sua chave nos arquivos de mensagens externalizados:
[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}
Trata-se de mensagens configuradas por {0} e {1}. Portanto, é necessário fornecer dois valores para essa mensagem. É isso que faz o terceiro parâmetro do método [Errors.rejectValue].
- O quarto parâmetro é uma mensagem padrão para o erro;
Voltemos à ação [/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";
}
}
- linha 4: o validador [Form01Validator] é executado com os seguintes parâmetros:
- parâmetro 1: o objeto em validação,
- parâmetro 2: a lista de erros desse objeto. Trata-se do objeto [BindingResult result] passado como parâmetro da ação. Se a validação falhar, esse objeto terá mais um erro;
- linha 5: verifica-se se há erros de validação;
- linhas 7-10: percorremos a lista de erros para armazenar, para cada um deles:
- o nome do objeto validado,
- seu código de erro,
- sua mensagem de erro padrão;
- linha 10: com essas informações, constrói-se o modelo da visualização [vue-01.xml]. Desta vez, há uma mensagem, que é a versão concatenada e resumida das diferentes mensagens de erro;
- linhas 12-15: se todos os valores enviados forem válidos, redireciona-se o cliente para a ação [/js01], colocando os valores enviados como atributos Flash;
6.2.4. A visualização
A visualização [vue-01.xml] é complexa. Apresentaremos apenas uma pequena parte dela:
<!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>
<!-- título -->
<h3>
<span th:text="#{form01.title}"></span>
<span th:text="${locale}"></span>
</h3>
<!-- menu -->
<p>
...
</p>
<!-- formulário -->
<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>
<!-- obrigatório -->
<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>
<!-- botão de validação -->
<input type="submit" th:value="#{form01.valider}" value="Valider" onclick="javascript:postForm01()" />
</p>
</form>
<!-- mensagem dos validadores do lado do servidor -->
<br/>
<fieldset class="fieldset">
<legend>
<span th:text="#{server.error.message}"></span>
</legend>
<span th:text="${message}" class="error"></span>
</fieldset>
</body>
</html>
Esta página utiliza várias mensagens encontradas nos arquivos de mensagens externalizados:
[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
Voltemos ao código da página:
- linha 8: várias importações de bibliotecas JavaScript que podemos ignorar aqui;
- linha 14: exibe a localização inserida no modelo pelo servidor;
- linha 59: exibe a mensagem inserida no modelo pelo servidor;
O código das linhas 33 a 44 é novo. Vamos analisá-lo:
<!-- obrigatório -->
<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>
Talvez o mais simples seja examinar o código HTML gerado por este segmento do Thymeleaf:
<!-- obrigatório -->
<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>
Vamos utilizar, no lado do cliente, uma biblioteca de validação chamada [jquery.validate]. Todos os atributos [data-x] são destinados a ela. Quando a validação no lado do cliente for desativada, esses atributos não serão utilizados. Portanto, por enquanto, não há necessidade de compreendê-los. Podemos simplesmente nos concentrar na seguinte linha do Thymeleaf:
<input type="text" th:field="*{strNotEmpty}" data-val="true" th:attr="data-val-required=#{NotNull}" />
que gera a seguinte linha HTML:
<input type="text" data-val="true" data-val-required="Le champ est obligatoire" id="strNotEmpty" name="strNotEmpty" value="" />
No exemplo acima, há uma dificuldade para gerar o atributo [data-val-required="Le champ est obligatoire"]. De fato, o valor associado ao atributo provém dos arquivos de mensagens externalizados. Portanto, é necessário utilizar uma expressão Thymeleaf para obtê-lo. Trata-se da seguinte expressão: [th:attr="data-val-required=#{NotNull}"]. Essa expressão é avaliada e seu valor é inserido tal como está na tag HTML gerada. Ela se chama [th:attr] porque é usada para gerar atributos não predefinidos no Thymeleaf. Encontramos atributos predefinidos como [th:text, th:value, th:class, ...], mas não existe um atributo [th:data-val-required].
6.2.5. A folha de estilo
Acima, encontramos classes CSS, como [class="field-validation-valid"]. Algumas dessas classes são utilizadas pela biblioteca JavaScript de validação. Elas estão definidas no seguinte arquivo [form01.css]:
![]() |
@CHARSET "UTF-8";
/*estilos personalizados*/
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;
}
/* Estilos para auxiliares de validação
-----------------------------------------------------------*/
.field-validation-error {
color: #f00;
}
.field-validation-valid {
display: none;
}
.input-validation-error {
border: 1px solid #f00;
background-color: #taxa;
}
.validation-summary-errors {
font-weight: bold;
color: #f00;
}
.validation-summary-valid {
display: none;
}
6.3. Validação no lado do cliente
6.3.1. Noções básicas sobre jQuery e JavaScript
A validação do lado do cliente é feita com JavaScript. Vamos utilizar o framework jQuery, que oferece diversas funções que facilitam o desenvolvimento em JavaScript. Apresentamos os conceitos básicos do jQuery que você precisa conhecer para compreender os scripts deste capítulo e dos seguintes.
Criamos um arquivo estático HTML [JQuery-01.html], que colocamos em uma pasta [static / vues]:
![]() |
Esse arquivo terá o seguinte conteúdo:
<!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>
- linha 6: importação de jQuery;
- linhas 10-12: um elemento da página com o ID [element1]. Vamos trabalhar com esse elemento.
Precisamos baixar o arquivo [jquery-1.11.1.min.js]. Encontraremos a versão mais recente do jQuery no URL [http://jquery.com/download/]:

Colocaremos o arquivo baixado na pasta [static / js]:
![]() |
Feito isso, acesse a visualização estática [jQuery-01.html] usando o Chrome [1-2]:
![]() |
No Google Chrome, digite [Ctrl-Maj-I] para exibir as ferramentas de desenvolvimento [3]. A aba [Console] [4] permite executar código JavaScript. A seguir, apresentamos comandos JavaScript a serem digitados, acompanhados de uma explicação.
JS | resultado |
|
: retorna a coleção de todos os elementos com o id [element1]; portanto, normalmente uma coleção de 0 ou 1 elemento, pois não é possível ter dois ids idênticos em uma página HTML. | ![]() |
|
: atribui o texto [blabla] a todos os elementos da coleção. Isso tem como efeito alterar o conteúdo exibido pela página | ![]() |
|
oculta os elementos da coleção. O texto [blabla] não é mais exibido. | ![]() |
|
: exibe a coleção novamente. Isso nos permite ver que o elemento com o id [element1] possui o atributo CSS style='display: none;', o que faz com que o elemento fique oculto. | |
|
: exibe os elementos da coleção. O texto [blabla] aparece novamente. É o atributo CSS style='display: block;' que garante essa exibição. | ![]() |
|
: define um atributo para todos os elementos da coleção. O atributo aqui é [style] e seu valor é [color: red]. O texto [blabla] fica em vermelho. | ![]() |
![]() | |
![]() |
Observe-se que o URL do navegador não sofreu alterações durante todas essas operações. Não houve troca de dados com o servidor web. Tudo ocorre internamente no navegador. Agora, vamos visualizar o código-fonte da página:
<!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>
Este é o texto inicial. Ele não reflete de forma alguma as alterações que fizemos no elemento nas linhas 10 a 12. É importante lembrar disso ao depurar JavaScript. Portanto, muitas vezes é desnecessário visualizar o código-fonte da página exibida.
Já sabemos o suficiente para entender os scripts jS que virão a seguir.
6.3.2. As bibliotecas de validação jS
Vamos utilizar bibliotecas do ecossistema jQuery. Em torno do jQuery, há uma série de projetos que, por sua vez, dão origem a bibliotecas. Utilizaremos a biblioteca de validação [jquery.validate.unobstrusive] criada pela Microsoft e doada à fundação jQuery. A partir de agora, vamos nos referir a ela como biblioteca de validação MS ou, mais simplesmente, biblioteca MS. Para obtê-la, é necessário um ambiente do Microsoft Visual Studio. Não encontrei outra maneira de obtê-la. É possível usar uma versão gratuita do tipo [Visual Studio Community] [http://www.visualstudio.com/en-us/news/vs2013-community-vs.aspx] (dezembro de 2014). O leitor que não estiver interessado em seguir os passos a seguir pode baixar essa biblioteca e aquelas nas quais ela se baseia nos exemplos disponíveis no site deste documento.
Cria-se um projeto de console com o Visual Studio [1-4]:
|
![]() |
- no [5], o projeto de console;
- em [6-7]: vamos adicionar os pacotes [NuGet] ao projeto. [NuGet] é uma função do Visual Studio que permite baixar bibliotecas no formato DLL, bem como bibliotecas jS.
![]() |
- em [9-10], faça uma pesquisa com a palavra-chave [jQuery];
- para [11-13], baixe, na ordem indicada, as bibliotecas jS necessárias para a validação no lado do cliente;
- em [14], baixe também a biblioteca [Microsoft jQuery Unobtrusive Ajax], que usaremos em breve;
![]() |
- em [15-16], faça uma busca por pacotes com a palavra-chave [globalize];
- para o [17], baixe a biblioteca [jQuery.Validation.Globalize];
![]() |
Esses diversos downloads instalaram várias bibliotecas jS na pasta [Scripts] do projeto [18]. Nem todas são úteis. Cada arquivo vem em duas cópias:
- [js]: a versão legível da biblioteca;
- [min.js]: a versão não legível, conhecida como “minificada” (minified), da biblioteca. Ela não é realmente ilegível. Trata-se de texto. Mas não é compreensível. Essa é a versão a ser usada em produção, pois esse arquivo é menor do que a versão correspondente [js] e, portanto, melhora a velocidade das trocas entre cliente e servidor;
As versões [min.map] não são indispensáveis. Na pasta [cultures], pode-se manter apenas as configurações regionais gerenciadas pelo aplicativo.
Com o Explorador do Windows, copie esses arquivos para a pasta [static / js / jquery] do projeto [springmvc-validation-client] e mantenha apenas os arquivos úteis [20]:
![]() |
No [21], mantêm-se apenas duas configurações regionais:
- [fr-FR]: o francês da França;
- [en-US]: o inglês do USA;
6.3.3. Importação das bibliotecas de validação jS
Para serem utilizadas, essas bibliotecas devem ser importadas pela visão [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>
- linha 11: a importação de um arquivo jS, sobre o qual ainda não falamos;
- linhas 13-18: um script jS interpretado pelo Thymelaf. Ele gerencia a configuração regional no lado do cliente;
6.3.4. Gerenciamento da localização no lado do cliente
A localização no lado do cliente é feita pelo seguinte script jS:
<script th:inline="javascript">
/*<![CDATA[*/
var culture = [[${locale}]];
Globalize.culture(culture);
/*]]>*/
</script>
- linhas 3-4: código jS no qual se encontra a expressão Thymeleaf [[${locale}]]. Observe a sintaxe específica dessa expressão. Isso ocorre porque ela está em JavaScript. A expressão [[${locale}]] será substituída pelo valor da chave [locale] do modelo da visualização;
O resultado no fluxo HTML gerado a partir dessas linhas é o seguinte:
<script>
/*<![CDATA[*/
var culture = 'en-US';
Globalize.culture(culture);
/*]]>*/
</script>
As linhas 3 e 4 definem a configuração de idioma no lado do cliente. Gerenciamos apenas duas: [fr-FR] e [en-US]. É por isso que importamos apenas dois arquivos de configuração de idioma:
<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>
A configuração de idioma a ser usada no lado do cliente é definida no lado do servidor. Voltemos ao código do lado do servidor:
@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";
}
// preparação do modelo da visualização vue-01
private void setModel(Form01 formulaire, Model model, Locale locale, String message) {
// são suportadas apenas as configurações regionais fr-FR e 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));
...
}
- linha 20: a localização [fr-FR] ou [en-US] é inserida no modelo da visualização [vue-01.xml] (linha 4). É importante observar uma fonte de complicações. Enquanto uma localidade francesa é indicada como [fr-FR] no lado do cliente, ela é indicada como [fr_FR] no lado do servidor. É por isso que, nas linhas 14 e 18, ela é armazenada dessa forma no objeto [Form01 formulaire], que recebe os valores enviados;
É importante observar o seguinte ponto. O script
<script>
/*<![CDATA[*/
var culture = 'en-US';
Globalize.culture(culture);
/*]]>*/
</script>
altera a cultura do cliente com base na cultura local transmitida pelo servidor. Isso não internacionaliza as mensagens exibidas pela página. Apenas altera a forma de interpretar certas informações que dependem da cultura de um país. Com a cultura [fr_FR], o número real [12,78] é válido, enquanto é inválido com a cultura [en-US]. Nesse caso, deve-se escrever [12.78]. Da mesma forma, a data [12/01/2014] é válida na cultura [fr-FR], enquanto que na cultura [en-US] é preciso escrever [01/12/2014]. Os arquivos da pasta [jquery / globalize] lidam com esse tipo de problema:
![]() |
A internacionalização das mensagens de erro é gerenciada exclusivamente no lado do servidor. Veremos que a página HTML / jS traz consigo mensagens de erro correspondentes à configuração regional gerenciada pelo servidor: em francês para a configuração regional [fr_FR] e em inglês para a configuração regional [en_US].
6.3.5. Os arquivos de mensagens
A visualização [vue-01.xml] utiliza as seguintes mensagens internacionalizadas:
![]() |
[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
O arquivo [messages.properties] é uma cópia do arquivo de mensagens em inglês. Portanto, qualquer localização diferente de [fr] utilizará mensagens em inglês. Vale lembrar que o arquivo [messages_fr.properties] é utilizado para qualquer configuração regional [fr_XX], como [fr_CA] ou [fr_FR].
A visualização [vue-01.xml] utiliza as chaves dessas mensagens. Caso deseje saber o valor associado a essas chaves, o leitor deve retornar a este parágrafo para descobri-lo.
6.3.6. Alteração da configuração regional
A visualização [vue-01.xml] apresenta quatro links:
<body>
<!-- título -->
<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>
<!-- formulário -->
<form action="/someURL" th:action="@{/js02.html}" method="post" th:object="${form01}" name="form" id="form">
...
alguns dos quais estão representados abaixo [1]:
![]() |
Vamos examinar os dois links que permitem alterar a localidade para francês ou inglês:
<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>
Um clique nesses links faz com que seja executado um script jS presente no arquivo [local.js] [2]. Em ambos os casos, é chamada uma função jS [setLocale]:
// configuração regional
function setLocale(locale) {
// atualiza-se a configuração regional
lang.val(locale);
// envia-se o formulário — isso não aciona os validadores do cliente — por isso não se desativou a validação do lado do cliente
document.form.submit();
}
Para entender a linha 4, é necessário um preâmbulo. A vista [vue-01.xml] contém um campo oculto chamado [lang]:
<input type="hidden" th:field="*{lang}" th:value="*{lang}" value="true" />
que corresponde a um campo [lang] em [Form01]:
// local
private String lang;
Os campos ocultos são úteis quando se deseja enriquecer os valores enviados. O JavaScript permite atribuir um valor a eles, e esse valor é enviado como uma entrada normal feita pelo usuário. O código HTML gerado pelo Thymeleaf é o seguinte:
<input type="hidden" value="en_US" id="lang" name="lang" />
O valor do parâmetro [value] é o mesmo do campo [Form01.lang] no momento da geração do HTML. O que é importante observar é o identificador jS do nó [id="lang"]. Esse identificador é utilizado pela seguinte função []:
// variáveis globais
var lang;
// documento pronto
$(document).ready(function() {
// referências globais
lang = $("#lang");
});
// local
function setLocale(locale) {
// atualiza-se a local
lang.val(locale);
// enviando o formulário — por algum motivo desconhecido, isso não aciona os validadores do cliente
// por isso não se inibiu a validação
document.form.submit();
}
- linhas 5-8: a função jS [$(document).ready(f)] é executada quando o navegador carrega todo o documento enviado pelo servidor. Seu parâmetro é uma função. Utiliza-se a função jS [$(document).ready(f)] para inicializar o ambiente jS do documento carregado;
- linha 7: a expressão [$("#lang")] é uma expressão jQuery. Seu valor é uma referência ao nó do DOM com o atributo [id='lang'];
- linha 2: as variáveis declaradas fora de uma função são globais para as funções. Aqui, isso significa que a variável [lang], inicializada em [$(document).ready()], também é reconhecida na função [setLocale] da linha 11;
- linha 13: altera o atributo [value] do nó identificado por [lang]. Se lang for igual a [xx_XX], então a tag HTML do nó passa a ser:
<input type="hidden" value="xx_XX" id="lang" name="lang" />
O JavaScript permite modificar o valor dos elementos do DOM (Document Object Model).
- linha 16: [document] refere-se ao DOM. [document.form] refere-se ao primeiro formulário encontrado neste documento. Um documento HTML pode conter várias tags <form> e, portanto, vários formulários. Neste caso, temos apenas um. [document.form.submit] envia esse formulário como se o usuário tivesse clicado em um botão com o atributo [type='submit']. Para qual ação os valores do formulário são enviados? Para saber isso, é preciso observar a tag [form] do formulário em [vue-01.xml]:
<!-- formulário -->
<form action="/someURL" th:action="@{/js02.html}" method="post" th:object="${form01}" name="form" id="form">
A ação que receberá os valores enviados é aquela designada pelo atributo [th:action]. Portanto, será a ação [/js02.html]. Vale lembrar que, nesse nome, o sufixo [.html] será removido e, no final, será executada a ação [/js02]. O importante é entender que o novo valor [xx_XX] do nó [lang] será enviado na forma [lang=xx_XX]. No entanto, configuramos nossa aplicação para interceptar o parâmetro [lang] e interpretá-lo como uma alteração de localidade. Portanto, no lado do servidor, a localidade passará a ser [xx_XX]. Vejamos a ação [/js02] que será executada:
@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";
}
}
// preparação do modelo da visualização vista-01
private void setModel(Form01 formulaire, Model model, Locale locale, String message) {
// só são suportadas as configurações regionais fr-FR e 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));
...
}
- linha 2: a ação [/js02] receberá a nova localização [xx_XX] encapsulada no parâmetro [Locale locale]:
- linhas 5-12: se alguns dos valores enviados forem inválidos, a visualização [vue-01.xml] será exibida com mensagens de erro utilizando a nova localização [xx_XX]. Além disso, a linha 11 faz com que a variável [locale=xx-XX] seja inserida no modelo. No lado do cliente, esse valor será utilizado para atualizar a localização no lado do cliente. Já descrevemos esse processo;
- linhas 14-15: se todos os valores enviados forem válidos, haverá um redirecionamento para a ação seguinte [/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";
}
- na linha 2, a nova configuração regional [xx_XX] é inserida;
- linha 3: o método [setModel] definirá então a cultura do cliente como [xx-XX];
Agora, vamos observar a influência da configuração regional na visualização [vue-01.xml]. Por enquanto, não apresentamos essa visualização na íntegra, pois ela conta com mais de 300 linhas. No entanto, a maior parte das linhas consiste na repetição de uma sequência semelhante à seguinte:
<!-- obrigatório -->
<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>
Esse código exibe o seguinte fragmento [1]:
![]() |
A mensagem de erro [2] provém do atributo [th:attr="data-val-required=#{NotNull}"] da linha 5. [#{NotNull}] é uma mensagem localizada. Dependendo da configuração regional do servidor, a linha 5 gera a tag:
<input type="text" data-val="true" data-val-required="Field is required" id="strNotEmpty" name="strNotEmpty" />
ou a tag:
<input type="text" data-val="true" data-val-required="Le champ est obligatoire" id="strNotEmpty" name="strNotEmpty" />
Os atributos [data-x] são processados pela biblioteca de validação jS.
Concluindo, vale lembrar que os dois links para alteração da localidade:
- provocam uma POST dos valores inseridos;
- alteram a configuração regional tanto no lado do servidor quanto no lado do cliente;
- geram uma página HTML que traz consigo as mensagens de erro destinadas à biblioteca de validação jS, e que essas mensagens estão no idioma da configuração regional escolhida;
6.3.7. O POST dos valores inseridos
Vamos analisar o botão [Valider], que envia os valores inseridos na visualização [vue-01.xml]. Seu código HTML é o seguinte:
<!-- botão de confirmação -->
<input type="submit" value="Valider" onclick="javascript:postForm01()" />
Se o JavaScript estiver ativado no navegador, clicar no botão acionará a execução do método [postForm01]. Se essa função retornar o valor booleano [False], então o submit não será executado. Se ela retornar outro valor, então ele será executado. Essa função está no arquivo [local.js]:
![]() |
Ela é importada pela visão [vue-01.xml] na linha 6 abaixo:
<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>
Nesse arquivo, encontramos o seguinte código:
// variáveis globais
var formulaire;
var clientValidation;
var double1;
var double2;
var double3;
...
$(document).ready(function() {
// referências globais
formulaire = $("#form");
clientValidation = $("#clientValidation");
double1 = $("#double1");
double2 = $("#double2");
double3 = $("#double3");
...
});
....
// envio do formulário
function postForm01() {
...
}
- linhas 8-16: a função jS [$(document).ready(f)] é uma função executada quando o navegador carrega todo o documento enviado pelo servidor. Seu parâmetro é uma função. Utiliza-se a função jS [$(document).ready(f)] para inicializar o ambiente jS do documento carregado;
- linhas 10-14: para entender essas linhas, é preciso examinar tanto o código Thymeleaf quanto o código HTML gerado;
O código Thymeleaf em questão é o seguinte:
<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" />
que gera o seguinte código 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" />
Cada atributo [th:field='x'] gera dois atributos: HTML, [name='x'] e [id='x']. O atributo [name] é o nome dos valores enviados. Assim, a presença dos atributos [name='x'] e [value='y'] para uma tag HTML <input type='text'> irá inserir a string x=y nos valores enviados name1=val1&name2=val2&... O atributo [id='x'], por sua vez, é utilizado pelo JavaScript. Ele serve para identificar um elemento do DOM (Document Object Model). O documento HTML carregado é, de fato, transformado em uma árvore de JavaScript chamada DOM, na qual cada nó é identificado por seu atributo [id].
Voltemos ao código da função [$(document).ready()]:
// variáveis globais
var formulaire;
var clientValidation;
var double1;
var double2;
var double3;
...
$(document).ready(function() {
// referências globais
formulaire = $("#form");
clientValidation = $("#clientValidation");
double1 = $("#double1");
double2 = $("#double2");
double3 = $("#double3");
...
});
....
// envio de formulário
function postForm01() {
...
}
- linha 10: a expressão [$("#form")] é uma expressão jQuery. Seu valor é uma referência ao nó do DOM com o atributo [id='form '];
- linhas 10-14: recuperam-se as referências a cinco nós do DOM;
- linhas 2-6: as variáveis declaradas fora de uma função são globais para as funções. Aqui, isso significa que as variáveis [formulaire, clientValidation , double1, double2, double3] inicializadas em [$(document).ready()] também serão conhecidas na função [postForm01] da linha 19;
Agora, vamos examinar a função [postForm01]:
// envio de formulário
function postForm01() {
// modo de validação do lado do cliente
var validationActive = clientValidation.val() === "true";
if (validationActive) {
// limpa-se os erros do servidor
clearServerErrors();
// validação do formulário
if (!formulaire.validate().form()) {
// sem envio
return false;
}
}
// valores reais no formato anglo-saxão
var value1 = double1.val().replace(",", ".");
double1.val(value1);
var value2 = double2.val().replace(",", ".");
double2.val(value2);
var value3 = double3.val().replace(",", ".");
double3.val(value3);
// permite que o envio seja realizado
return true;
}
Vale lembrar que essa função jS é executada antes da função [submit] do formulário. Se ela retornar o valor booleano [false] (linha 11), o envio não ocorrerá. Se ela retornar outro valor (linha 22), o envio ocorrerá.
- O código importante está nas linhas 4 a 12;
- linha 4: recuperamos o valor do campo oculto [clientValidation]. Esse valor é 'true' se a validação do cliente deve ser ativada, 'false' caso contrário;
- linha 6: em caso de validação do lado do cliente, apagamos as mensagens de erro do servidor que possam estar presentes porque o usuário acabou de alterar a localidade;
- linha 9: vale lembrar que a variável [formulaire] representa o nó da tag HTML <form>, ou seja, o formulário. Este formulário contém validadores jS que ainda não apresentamos e que serão abordados nos parágrafos a seguir. A expressão [formulaire.validate().form()] força a execução de todos os validadores jS presentes no formulário. Seu valor é [true] se todos os valores testados forem válidos; caso contrário, é [false];
- linha 11: retorna-se o valor [false] se pelo menos um dos valores testados for inválido. Isso impedirá o envio do [submit] do formulário para o servidor;
- linhas 15-20: os identificadores [double1, double2, double3] representam os três números reais do formulário. Dependendo da cultura, o valor inserido é diferente. Com a cultura [fr-FR], insere-se [10,37], enquanto que com a cultura [en-US] insere-se [10.37]. Isso se refere à inserção. Com a cultura [fr-FR], o valor lançado para [double1] será semelhante a [double1=10,37]. Ao chegar ao servidor, o valor [10,37] será rejeitado, pois o servidor espera [10.37], o formato padrão para números reais em Java. Portanto, nas linhas 15 a 20, substitua a vírgula pelo ponto no valor inserido para esses números;
- linha 15: a expressão [double1.val()] gera a sequência de caracteres inserida para o nó [double1]. A expressão [double1.val().replace(",", ".")] substitui, nessa sequência, as vírgulas por pontos. O resultado é a sequência [value1];
- linha 16: a instrução [double1.val(value1)] atribui esse valor [value1] ao nó [double1].
Tecnicamente, se o usuário tiver digitado [10,37] para o valor real [double1], após as instruções anteriores, o nó [double1] terá o valor [10.37] e o valor que será enviado será [param1=val1&double1=10.37¶m2=val2], valor que será aceito pelo servidor;
- linha 22: retorna-se o valor [true] para que o [submit] do formulário seja executado;
Vale lembrar que a função jS [postForm01]:
- executa todos os validadores jS do formulário se a validação do lado do cliente estiver ativada e impede que o [submit] do formulário seja enviado ao servidor caso algum dos valores inseridos tenha sido declarado inválido;
- permite que o [submit] seja executado, seja porque a validação do lado do cliente não está ativada, seja porque está ativada e todos os valores inseridos são válidos;
Resta a instrução da linha [3]:
// apaga-se os erros do servidor
clearServerErrors();
A função [clearServerErrors] tem como objetivo apagar as mensagens presentes na coluna 4 da visualização [vue-01.xml]:
![]() |
Na captura de tela acima, clicamos no link [English]. Observamos que isso gerou um POST dos valores inseridos sem que os validadores jS fossem acionados. Após o retorno do POST, a coluna [Server Validation] é preenchida com eventuais mensagens de erro. Se agora clicarmos no botão [Validate] [2] com os validadores jS ativados [3], então a coluna [Client Validation] [4] será preenchida com mensagens. Se nada for feito, os valores que estavam presentes na coluna [Server Validation] permanecerão, o que causará confusão, pois, no caso de erros detectados pelos validadores jS, o servidor não será acionado. Para evitar isso, apagamos a coluna [Server Validation] na função [postForm01]. É a função [] que realiza essa tarefa:
function clearServerErrors() {
// apaga-se as mensagens de erro do servidor
$(".error").each(function(index) {
$(this).text("");
});
}
Uma particularidade das mensagens de erro é que todas elas pertencem à classe [error]. Por exemplo, para a primeira linha da tabela em [vue-01.html]:
<span th:if="${#fields.hasErrors('strNotEmpty')}" th:errors="*{strNotEmpty}" class="error">Donnée erronée</span>
E esses são os únicos nós do DOM que possuem essa classe. Utilizamos essa propriedade na função [clearServerErrors]:
function clearServerErrors() {
// limpa-se as mensagens de erro do servidor
$(".error").each(function(index) {
$(this).text("");
});
}
- linha 3: a expressão [$(".error")] retorna a coleção de nós do DOM que possuem a classe [error];
- linha 3: a expressão [$(".error").each(function(index){f}] executa a função [f] para cada um dos nós da coleção. Ela recebe um parâmetro [index], que não é utilizado aqui e que corresponde ao número do nó na coleção;
- linha 4: a expressão [$(this)] designa o nó atual na iteração. Trata-se de uma tag HTML <span>. A expressão [$(this).text("")] atribui a string vazia ao texto exibido pela tag <span>;
Vamos examinar agora diferentes validadores jS.
6.3.8. Validador [required]
Vamos examinar o primeiro elemento do formulário:
![]() |
A linha [1] é gerada pela seguinte sequência da visualização [vue-01.xml]:
<!-- obrigatório -->
<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>
Essas linhas referem-se ao campo [strNotEmpty] do formulário [Form01]:
@NotNull
@NotBlank
private String strNotEmpty;
As restrições [1-2] determinam que o campo [strNotEmpty] deve ser uma string existente ([NotNull]), não pode estar vazio e não pode ser composto apenas por espaços ([NotBlank]). Queremos reproduzir essa restrição no lado do cliente usando JavaScript.
Vamos analisar as linhas 5 e 8. A linha 11 não apresenta nenhum problema. Ela exibe a mensagem de erro relacionada ao campo [strNotEmpty]. Comecemos pela linha 5:
<input type="text" th:field="*{strNotEmpty}" data-val="true" th:attr="data-val-required=#{NotNull}" />
A partir desse código, o Thymeleaf irá gerar a seguinte tag:
<input type="text" data-val="true" data-val-required="Field is required" id="strNotEmpty" name="strNotEmpty" value="x" />
- o atributo [data-val='true'] é utilizado pelas bibliotecas de validação jQuery. Sua presença indica que o valor do nó está sendo validado;
- o atributo [data-val-X='msg'] fornece duas informações. [X] é o nome do validador, e [msg] é a mensagem de erro associada a um valor inválido do nó sobre o qual o validador é aplicado. Trata-se apenas de uma informação. Isso não faz com que a mensagem de erro seja exibida;
- [required] é um validador reconhecido pela biblioteca de validação [jquery.validate.unobstrusive] da Microsoft. Não há necessidade de defini-lo. Isso nem sempre será o caso daqui em diante;
- as tags [data-x] são ignoradas pelo HTML5. Elas só são úteis se houver JavaScript para aproveitá-las;
Vamos examinar a linha 8 agora:
<span class="field-validation-valid" data-valmsg-for="strNotEmpty" data-valmsg-replace="true"></span>
Ela serve para exibir a mensagem de erro do validador [required]. Se houver erro, a biblioteca de validação jS substituirá dinamicamente a linha HTML da tabela pelo seguinte código:
<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>
- linha 4: a classe do nó [strNotEmpty] foi alterada. Passou a ser [input-validation-error], o que faz com que o campo com erro seja destacado em vermelho;
- linha 7: a classe do [span] mudou. Passou a ser [field-validation-error], o que fará com que o texto do [span] seja exibido em vermelho;
- linha 8: o [span], que antes estava vazio, agora contém o texto [Le champ est obligatoire]. Esse texto provém da tag [data-val-required="Le champ est obligatoire"] da linha 4;
- linha 7: para exibir a mensagem de erro do nó [strNotEmpty] da linha 4, é necessário utilizar, na linha 7, os atributos [data-valmsg-for="strNotEmpty"] e [data-valmsg-replace="true"];
6.3.9. Validador [assertfalse]
![]() |
A linha [1] é gerada pela seguinte sequência da visualização [vue-01.xml]:
<!-- obrigatório, 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>
Essas linhas referem-se ao campo [assertFalse] do formulário [Form01]:
@NotNull
@AssertFalse
private Boolean assertFalse;
Queremos reproduzir essa restrição no lado do cliente usando JavaScript. As linhas 12 a 17 são agora padrão:
- linhas 12 a 14: exibem, em caso de erro no campo [assertFalse], a mensagem contida no atributo [data-val-assertfalse] da linha 6 ou aquela contida no atributo [data-val-required] da mesma linha. Vale lembrar que essas mensagens são localizadas, ou seja, no idioma escolhido anteriormente pelo usuário ou em francês, caso ele não tenha feito nenhuma escolha;
- linhas 5-10: exibem os botões de opção com validadores JavaScript que são acionados assim que o usuário clica em um deles.
Os dois botões são construídos da mesma maneira. Vamos examinar o primeiro:
<input type="radio" th:field="*{assertFalse}" value="true" data-val="true" th:attr="data-val-required=#{NotNull},data-val-assertfalse=#{AssertFalse}" />
Depois de processada pelo Thymeleaf, essa linha fica assim:
<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" />
Temos validadores [data-val="true"]. São dois. Um validador chamado [required] [data-val-required="Le champ est obligatoire"] e outro chamado [assertfalse] [data-val-assertfalse="Seule la valeur False est acceptée"]. Vale lembrar que o valor do atributo [data-val-X] é a mensagem de erro do validador X.
Já vimos o validador [required]. A novidade aqui é que é possível associar vários validadores a um valor inserido. Embora o validador [required] seja reconhecido pela biblioteca de validação MS (Microsoft), esse não é o caso do validador [assertFalse]. Portanto, vamos aprender a criar um novo validador. Vamos criar vários deles, e eles serão colocados em um arquivo [client-validation.js]:
![]() |
Este arquivo, assim como os demais, é importado pela visão [vue-01.xml] (linha 6 abaixo):
<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>
A adição do validador [assertfalse] resume-se à criação das duas seguintes funções 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("''", "'");
});
Sinceramente, não sou especialista em JavaScript, uma linguagem que ainda me parece bastante obscura. Seus fundamentos são simples, mas as bibliotecas construídas sobre esses fundamentos costumam ser muito complexas. Para escrever as linhas de código acima, me inspirei em códigos encontrados na Internet. Foi o link [http://jsfiddle.net/LDDrk/] que me mostrou o caminho a seguir. Se ele ainda estiver disponível, convido o leitor a consultá-lo, pois é completo e inclui um exemplo funcional. Ele mostra como criar um novo validador e me permitiu criar todos os validadores deste capítulo. Voltemos ao código:
- linhas 2-4: definem o novo validador. A função [$.validator.addMethod] espera, como primeiro parâmetro, o nome do validador e, como segundo parâmetro, uma função que o defina;
- linha 2: a função tem três parâmetros:
- [value]: o valor a ser validado. A função deve retornar [true] se o valor for válido, [false] caso contrário,
- [element]: elemento HTML ao qual pertence o valor a ser validado,
- [param]: um objeto contendo os valores associados aos parâmetros de um validador. Ainda não abordamos esse conceito. Aqui, o validador [assertFalse] não possui parâmetros. É possível determinar se o valor [value] é válido sem a ajuda de informações adicionais. Não seria o mesmo caso se fosse necessário verificar se o valor [value] fosse um número real no intervalo [min, max]. Nesse caso, precisaríamos conhecer [min] e [max]. Esses dois valores são chamados de parâmetros do validador;
- linhas 6-9: uma função necessária para a biblioteca de validação MS. A função [$.validator.unobtrusive.adapters.add] espera, como primeiro parâmetro, o nome do validador; como segundo parâmetro, a matriz de parâmetros do validador; e, como terceiro parâmetro, uma função;
- o validador [assertFalse] não possui parâmetros. Por isso, o segundo parâmetro é uma matriz vazia;
- a função possui apenas um parâmetro, um objeto [options] que contém informações sobre o elemento a ser validado e para o qual é necessário definir duas novas propriedades: [rules] e [messages];
- linha 7: definem-se as regras [rules] para o validador [assertFalse]. Essas regras são os parâmetros do validador [assertFalse], os mesmos que os do parâmetro [param] da linha 2. Esses parâmetros estão em [options.params];
- linha 8: definem a mensagem de erro do validador [assertFalse]. Este é encontrado em [options.message]. Temos a seguinte dificuldade com as mensagens de erro. Nos arquivos de mensagens, encontraremos a seguinte mensagem:
Range.form01.int1014=La valeur doit être dans l''intervalle [10,14]
O apóstrofo duplo é necessário para o Thymeleaf. Ele o interpreta como um apóstrofo simples. Se colocarmos um apóstrofo simples, ele não será exibido pelo Thymeleaf. Agora, essas mensagens também servirão como mensagens de erro para a biblioteca de validação MS. No entanto, o JavaScript exibirá os dois apóstrofos. Na linha 8, substituímos, portanto, o apóstrofo duplo da mensagem de erro por um único.
Para entender um pouco melhor o que está acontecendo, podemos adicionar código de log da biblioteca jS:
// registros
var logs = {
assertfalse : true
}
// -------------- assertfalse
$.validator.addMethod("assertfalse", function(value, element, param) {
// registros
if (logs.assertfalse) {
console.log(jSON.stringify({
"[assertfalse] value" : value
}));
console.log("[assertfalse] element");
console.log(element);
console.log(jSON.stringify({
"[assertfalse] param" : param
}));
}
// teste de validade
return value === "false";
});
$.validator.unobtrusive.adapters.add("assertfalse", [], function(options) {
// registros
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
}));
}
// código
options.rules["assertfalse"] = options.params;
options.messages["assertfalse"] = options.message.replace("''", "'");
});
Esse código utiliza a biblioteca jSON JSON3 [http://bestiejs.github.io/json3/]. Se ativarmos os registros (linha 3), obtemos as seguintes exibições no console:
No carregamento inicial da página, temos os seguintes registros:
A função jS [$.validator.unobtrusive.adapters.add] foi executada. Observa-se o seguinte:
- [options.params] é um objeto vazio, pois o validador [assertFalse] não possui parâmetros;
- [options.message] é a mensagem de erro que criamos para o validador [assertFalse] no atributo [data-val-assertFalse];
- [options.messages] é um objeto que contém as demais mensagens de erro do elemento validado. Aqui encontramos a mensagem de erro que colocamos no atributo [data-val-required];
Agora, vamos inserir um valor incorreto no campo [assertFalse] e validar:
Assim, obtemos os seguintes logs:
![]() |
Neles, observamos o seguinte:
- o valor testado é [true] (linha 118);
- o elemento HTML testado é o botão de opção com o ID [assertFalse1] (linha 122);
- o validador [assertFalse] não possui nenhum parâmetro (linha 123);
Pronto. O que devemos reter de tudo isso?
Para um validador X jS, precisamos definir:
- na tag HTML a ser validada, o atributo [data-val-X='msg'], que define tanto o validador X quanto sua mensagem de erro;
- duas funções jS a serem incluídas no arquivo [client-validation.js]:
- [$.validator.addMethod("X", function(value, element, param)],
- [$.validator.unobtrusive.adapters.add("X", [param1, param2], function(options)];
A seguir, vamos nos basear no que foi feito para esse primeiro validador e simplesmente apresentar as novidades.
6.3.10. Validador [asserttrue]
Este validador é, obviamente, análogo ao validador [assertFalse].
![]() |
A linha [1] é gerada pela seguinte sequência da visualização [vue-01.xml]:
<!-- obrigatório, 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>
Essas linhas referem-se ao campo [assertTrue] do formulário [Form01]:
@NotNull
@AssertTrue
private Boolean assertTrue;
Não há nada de novo nas linhas 1 a 16. Elas utilizam um validador [asserrtrue] que deve ser definido no arquivo [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. Validadores [date] e [past]
![]() |
A linha [1] é gerada pela seguinte sequência da visão [vue-01.xml]:
<!-- obrigatório, data, passada -->
<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>
Essas linhas referem-se ao campo [dateInPast] do formulário [Form01]:
@NotNull
@Past
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date dateInPast;
A linha dos validadores de data é a seguinte:
<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}" />
Nela, há três validadores [data-val-X]: required, date, past. Precisamos definir em [client-validation.js] as funções associadas a esses dois novos validadores:
logs.date = true;
// -------------- data
$.validator.addMethod("date", function(value, element, param) {
// validade
var valide = Globalize.parseDate(value, "yyyy-MM-dd") != null;
// registros
if (logs.date) {
console.log(jSON.stringify({
"[date] value" : value,
"[date] valide" : valide
}));
}
// resultado
return valide;
});
$.validator.unobtrusive.adapters.add("date", [], function(options) {
options.rules["date"] = options.params;
options.messages["date"] = options.message.replace("''", "'");
});
e
logs.past = true;
// -------------- passado
$.validator.addMethod("past", function(value, element, param) {
// validade
var valide = value <= new Date().toISOString().substring(0, 10);
// registros
if (logs.past) {
console.log(jSON.stringify({
"[past] value" : value,
"[past] valide" : valide
}));
}
// resultado
return valide;
});
$.validator.unobtrusive.adapters.add("past", [], function(options) {
options.rules["past"] = options.params;
options.messages["past"] = options.message.replace("''", "'");
});
Antes de explicar o código, vamos dar uma olhada nos logs quando digitamos uma data posterior à de hoje:
A primeira coisa a se notar é que a data a ser validada chega como uma sequência de caracteres no formato [aaaa-mm-jj]. Isso explica as linhas a seguir:
var valide = Globalize.parseDate(value, "yyyy-MM-dd") != null;
A biblioteca [globalize.js] fornece a função [Globalize.parseDate] acima. O primeiro parâmetro é a data como uma sequência de caracteres e o segundo, seu formato. O resultado é um ponteiro null se a data for inválida; caso contrário, é a data resultante.
A validade do validador [past] é verificada pelo código a seguir:
var valide = value <= new Date().toISOString().substring(0, 10);
A seguir, na console, a avaliação da expressão [new Date().toISOString().substring(0, 10)]:
![]() |
A sequência de caracteres [value] deve vir antes, em ordem alfabética, da sequência [new Date().toISOString().substring(0, 10)] para ser válida.
Observe-se que a versão do Chrome utilizada apresenta a data no formato [yyyy-mm-dd]. Em um navegador em que isso não ocorresse, seria necessário indicar explicitamente ao usuário que utilize esse formato de entrada.
6.3.12. Validador [future]
![]() |
A linha [1] é gerada pela seguinte sequência da visualização [vue-01.xml]:
<!-- obrigatório, 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>
Essas linhas referem-se ao campo [dateInFuture] do formulário [Form01]:
@NotNull
@Future
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date dateInFuture;
- na linha 5, surge um novo validador [data-val-future];
Esse validador é, obviamente, muito semelhante ao validador [past]. As duas funções a serem adicionadas em [client-validation.js] são as seguintes:
// -------------- 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. Validadores [int] e [max]
![]() |
A linha [1] é gerada pela seguinte sequência da visão [vue-01.xml]:
<!-- obrigatório, 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>
Essas linhas referem-se ao campo [intMax100] do formulário [Form01]:
@NotNull
@Max(value = 100)
private Integer intMax100;
Na linha 5, há dois novos validadores: [int] e [max]. Este último possui um parâmetro: o valor máximo. Vamos examinar o código HTML gerado pela linha 5:
<!-- obrigatório, int, máx(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>
Vamos relembrar o significado dos diferentes atributos de [data-X]:
- [data-val="true"] indica que há validadores associados ao elemento HTML;
- [data-val-required] apresenta o validador [required] com sua mensagem;
- [data-val-int] apresenta o validador [int] com sua mensagem;
- [data-val-max] introduz o validador [max] com sua mensagem;
- [data-val-max-value="100"] introduz um parâmetro chamado [value] para o validador [max]. [100] é o valor desse parâmetro. É a primeira vez que nos deparamos com o conceito de parâmetros de um validador.
O arquivo [client-validation.js] é complementado com o seguinte validador [int]:
logs.int = true;
// -------------- inteiro
$.validator.addMethod("int", function(value, element, param) {
// validade
valide = /^\s*[-\+]?\s*\d+\s*$/.test(value);
// registros
if (logs.int) {
console.log(jSON.stringify({
"[int] value" : value,
"[int] valide" : valide,
}));
}
// resultado
return valide;
});
$.validator.unobtrusive.adapters.add("int", [], function(options) {
options.rules["int"] = options.params;
options.messages["int"] = options.message.replace("''", "'");
});
- linha 5: utiliza-se uma expressão regular para verificar se a cadeia [value] representa efetivamente um número inteiro. Este pode ser com sinal;
Aqui estão alguns exemplos de logs:
O validador [max] é adicionado da seguinte maneira em [client-validation.js]
// -------------- máximo a ser usado em conjunto com [int] ou [number]
logs.max = true;
$.validator.addMethod("max", function(value, element, param) {
// registros
if (logs.max) {
console.log(jSON.stringify({
"[max] value" : value,
"[max] param" : param
}));
}
// validade
var val = Globalize.parseFloat(value);
if (isNaN(val)) {
// registros
if (logs.max) {
console.log(jSON.stringify({
"[max] valide" : true
}));
}
// resultado
return true;
}
var max = Globalize.parseFloat(param.value);
var valide = val <= max;
// registros
if (logs.max) {
console.log(jSON.stringify({
"[max] valide" : valide
}));
}
// resultado
return valide;
});
$.validator.unobtrusive.adapters.add("max", [ "value" ], function(options) {
options.rules["max"] = options.params;
options.messages["max"] = options.message.replace("''", "'");
});
Vamos tratar agora o caso do parâmetro [value] do validador [max], introduzido pelo atributo [data-val-max-value="100"].
- na linha 35, o parâmetro [value] é incorporado ao segundo parâmetro da função [$.validator.unobtrusive.adapters.add];
- na linha 3, o objeto [param] não estará mais vazio, mas conterá {"value":100};
Para entender o código das linhas 3 a 33, é preciso saber que, quando há vários validadores em um mesmo elemento HTML:
- não se sabe a ordem de execução dos validadores;
- a execução dos validadores é interrompida assim que um validador declara o elemento inválido. É então a mensagem de erro desse último que é associada ao elemento inválido;
Vamos analisar o código:
- linha 12: verifica-se se há um número. Se o validador [int] tiver sido executado antes do validador [max], isso é necessariamente verdade, pois um valor inválido interrompe a execução dos validadores;
- linhas 13-22: se não houver um número, isso significa que o validador [int] ainda não foi executado. Indica-se, então, que o valor testado é válido para permitir que o validador [int] faça seu trabalho e declare o elemento inválido com sua própria mensagem de erro;
- linhas 23-24: calcula a validade de [value];
Aqui estão alguns registros:
Valor inserido | logs |
| |
| |
|
6.3.14. Validador [min]
![]() |
A linha [1] é gerada pela seguinte sequência da visualização [vue-01.xml]:
<!-- obrigatório, 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>
Essas linhas referem-se ao campo [intMin10] do formulário [Form01]:
@NotNull
@Min(value = 10)
private Integer intMin10;
A linha 5 introduz um novo validador [min] [data-val-int=#{typeMismatch}] com um parâmetro [value] [data-val-min-value=#{form01.intMin10.value}"]. Trata-se de um caso semelhante ao do validador [max]. Adiciona-se em [client-validation.js] o seguinte código:
logs.min = true;
//-------------- min deve ser usado em conjunto com [int] ou [number]
$.validator.addMethod("min", function(value, element, param) {
// registros
if (logs.min) {
console.log(jSON.stringify({
"[min] value" : value,
"[min] param" : param
}));
}
// validade
var val = Globalize.parseFloat(value);
if (isNaN(val)) {
// registros
if (logs.min) {
console.log(jSON.stringify({
"[min] valide" : true
}));
}
// resultado
return true;
}
var min = Globalize.parseFloat(param.value);
var valide = val >= min;
// registros
if (logs.min) {
console.log(jSON.stringify({
"[min] valide" : valide
}));
}
// resultado
return valide;
});
$.validator.unobtrusive.adapters.add("min", [ "value" ], function(options) {
options.rules["min"] = options.params;
options.messages["min"] = options.message.replace("''", "'");
});
Aqui estão alguns registros de execução:
Valor inserido | registros |
| |
| |
|
6.3.15. Validador [regex]
![]() |
A linha [1] é gerada pela seguinte sequência da visualização [vue-01.xml]:
<!-- obrigatório, 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>
Essas linhas referem-se ao campo [strBetween4and6] do formulário [Form01]:
@NotNull
@Size(min = 4, max = 6)
private String strBetween4and6;
A linha 5 gera o seguinte 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" />
Essa tag introduz o validador [regex] [data-val-regex="La chaîne doit avoir entre 4 et 6 caractères"] com seu parâmetro [pattern] [data-val-regex-pattern="^.{4,6}$"]. O parâmetro [pattern] é a expressão regular que deve ser verificada pelo valor a ser validado. Nesse caso, a expressão regular verifica se a sequência possui entre 4 e 6 caracteres quaisquer. O validador [regex] está predefinido na biblioteca de validação MS. Portanto, não há nada a ser adicionado ao arquivo [client-validation.js].
6.3.16. Validador [email]
![]() |
A linha [1] é gerada pela seguinte sequência da visualização [vue-01.xml]:
<!-- obrigatório, e-mail -->
<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>
Essas linhas referem-se ao campo [email] do formulário [Form01]:
@NotNull
@Email
@NotBlank
private String email;
A linha 5 gera a seguinte linha 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" />
Essa tag introduz o validador [email] [data-val-email="Adresse mail invalide"]. O validador [email] está predefinido na biblioteca de validação MS. Portanto, não há nada a ser adicionado ao arquivo [client-validation.js].
6.3.17. Validador [range]
![]() |
A linha [1] é gerada pela seguinte sequência da visualização [vue-01.xml]:
<!-- obrigatório, inteiro, intervalo (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>
Essas linhas referem-se ao campo [int1014] do formulário [Form01]:
@Range(min = 10, max = 14)
@NotNull
private Integer int1014;
A linha 5 gera a seguinte linha 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" />
Essa tag introduz um novo validador [range] [data-val-range="La valeur doit être dans l''intervalle [10,14]"] que possui dois parâmetros: [min] [data-val-range-min="10"] e [max] [data-val-range-max="14"].
No arquivo [client-validation.js], definimos o validador [range] da seguinte maneira:
// -------------- intervalo a ser usado em conjunto com [int] ou [number]
logs.range=true
$.validator.addMethod("range", function(value, element, param) {
// registros
if (logs.range) {
console.log(jSON.stringify({
"[range] value" : value,
"[range] param" : param
}));
}
// validade
var val = Globalize.parseFloat(value);
if (isNaN(val)) {
// registros
if (logs.min) {
console.log(jSON.stringify({
"[range] valide" : true
}));
}
// concluído
return true;
}
var min = Globalize.parseFloat(param.min);
var max = Globalize.parseFloat(param.max);
var valide = val >= min && val <= max;
// registros
if (logs.range) {
console.log(jSON.stringify({
"[range] valide" : valide
}));
}
// concluído
return valide;
});
$.validator.unobtrusive.adapters.add("range", [ "min", "max" ], function(options) {
options.rules["range"] = options.params;
options.messages["range"] = options.message.replace("''", "'");
});
Ele é muito semelhante aos validadores [min] e [max] já analisados.
Aqui estão alguns exemplos de logs:
Valor inserido | registros |
| |
| |
|
6.3.18. Validador [number]
![]() |
A linha [1] é gerada pela seguinte sequência da visualização [vue-01.xml]:
<!-- duplo1: obrigatório, número, intervalo (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>
Essas linhas referem-se ao campo [double1] do formulário [Form01]:
@NotNull
@DecimalMax(value = "3.4")
@DecimalMin(value = "2.3")
private Double double1;
A linha 5 gera a seguinte linha 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" />
A tag insere um novo validador [number] com o atributo [data-val-number="Format invalide"]. Esse validador é definido da seguinte forma no arquivo [client-validation.js]:
// -------------- número
logs.number = true;
$.validator.addMethod("number", function(value, element, param) {
var valide = !isNaN(Globalize.parseFloat(value));
// registros
if (logs.number) {
console.log(jSON.stringify({
"[number] value" : value,
"[number] valide" : valide
}));
}
// resultado
return valide;
});
$.validator.unobtrusive.adapters.add("number", [], function(options) {
options.rules["number"] = options.params;
options.messages["number"] = options.message.replace("''", "'");
});
Aqui estão alguns exemplos de registros:
Valor inserido | registros |
Sabe-se que os números reais são sensíveis à cultura. Acima, estamos na cultura [fr-FR]. Ao digitar [2.5] (notação anglo-saxônica), o número é aceito. A culpa é do [Globalize.parseFloat], que aceita as duas notações:
Vamos mudar para o inglês e inserir [+2,5] e [+2.5]. Os registros são os seguintes:
Valor inserido | registros |
Há um problema com [2,5]. Ele foi declarado como um valor válido, mas deveria ser [2.5]. A culpa é de [Globalize.parseFloat]:
No exemplo acima, o [Globalize.parseFloat] ignora a vírgula e considera que o número é 25. Na cultura do [en-US], um número real pode conter um ponto decimal e vírgulas, que às vezes são usadas para separar os milhares.
É possível melhorar isso da seguinte maneira:
// -------------- número
logs.number = true;
$.validator.addMethod("number", function(value, element, param) {
// gerenciamos apenas as culturas [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;
// teste de validade
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));
}
// registros
if (logs.number) {
console.log(jSON.stringify({
"[number] value" : value,
"[number] culture" : culture,
"[number] valide" : valide
}));
}
// resultado
return valide;
});
- linha 5: a expressão regular de um número real para a cultura [fr-FR];
- linha 6: a expressão regular de um número real para a cultura [en-US];
- linha 7: o nome da cultura atual. No nosso exemplo, será uma das duas culturas acima;
- linhas 9-16: a verificação de validade do valor inserido;
- linha 15: previmos o caso em que a cultura não fosse nem [fr-FR] nem [en-US];
Os logs agora apresentam o seguinte:
Cultura [fr-FR]
Valor inserido | registros |
| |
| |
| |
|
Cultura [en-US]
Valor inserido | registros |
| |
| |
|
6.3.19. Validador [custom3]
![]() |
A linha [1] é gerada pela seguinte sequência da visualização [vue-01.xml]:
<!-- double3: obrigatório, número, 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>
Essas linhas referem-se ao campo [double3] do formulário [Form01]:
@NotNull
private Double double3;
Queremos analisar aqui um validador que não valida mais um valor inserido, mas sim uma relação entre dois valores inseridos. Neste caso, queremos que [double1+double3] esteja dentro do intervalo de [10,13].
A linha 5 gera a seguinte linha 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" />
Essa linha introduz o novo validador [custom3] declarado pelo atributo [data-val-custom3="[double3+double1] must be in [10,13]"]. Esse validador possui os seguintes parâmetros:
- [field], declarado pelo atributo [data-val-custom3-field="double1"]. Esse parâmetro indica o campo cujo valor participa do cálculo da validade de [double3];
- [min] declarado pelo atributo [data-val-custom3-min="10.0"]. Esse parâmetro é o mínimo do intervalo [min, max], no qual [double1+double3] deve estar;
- [max] declarado pelo atributo [data-val-custom3-max="13.0"]. Esse parâmetro é o valor máximo do intervalo [min, max], no qual [double1+double3] deve estar;
Este validador é gerenciado da seguinte forma em [client-validation.js]:
// -------------- custom3 usado em conjunto com [number]
logs.custom3 = true;
$.validator.addMethod("custom3", function(value1, element, param) {
// segundo valor
var value2 = $("#" + param.field).val();
// registros
if (logs.custom3) {
console.log(jSON.stringify({
"[custom3] value1" : value1,
"[custom3] param" : param,
"[custom3] value2" : value2
}))
}
// primeiro valor
var valeur1 = Globalize.parseFloat(value1);
if (isNaN(valeur1)) {
// deixamos que o validador [number] faça o trabalho
if (logs.custom3) {
console.log(jSON.stringify({
"[custom3] valide" : true
}))
}
return true;
}
// segundo valor
var valeur2 = Globalize.parseFloat(value2);
if (isNaN(valeur2)) {
// não é possível realizar o cálculo de validade
if (logs.custom3) {
console.log(jSON.stringify({
"[custom3] valide" : false
}))
}
return false;
}
// cálculo de validade
var min = Globalize.parseFloat(param.min);
var max = Globalize.parseFloat(param.max);
var somme = valeur1 + valeur2;
var valide = somme >= min && somme <= max;
// registros
if (logs.custom3) {
console.log(jSON.stringify({
"[custom3] valide" : valide
}))
}
// resultado
return valide;
});
$.validator.unobtrusive.adapters.add("custom3", [ "field", "max", "min" ], function(options) {
options.rules["custom3"] = options.params;
options.messages["custom3"] = options.message.replace("''", "'");
});
Aqui estão alguns exemplos de registros:
Valores inseridos [double1,double3] | registros |
| |
| |
| |
|
6.3.20. Validador [url]
![]() |
A linha [1] é gerada pela seguinte sequência da visualização [vue-01.xml]:
<!-- obrigatório, 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>
Essas linhas referem-se ao campo [url] do formulário [Form01]:
@URL
@NotBlank
private String url;
A linha 5 gera a seguinte linha HTML:
<input type="text" data-val="true" data-val-url="Invalid URL" data-val-required="Field is required" value="" id="url" name="url" />
Ela insere o validador [url] com o atributo [data-val-url]. Esse validador está predefinido na biblioteca de validação jQuery. Não há nada a ser adicionado em [client-validation.js].
6.3.21. Ativação/desativação da validação do lado do cliente
Enquanto a validação do lado do cliente estiver ativa, a validação do lado do servidor nunca será exibida, pois os valores enviados só chegam ao servidor se tiverem sido declarados válidos do lado do cliente. Para que a validação do lado do servidor seja executada, é necessário desativar a validação do lado do cliente. A visualização [vue-01.xml] oferece dois links para gerenciar essa ativação/desativação:
<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>
Esses dois links não ficam visíveis ao mesmo tempo:
![]() | ![]() |
A tradução HTML desses links é a seguinte:
<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>
O script jS [setClientValidation] está definido no arquivo [local.js] (ver acima). Na função [$(document).ready] desse arquivo, os links de validação são utilizados:
// documento pronto
$(document).ready(function() {
// referências globais
...
activateValidationTrue = $("#clientValidationTrue");
activateValidationFalse = $("#clientValidationFalse");
clientValidation = $("#clientValidation");
...
// links de validação
// clientValidation é um campo oculto definido pelo servidor
var validate = clientValidation.val();
setClientValidation2(validate === "true");
});
- linha 5: uma referência ao link de ativação da validação do lado do cliente;
- linha 6: uma referência ao link de desativação da validação do lado do cliente;
- linha 7: uma referência a um campo oculto do formulário que armazena o último estado da ativação na forma de um valor booleano [true : validation client activée, false : validation client désactivée]. Esse campo encontra-se na visualização [vue-01.xml] da seguinte forma:
<input type="hidden" th:field="*{clientValidation}" th:value="*{clientValidation}" value="true" />
e corresponde ao campo [clientValidation] do formulário [Form01]:
// validação do cliente
private boolean clientValidation = true;
- linha 11: recupera-se o valor do campo oculto;
- linha 12: chama-se a seguinte função [setClientValidation2]:
function setClientValidation2(activate) {
// links
if (activate) {
// a validação do cliente está ativa
activateValidationTrue.hide();
activateValidationFalse.show();
// os validadores do formulário são analisados
$.validator.unobtrusive.parse(formulaire);
} else {
// a validação do cliente está desativada
activateValidationFalse.hide();
activateValidationTrue.show();
// desativando os validadores do formulário
formulaire.data('validator', null);
}
}
- linha 1: o parâmetro [activate] tem o valor [true] se for necessário ativar a validação no lado do cliente; caso contrário, é false;
- linhas 5-6: o link de desativação é exibido, enquanto o link de ativação fica oculto;
- linha 8: para que a validação do lado do cliente funcione, é necessário analisar o documento em busca dos validadores [data-val-X]. O parâmetro da função [$.validator.unobtrusive.parse] é o identificador jS do formulário a ser analisado;
- linhas 11-12: o link de ativação é exibido, enquanto o link de desativação fica oculto;
- linha 14: os validadores do formulário são desativados. A partir de agora, é como se não houvesse validadores jS no formulário;
Para que serve essa função [setClientValidation2]? Ela serve para gerenciar os POST. Como o campo [clientValidation] é um campo oculto, ele é enviado e retorna com o formulário devolvido pelo servidor. Então, usa-se seu valor para restabelecer a validação do lado do cliente como estava antes do POST. De fato, não há memória do jS entre as solicitações. Portanto, é necessário que o servidor transmita, na nova visualização, as informações que permitem inicializar o jS dessa visualização. Isso geralmente é feito na função [$(document).ready].
Voltemos à função [setClientValidation], que gerencia o clique nos links de ativação/desativação da validação do lado do cliente:
// validação do lado do cliente
function setClientValidation(activate) {
// gerencia-se a ativação/desativação da validação do cliente
setClientValidation2(activate);
// a escolha do usuário é armazenada no campo oculto
clientValidation.val(activate ? "true" : "false");
// ajustes adicionais
if (activate) {
// a validação do cliente está ativa
// limpa-se todas as mensagens de erro do servidor
clearServerErrors();
// o formulário é validado
formulaire.validate().form();
} else {
// a validação do cliente está desativada
// apaga-se todas as mensagens de erro do cliente
clearClientErrors();
}
}
- linha 4: utiliza-se a função [setClientValidation2] que acabamos de ver;
- linha 6: armazenamos a escolha do usuário no campo oculto para recuperá-la ao retornar da próxima função POST;
- linha 11: se a validação do cliente estiver ativa, apagamos as mensagens de erro da coluna [serveur] da visualização. Descrevemos a função [clearServerErrors] no parágrafo 6.3.7;
- linha 13: os validadores jS são executados para exibir eventuais mensagens de erro na coluna [client] da visualização;
- linha 17: se a validação do cliente estiver desativada, as mensagens de erro da coluna [client] da visualização são apagadas. Vamos examinar, no console de desenvolvimento do Chrome, o código HTML de um elemento com erro:
<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>
- na linha 2, vemos que, na coluna 2 da tabela, o elemento com erro possui o estilo [class="input-validation-error"];
- na linha 5, vemos que, na coluna 3 da tabela, a mensagem de erro tem o estilo [class="field-validation-error"];
Isso se aplica a todos os elementos com erro. Essas duas informações são utilizadas na seguinte função [clearClientErrors]:
// limpar erros do cliente
function clearClientErrors() {
// limpa-se as mensagens de erro do cliente
$(".field-validation-error").each(function(index) {
$(this).text("");
});
// alterando a classe CSS das entradas incorretas
$(".input-validation-error").each(function(index) {
$(this).removeClass("input-validation-error");
});
}
- linhas 4-6: procuram-se todos os elementos do DOM que tenham a classe [field-validation-error] e apaga-se o texto que eles exibem. É assim que as mensagens de erro são apagadas;
- linhas 8-10: procuram-se todos os elementos do DOM que tenham a classe [input-validation-error] e remove-se essa classe deles. Assim, o elemento com erro, que havia sido destacado em vermelho, recupera seu estilo original;


















































