6. Validação JavaScript do lado do cliente
No capítulo anterior, abordámos a validação do lado do servidor. Voltemos à arquitetura de uma aplicação Spring MVC:
![]() |
BD
Até agora, as páginas enviadas ao cliente não continham JavaScript. Vamos agora abordar esta tecnologia, que nos permitirá, numa primeira fase, realizar validações do lado do cliente. O princípio é o seguinte:
- é o JavaScript que envia os valores para o servidor web;
- e, por isso, antes deste POST, pode verificar a validade dos dados e impedir o POST caso estes sejam inválidos;
Vamos utilizar o formulário que validámos do lado do servidor. Vamos agora oferecer a possibilidade de o validar tanto do lado do cliente como do lado do servidor.
Nota: o tema é complexo. O leitor que não estiver interessado neste tema pode passar diretamente para o parágrafo 7.
6.1. As funcionalidades do projeto
Apresentamos algumas vistas do projeto para ilustrar as suas funcionalidades. A página inicial é obtida 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 do lado do cliente, as validações do lado do servidor são sempre bem-sucedidas. Por isso, disponibilizámos um link para desativar as validações do lado do cliente. Quando se está neste modo, volta-se ao modo de funcionamento que já analisámos. Eis um exemplo:
123 ![]() |
- em [1], os valores introduzidos;
- em [2], as mensagens de erro relacionadas com as entradas;
- em [3], um resumo dos erros, indicando para cada um deles:
- o nome do campo validado,
- o código de erro,
- a mensagem predefinida para esse código de erro;
Agora, vamos ativar a validação do lado do cliente:
![]() |
- em [1], os valores introduzidos. É possível observar que as entradas erradas têm um estilo específico;
- em [2], as mensagens de erro associadas às entradas incorretas. São idênticas às geradas pelo servidor;
- em [3-4], já não há nada, pois enquanto houver entradas erradas, o POST para o servidor não é enviado;
6.2. Validação do lado do servidor
6.2.1. Configuração
Começamos por criar um novo projeto Maven, o [springmvc-validation-client]:
![]() |
Desenvolvemos o projeto da seguinte forma:
![]() |
A classe [Config] configura o projeto. É idêntica à que existia nos 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);
// é apresentada 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);
}
}
}
- na linha 13, o Spring Boot é iniciado com o ficheiro de configuração [Config];
- linhas 15-20: para o exemplo, mostramos como apresentar a lista de objetos geridos pelo Spring. Isto pode ser útil se, por vezes, tivermos a impressão de que o Spring não está a gerir um dos nossos componentes. É uma forma de verificar isso. É também uma forma de verificar a autoconfiguração efetuada pelo Spring Boot. Na consola, obtemos uma lista semelhante à seguinte:
Destacámos os objetos definidos na classe [Config].
6.2.2. O modelo do formulário
Vamos continuar a explorar o projeto:
![]() |
A classe [Form01] é a classe que irá receber os valores enviados. Tem a seguinte estrutura:
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 do 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. Neste caso, vamos exigir que [double1+double2] se encontre 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) {
...
}
}
- na linha 9, a ação [/js01];
- linha 10: é instanciado um objeto do tipo [Form01] 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 estas informações, prepara-se o modelo;
- linha 12: é apresentada a vista [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ó 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));
// a eventual mensagem
if (message != null) {
model.addAttribute("message", message);
}
}
- o objetivo do método [setModel] é inserir no modelo:
- informações sobre a localização,
- a mensagem passada como último parâmetro;
- linha 14: insere-se no modelo informações sobre a localização (língua, país);
- linhas 16-18: insere-se na configuração regional a eventual mensagem passada como parâmetro;
- linhas 8 e 12: as informações sobre a localização também são armazenadas no formulário [Form01]. O JavaScript irá utilizar esta informação;
Os valores introduzidos no formulário [vue-01.xml] serão enviados para a ação seguinte, [/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);
...
}
- 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]. Quando chegamos à linha 3, essa validação ainda não foi efetuada;
- 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]. Esta interface tem 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]. Deve devolver true para indicar que suporta esta classe, ou false caso contrário;
- linha 22: indicamos que a classe [Form01Validator] apenas valida objetos do tipo [Form01];
- linhas 15-18: recordemos que pretendemos implementar a restrição [double1+double2] no intervalo [10,13]. em vez de nos limitarmos a este intervalo, vamos verificar a restrição [double1+double2] no intervalo [min, max]. É por isso que temos um construtor com estes 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 efetuada pelo método [validate] falhar, 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. Neste caso, testámos os campos [double1, double2]. Pode indicar-se um dos dois,
- a mensagem de erro associada ou, mais precisamente, a sua chave nos ficheiros 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 com {0} e {1}. Por isso, é necessário fornecer dois valores para esta mensagem. É isso que faz o terceiro parâmetro do método [Errors.rejectValue].
- O quarto parâmetro é uma mensagem predefinida 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, este objeto terá mais um erro;
- linha 5: verifica-se se existem erros de validação;
- linhas 7-10: percorre-se a lista de erros para registar, para cada um deles:
- o nome do objeto validado,
- o seu código de erro,
- a sua mensagem de erro por predefinição;
- linha 10: com estas informações, constrói-se o modelo da vista [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 vista
A vista [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 ficheiros 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: um grande número de importações de bibliotecas JavaScript que podemos ignorar aqui;
- linha 14: apresenta a localização inserida no modelo pelo servidor;
- linha 59: apresenta a mensagem inserida no modelo pelo servidor;
O código das linhas 33-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 analisar 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, do lado do cliente, uma biblioteca de validação chamada [jquery.validate]. Todos os atributos [data-x] destinam-se a ela. Quando a validação do lado do cliente for desativada, esses atributos não serão utilizados. Por isso, por enquanto, não é necessário compreendê-los. Podemos simplesmente concentrar-nos 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, existe uma dificuldade em gerar o atributo [data-val-required="Le champ est obligatoire"]. Com efeito, o valor associado ao atributo provém dos ficheiros de mensagens externalizados. Por isso, é necessário recorrer a uma expressão Thymeleaf para o obter. Trata-se da seguinte expressão: [th:attr="data-val-required=#{NotNull}"]. Esta expressão é avaliada e o seu valor é inserido tal como está na baliza HTML gerada. Chama-se [th:attr] porque é utilizada para gerar atributos não predefinidos no Thymeleaf. Encontrámos atributos predefinidos [th:text, th:value, th:class, ...], mas não existe nenhum atributo [th:data-val-required].
6.2.5. A folha de estilo
Acima, encontramos classes CSS, tais como [class="field-validation-valid"]. Algumas destas classes são utilizadas pela biblioteca JavaScript de validação. Estão definidas no seguinte ficheiro [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 do lado do cliente
6.3.1. Noções básicas de jQuery e de JavaScript
A validação do lado do cliente é feita com JavaScript. Vamos recorrer ao framework jQuery, que disponibiliza inúmeras funções que facilitam o desenvolvimento em JavaScript. Apresentamos os conceitos básicos do jQuery que é necessário conhecer para compreender os scripts deste capítulo e dos seguintes.
Criamos um ficheiro estático HTML [JQuery-01.html] que colocamos numa pasta [static / vues]:
![]() |
Este ficheiro 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 experimentar com este elemento.
Temos de descarregar o ficheiro [jquery-1.11.1.min.js]. Encontramos a versão mais recente do jQuery no URL [http://jquery.com/download/]:

Colocaremos o ficheiro descarregado na pasta [static / js]:
![]() |
Feito isto, acedemos à visualização estática [jQuery-01.html] com o Chrome [1-2]:
![]() |
No Google Chrome, execute [Ctrl-Maj-I] para aceder às ferramentas de desenvolvimento [3]. O separador [Console] [4] permite executar código JavaScript. Apresentamos a seguir alguns comandos JavaScript a introduzir, acompanhados de uma explicação.
JS | resultado |
|
: devolve a coleção de todos os elementos com o id [element1]; portanto, normalmente uma coleção de 0 ou 1 elemento, uma vez que não é possível ter dois ids idênticos numa página HTML. | ![]() |
|
: atribui o texto [blabla] a todos os elementos da coleção. Isto tem como efeito alterar o conteúdo apresentado pela página | ![]() |
|
oculta os elementos da coleção. O texto [blabla] já não é apresentado. | ![]() |
|
: volta a exibir a coleção. Isto permite-nos ver que o elemento com o ID [element1] tem o atributo CSS style='display: none;', o que faz com que o elemento fique oculto. | |
|
: apresenta os elementos da coleção. O texto [blabla] volta a aparecer. É o atributo CSS style='display: block;' que garante esta apresentação. | ![]() |
|
: define um atributo para todos os elementos da coleção. O atributo é, neste caso, [style] e o seu valor é [color: red]. O texto [blabla] fica a vermelho. | ![]() |
![]() | |
![]() |
Note-se que o URL do navegador não se alterou durante todas estas operações. Não houve qualquer comunicação com o servidor web. Tudo decorre no interior do 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. Não reflete de forma alguma as alterações que fizemos no elemento nas linhas 10-12. É importante ter isto em conta ao depurar JavaScript. Por isso, muitas vezes é inútil visualizar o código-fonte da página apresentada.
Já sabemos o suficiente para compreender os scripts jS que se seguem.
6.3.2. As bibliotecas de validação jS
Vamos utilizar bibliotecas do ecossistema jQuery. Em torno do jQuery, existe um certo número de projetos que, por sua vez, dão origem a bibliotecas. Vamos utilizar a biblioteca de validação [jquery.validate.unobstrusive] criada pela Microsoft e doada à fundação jQuery. Passaremos a referir-nos a ela como biblioteca de validação MS ou, mais simplesmente, biblioteca MS. Para a obter, é necessário um ambiente Microsoft Visual Studio. Não encontrei outra forma de a obter. É possível utilizar 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 obter esta biblioteca e aquelas nas quais ela se baseia nos exemplos disponíveis no site deste documento.
Cria-se um projeto de consola com o Visual Studio [1-4]:
|
![]() |
- em [5], o projeto de consola;
- em [6-7]: vamos adicionar os pacotes [NuGet] ao projeto. [NuGet] é uma função do Visual Studio que permite descarregar bibliotecas na forma de DLL, mas também bibliotecas jS.
![]() |
- em [9-10], faça uma pesquisa com a palavra-chave [jQuery];
- para [11-13], descarregue, pela ordem indicada, as bibliotecas jS necessárias para a validação do lado do cliente;
- em [14], descarregue também a biblioteca [Microsoft jQuery Unobtrusive Ajax], que iremos utilizar em breve;
![]() |
- em [15-16], pesquise pacotes com a palavra-chave [globalize];
- para o [17], descarregue a biblioteca [jQuery.Validation.Globalize];
![]() |
Estes vários downloads instalaram várias bibliotecas jS na pasta [Scripts] do projeto [18]. Nem todas são úteis. Cada ficheiro existe em duas cópias:
- [js]: a versão legível da biblioteca;
- [min.js]: a versão não legível, também conhecida como «minificada» (minified), da biblioteca. Não é propriamente ilegível. Trata-se de texto. Mas não é compreensível. É a versão a utilizar em produção, pois este ficheiro é mais pequeno do que a versão correspondente [js] e, por isso, melhora a velocidade das trocas cliente/servidor;
As versões [min.map] não são indispensáveis. Na pasta [cultures], pode-se conservar apenas as configurações regionais geridas pela aplicação.
Com o Explorador do Windows, copiem estes ficheiros para a pasta [static / js / jquery] do projeto [springmvc-validation-client] e mantenham apenas os ficheiros ú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 poderem ser utilizadas, estas bibliotecas têm de ser importadas através da vista [vue-01.xml]:
<head>
<title>Spring 4 MVC</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<link rel="stylesheet" href="/css/form01.css" />
<script type="text/javascript" src="/js/jquery/jquery-2.1.1.min.js"></script>
<script type="text/javascript" src="/js/jquery/jquery.validate.min.js"></script>
<script type="text/javascript" src="/js/jquery/jquery.validate.unobtrusive.min.js"></script>
<script type="text/javascript" src="/js/jquery/globalize/globalize.js"></script>
<script type="text/javascript" src="/js/jquery/globalize/cultures/globalize.culture.fr-FR.js"></script>
<script type="text/javascript" src="/js/jquery/globalize/cultures/globalize.culture.en-US.js"></script>
<script type="text/javascript" src="/js/client-validation.js"></script>
<script type="text/javascript" src="/js/local.js"></script>
<script th:inline="javascript">
/*<![CDATA[*/
var culture = [[${locale}]];
Globalize.culture(culture);
/*]]>*/
</script>
</head>
- linha 11: a importação de um ficheiro jS de que ainda não falámos;
- linhas 13-18: um script jS interpretado pelo Thymelaf. Este script gere a configuração regional do lado do cliente;
6.3.4. Gestão da localização do lado do cliente
A localização do 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}]]. Repare na sintaxe específica desta expressão. Isto deve-se ao facto de estar escrita em JavaScript. A expressão [[${locale}]] será substituída pelo valor da chave [locale] do modelo da vista;
O resultado no fluxo HTML gerado a partir destas linhas é o seguinte:
<script>
/*<![CDATA[*/
var culture = 'en-US';
Globalize.culture(culture);
/*]]>*/
</script>
As linhas 3 e 4 definem a configuração de idioma do lado do cliente. Apenas duas são geridas: [fr-FR] e [en-US]. É por isso que importámos apenas dois ficheiros 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 utilizar 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 vista 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 20: a localização [fr-FR] ou [en-US] é inserida no modelo da vista [vue-01.xml] (linha 4). É de salientar uma fonte de complicações. Enquanto uma localização francesa é indicada como [fr-FR] do lado do cliente, é indicada como [fr_FR] do lado do servidor. É por esta razão que, nas linhas 14 e 18, é armazenada desta forma no objeto [Form01 formulaire], que recebe os valores enviados;
É importante notar 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. Isto não internacionaliza as mensagens apresentadas pela página. Apenas altera a forma como são interpretadas determinadas informações que dependem da cultura de um país. Com a cultura [fr_FR], o número real [12,78] é válido, ao passo que é inválido com a cultura [en-US]. Nesse caso, deve escrever-se [12.78]. Da mesma forma, a data [12/01/2014] é válida na cultura [fr-FR], enquanto que na cultura [en-US] é necessário escrever [01/12/2014]. Os ficheiros da pasta [jquery / globalize] tratam deste tipo de problemas:
![]() |
A internacionalização das mensagens de erro é gerida exclusivamente do lado do servidor. Veremos que a página HTML / jS inclui mensagens de erro correspondentes à configuração regional gerida 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 ficheiros de mensagens
A vista [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 ficheiro [messages.properties] é uma cópia do ficheiro de mensagens em inglês. Por conseguinte, qualquer configuração regional diferente de [fr] utilizará mensagens em inglês. Recorde-se que o ficheiro [messages_fr.properties] é utilizado para qualquer configuração regional [fr_XX], tal como [fr_CA] ou [fr_FR].
A vista [vue-01.xml] utiliza as chaves destas mensagens. Se desejar conhecer o valor associado a estas chaves, o leitor é convidado a voltar a este parágrafo para o descobrir.
6.3.6. Alteração da configuração regional
A vista [vue-01.xml] apresenta quatro ligações:
<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 analisar os dois links que permitem alterar a configuração regional 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>
Ao clicar nestes links, é executado um script jS presente no ficheiro [local.js] [2]. Em ambos os casos, é chamada uma função jS [setLocale]:
// configuração regional
function setLocale(locale) {
// atualiza-se a localização
lang.val(locale);
// envia-se o formulário — isto não aciona os validadores do cliente — e é por isso que não se inibiu a validação do lado do cliente
document.form.submit();
}
Para compreender a linha 4, é necessário um preâmbulo. A vista [vue-01.xml] inclui um campo oculto denominado [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 pretende enriquecer os valores enviados. O JavaScript permite atribuir-lhes um valor e esse valor é enviado como uma entrada normal efetuada pelo utilizador. 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 do campo [Form01.lang] no momento da geração do HTML. O que é importante notar é o identificador jS do nó [id="lang"]. Este 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 configuração regional
lang.val(locale);
// envia-se o formulário — por alguma razão desconhecida, isto não aciona os validadores do cliente
// é por isso que não se inibiu a validação
document.form.submit();
}
- linhas 5-8: a função jS [$(document).ready(f)] é uma função que é executada quando o navegador carregou a totalidade do documento enviado pelo servidor. O 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. O 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 todas as funções. Neste caso, 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 baliza HTML do nó passa a ser:
<input type="hidden" value="xx_XX" id="lang" name="lang" />
O JavaScript permite alterar 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 ter várias etiquetas <form> e, portanto, vários formulários. Neste caso, temos apenas um. O [document.form.submit] envia este formulário como se o utilizador tivesse clicado num botão com o atributo [type='submit']. Para que ação são enviadas as valores do formulário? Para saber isso, é necessário consultar 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 irá receber os valores enviados é aquela designada pelo atributo [th:action]. Será, portanto, a ação [/js02.html]. Recorde-se que, neste nome, o sufixo [.html] será removido e, no final, será a ação [/js02] que será executada. O que é importante compreender é que o novo valor [xx_XX] do nó [lang] será enviado na forma [lang=xx_XX]. No entanto, configurámos a nossa aplicação para interceptar o parâmetro [lang] e interpretá-lo como uma alteração da localização. Assim, do lado do servidor, a localização 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 vista 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 vista [vue-01.xml] será apresentada 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. Do lado do cliente, este valor será utilizado para atualizar a localização do lado do cliente. Já descrevemos este processo;
- linhas 14-15: se todos os valores enviados forem válidos, então ocorre 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] irá então definir a cultura do cliente como [xx-XX];
Agora, vejamos a influência da configuração regional na vista [vue-01.xml]. Por enquanto, não a apresentámos na íntegra, pois 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>
Este código apresenta 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 baliza:
<input type="text" data-val="true" data-val-required="Field is required" id="strNotEmpty" name="strNotEmpty" />
ou a baliza:
<input type="text" data-val="true" data-val-required="Le champ est obligatoire" id="strNotEmpty" name="strNotEmpty" />
Os atributos [data-x] são utilizados pela biblioteca de validação jS.
Em suma, fica a saber-se que os dois links para alterar a configuração regional:
- provocam uma POST dos valores introduzidos;
- alteram a configuração regional tanto no lado do servidor como no lado do cliente;
- geram uma página HTML que inclui as mensagens de erro destinadas à biblioteca de validação jS, e que essas mensagens estão no idioma da configuração regional selecionada;
6.3.7. O POST dos valores introduzidos
Analisemos o botão [Valider], que envia os valores introduzidos na vista [vue-01.xml]. O seu código HTML é o seguinte:
<!-- botão de validação -->
<input type="submit" value="Valider" onclick="javascript:postForm01()" />
Se o JavaScript estiver ativo no navegador, clicar no botão irá desencadear a execução do método [postForm01]. Se esta função devolver o valor booleano [False], então o submit não será executado. Se devolver outro valor, então será executado. Esta função encontra-se no ficheiro [local.js]:
![]() |
É importada pela vista [vue-01.xml] através da 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>
Neste ficheiro, encontra-se 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 que é executada quando o navegador tiver carregado a totalidade do documento enviado pelo servidor. O 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 compreender estas linhas, é necessário analisar tanto o código Thymeleaf como 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 baliza HTML <input type='text'> irá inserir a cadeia x=y nos valores enviados name1=val1&name2=val2&... O atributo [id='x'] é utilizado pelo JavaScript. Serve para identificar um elemento do DOM (Document Object Model). O documento HTML carregado é, de facto, transformado numa árvore de JavaScript denominada DOM, onde cada nó é identificado pelo 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. O 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 todas as funções. Neste caso, 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 analisar 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) {
// limpar erros do servidor
clearServerErrors();
// validação do formulário
if (!formulaire.validate().form()) {
// sem envio
return false;
}
}
// valores reais no formato anglo-saxónico
var value1 = double1.val().replace(",", ".");
double1.val(value1);
var value2 = double2.val().replace(",", ".");
double2.val(value2);
var value3 = double3.val().replace(",", ".");
double3.val(value3);
// deixa-se que o envio seja efetuado
return true;
}
Recorde-se que esta função jS é executada antes da função [submit] do formulário. Se ela devolver o valor booleano [false] (linha 11), o envio não será efetuado. Se devolver outro valor (linha 22), o envio será efetuado.
- O código importante está nas linhas 4 a 12;
- linha 4: recupera-se o valor do campo oculto [clientValidation]. Este valor é «true» se a validação do cliente tiver de ser ativada, «false» caso contrário;
- linha 6: em caso de validação do lado do cliente, apagam-se as mensagens de erro do servidor que possam estar presentes devido ao facto de o utilizador ter acabado de alterar a configuração regional;
- linha 9: recorde-se 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 apresentámos e que serão abordados nos parágrafos seguintes. A expressão [formulaire.validate().form()] força a execução de todos os validadores jS presentes no formulário. O seu valor é [true] se todos os valores testados forem válidos, [false] caso contrário;
- linha 11: é devolvido o valor [false] se pelo menos um dos valores testados for inválido. Isto 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 introduzido é diferente. Com a cultura [fr-FR], escreve-se [10,37], enquanto que com a cultura [en-US] escreve-se [10.37]. Isto aplica-se à introdução de dados. 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 este espera [10.37], o formato predefinido para números reais em Java. Assim, nas linhas 15 a 20, substitui-se, no valor introduzido para estes números, a vírgula pelo ponto;
- linha 15: a expressão [double1.val()] gera a cadeia de caracteres introduzida para o nó [double1]. A expressão [double1.val().replace(",", ".")] substitui, nessa cadeia, as vírgulas por pontos. O resultado é a cadeia [value1];
- linha 16: a instrução [double1.val(value1)] atribui este valor [value1] ao nó [double1].
Tecnicamente, se o utilizador tiver introduzido [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 esse que será aceite pelo servidor;
- linha 22: atribui-se o valor [true] para que o [submit] do formulário seja executado;
É importante notar 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 se algum dos valores introduzidos tiver sido declarado inválido;
- permite que o [submit] seja executado, quer porque a validação do lado do cliente não está ativada, quer porque está ativada e todos os valores introduzidos são válidos;
Resta a instrução da linha [3]:
// eliminam-se os erros do servidor
clearServerErrors();
A função [clearServerErrors] tem como objetivo apagar as mensagens presentes na coluna 4 da vista [vue-01.xml]:
![]() |
Na captura de ecrã acima, clicámos na ligação [English]. Verificámos que isso provocava um POST dos valores introduzidos, 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 não se fizer nada, os valores que estavam presentes na coluna [Server Validation] permanecerão, o que criará confusão, uma vez que, no caso de erros detetados pelos validadores jS, o servidor não é solicitado. Para evitar isso, apaga-se a coluna [Server Validation] na função [postForm01]. É a função [] que realiza esta tarefa:
function clearServerErrors() {
// eliminam-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 estes são os únicos nós do DOM que têm esta classe. Utilizamos esta propriedade na função [clearServerErrors]:
function clearServerErrors() {
// eliminam-se as mensagens de erro do servidor
$(".error").each(function(index) {
$(this).text("");
});
}
- linha 3: a expressão [$(".error")] devolve a coleção de nós do DOM que têm 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. 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 baliza HTML <span>. A expressão [$(this).text("")] atribui a cadeia vazia ao texto exibido pela baliza <span>;
Vamos agora analisar diferentes validadores jS.
6.3.8. Validador [required]
Analisemos o primeiro elemento do formulário:
![]() |
A linha [1] é gerada pela seguinte sequência da vista [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>
Estas linhas dizem respeito 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 cadeia de caracteres existente ([NotNull]), não pode estar vazio e não pode ser constituído apenas por espaços ([NotBlank]). Pretendemos reproduzir esta restrição do lado do cliente com JavaScript.
Analisemos as linhas 5 e 8. A linha 11 não apresenta qualquer problema. Apresenta a mensagem de erro relacionada com o campo [strNotEmpty]. Comecemos pela linha 5:
<input type="text" th:field="*{strNotEmpty}" data-val="true" th:attr="data-val-required=#{NotNull}" />
A partir deste código, o Thymeleaf irá gerar a seguinte baliza:
<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. A sua presença indica que o valor do nó é sujeito a validação;
- o atributo [data-val-X='msg'] fornece duas informações. [X] é o nome do validador, [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. Não provoca a exibição da mensagem de erro;
- [required] é um validador reconhecido pela biblioteca de validação [jquery.validate.unobstrusive] da Microsoft. Não é necessário defini-lo. Não será sempre assim daqui em diante;
- as balizas [data-x] são ignoradas pelo HTML5. Só são úteis se houver JavaScript para as utilizar;
Vamos agora analisar a linha 8:
<span class="field-validation-valid" data-valmsg-for="strNotEmpty" data-valmsg-replace="true"></span>
Serve para apresentar a mensagem de erro do validador [required]. Se houver um 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 a vermelho;
- linha 7: a classe do [span] mudou. Passou a ser [field-validation-error], o que fará com que o texto do [span] seja apresentado a vermelho;
- linha 8: o [span], que anteriormente estava vazio, tem agora o texto [Le champ est obligatoire]. Este texto provém da baliza [data-val-required="Le champ est obligatoire"] da linha 4;
- linha 7: para apresentar 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 vista [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>
Estas linhas dizem respeito ao campo [assertFalse] do formulário [Form01]:
@NotNull
@AssertFalse
private Boolean assertFalse;
Pretendemos reproduzir esta restrição do lado do cliente com JavaScript. As linhas 12-17 são agora padrão:
- linhas 12-14: exibem, em caso de erro no campo [assertFalse], a mensagem contida no atributo [data-val-assertfalse] da linha 6 ou a contida no atributo [data-val-required] da mesma linha. Recorde-se que estas mensagens são localizadas, ou seja, no idioma previamente escolhido pelo utilizador ou em francês, caso este não tenha feito qualquer escolha;
- linhas 5-10: apresentam os botões de opção com validadores JavaScript que são acionados assim que o utilizador clica num deles.
Os dois botões são construídos da mesma forma. Vamos analisar 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, esta linha passa a ter o seguinte aspeto:
<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 denominado [required] [data-val-required="Le champ est obligatoire"] e outro denominado [assertfalse] [data-val-assertfalse="Seule la valeur False est acceptée"]. Recorde-se que o valor do atributo [data-val-X] corresponde à mensagem de erro do validador X.
Já vimos o validador [required]. A novidade aqui é que é possível associar vários validadores a um valor introduzido. Se o validador [required] é conhecido pela biblioteca de validação MS (Microsoft), o mesmo não se aplica ao validador [assertFalse]. Vamos, portanto, aprender a criar um novo validador. Vamos criar vários e estes serão colocados num ficheiro [client-validation.js]:
![]() |
Este ficheiro, tal como os outros, é importado pela vista [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, para mim, continua a ser um mistério. Os seus fundamentos são simples, mas as bibliotecas construídas sobre esses fundamentos são frequentemente muito complexas. Para escrever as linhas de código acima, inspirei-me em códigos encontrados na Internet. Foi o link [http://jsfiddle.net/LDDrk/] que me indicou o caminho a seguir. Se ainda existir, convido o leitor a consultá-lo, pois é completo e inclui um exemplo funcional. Mostra como criar um novo validador e permitiu-me 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 validar. A função deve devolver [true] se o valor for válido, [false] caso contrário,
- [element]: elemento HTML ao qual pertence o valor a validar,
- [param]: um objeto que contém os valores associados aos parâmetros de um validador. Ainda não introduzimos este conceito. Aqui, o validador [assertFalse] não tem parâmetros. É possível determinar se o valor [value] é válido sem recorrer a informações adicionais. A situação seria diferente se fosse necessário verificar se o valor [value] fosse um número real no intervalo [min, max]. Nesse caso, teríamos de conhecer os valores [min] e [max]. A estes dois valores chamamos «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 tem parâmetros. É por isso que o segundo parâmetro é uma matriz vazia;
- a função tem apenas um parâmetro, um objeto [options] que contém informações sobre o elemento a validar e para o qual é necessário definir duas novas propriedades: [rules] e [messages];
- linha 7: definem-se as regras [rules] para o validador [assertFalse]. Estas regras correspondem aos parâmetros do validador [assertFalse], os mesmos que os do parâmetro [param] da linha 2. Estes parâmetros encontram-se em [options.params];
- linha 8: definem a mensagem de erro do validador [assertFalse]. Este encontra-se em [options.message]. Temos a seguinte dificuldade com as mensagens de erro. Nos ficheiros 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. Este interpreta-o como um apóstrofo simples. Se colocarmos um apóstrofo simples, este não é exibido pelo Thymeleaf. Ora, estas mensagens vão também servir de mensagens de erro para a biblioteca de validação MS. No entanto, o JavaScript irá exibir os dois apóstrofos. Na linha 8, substituímos, portanto, o apóstrofo duplo da mensagem de erro por um único apóstrofo.
Para perceber um pouco o que se passa, podemos adicionar código de registo da biblioteca jS:
// registos
var logs = {
assertfalse : true
}
// -------------- assertfalse
$.validator.addMethod("assertfalse", function(value, element, param) {
// registos
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) {
// registos
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("''", "'");
});
Este código utiliza a biblioteca jSON JSON3 [http://bestiejs.github.io/json3/]. Se ativarmos os registos (linha 3), obtemos as seguintes mensagens na consola:
No carregamento inicial da página, obtêm-se os seguintes registos:
A função jS [$.validator.unobtrusive.adapters.add] foi executada. Ficamos a saber o seguinte:
- [options.params] é um objeto vazio, uma vez que o validador [assertFalse] não tem parâmetros;
- [options.message] é a mensagem de erro que criámos para o validador [assertFalse] no atributo [data-val-assertFalse];
- [options.messages] é um objeto que contém as outras mensagens de erro do elemento validado. Aqui encontramos a mensagem de erro que colocámos no atributo [data-val-required];
Agora, vamos atribuir um valor incorreto ao campo [assertFalse] e validar:
Obtenemos então os seguintes registos:
![]() |
Neles, observamos o seguinte:
- o valor testado é o valor [true] (linha 118);
- o elemento HTML testado é o botão de opção com o ID [assertFalse1] (linha 122);
- o validador [assertFalse] não tem nenhum parâmetro (linha 123);
Pronto. O que devemos reter de tudo isto?
Para um validador X jS, temos de definir:
- na baliza HTML a validar, o atributo [data-val-X='msg'], que define tanto o validador X como a sua mensagem de erro;
- duas funções jS a incluir no ficheiro [client-validation.js]:
- [$.validator.addMethod("X", function(value, element, param)],
- [$.validator.unobtrusive.adapters.add("X", [param1, param2], function(options)];
Posteriormente, vamos basear-nos no que foi feito para este primeiro validador e limitar-nos a apresentar as novidades.
6.3.10. Validador [asserttrue]
Este validador é, evidentemente, análogo ao validador [assertFalse].
![]() |
A linha [1] é gerada pela seguinte sequência da vista [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>
Estas linhas dizem respeito ao campo [assertTrue] do formulário [Form01]:
@NotNull
@AssertTrue
private Boolean assertTrue;
Não há nada de novo nas linhas 1 a 16. Estas utilizam um validador [asserrtrue] que deve ser definido no ficheiro [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 vista [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>
Estas linhas dizem respeito 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 encontram-se três validadores [data-val-X]: required, date, past. Temos de definir em [client-validation.js] as funções associadas a estes dois novos validadores:
logs.date = true;
// -------------- data
$.validator.addMethod("date", function(value, element, param) {
// validade
var valide = Globalize.parseDate(value, "yyyy-MM-dd") != null;
// registos
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);
// registos
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, vejamos os registos quando se introduz uma data posterior à de hoje:
A primeira coisa a notar é que a data a validar é recebida como uma cadeia de caracteres no formato [aaaa-mm-jj]. O que explica as linhas seguintes:
var valide = Globalize.parseDate(value, "yyyy-MM-dd") != null;
A biblioteca [globalize.js] fornece a função [Globalize.parseDate] acima referida. O primeiro parâmetro é a data como cadeia de caracteres e o segundo é o 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 seguinte:
var valide = value <= new Date().toISOString().substring(0, 10);
Eis, numa consola, a avaliação da expressão [new Date().toISOString().substring(0, 10)]:
![]() |
A cadeia de caracteres [value] deve preceder alfabeticamente a cadeia [new Date().toISOString().substring(0, 10)] para ser válida.
Note-se que a versão do Chrome utilizada apresenta a data no formato [yyyy-mm-dd]. Num navegador em que tal não aconteça, seria necessário indicar explicitamente ao utilizador que utilize este formato de introdução de dados.
6.3.12. Validador [future]
![]() |
A linha [1] é gerada pela seguinte sequência da vista [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>
Estas linhas dizem respeito 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];
Este validador é, naturalmente, muito semelhante ao validador [past]. As duas funções a adicionar 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 vista [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>
Estas linhas dizem respeito ao campo [intMax100] do formulário [Form01]:
@NotNull
@Max(value = 100)
private Integer intMax100;
Na linha 5, existem dois novos validadores: [int] e [max]. Este último tem um parâmetro: o valor máximo. Analisemos 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>
Recorde-se o significado dos diferentes atributos [data-X]:
- [data-val="true"] indica que existem validadores associados ao elemento HTML;
- [data-val-required] apresenta o validador [required] com a sua mensagem;
- [data-val-int] apresenta o validador [int] com a sua mensagem;
- [data-val-max] introduz o validador [max] com a sua mensagem;
- [data-val-max-value="100"] introduz um parâmetro denominado [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 ficheiro [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);
// registos
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;
Eis alguns exemplos de registos:
O validador [max] é adicionado da seguinte forma em [client-validation.js]
// -------------- max a utilizar em conjunto com [int] ou [number]
logs.max = true;
$.validator.addMethod("max", function(value, element, param) {
// registos
if (logs.max) {
console.log(jSON.stringify({
"[max] value" : value,
"[max] param" : param
}));
}
// validade
var val = Globalize.parseFloat(value);
if (isNaN(val)) {
// registos
if (logs.max) {
console.log(jSON.stringify({
"[max] valide" : true
}));
}
// resultado
return true;
}
var max = Globalize.parseFloat(param.value);
var valide = val <= max;
// registos
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 abordar 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] é integrado no segundo parâmetro da função [$.validator.unobtrusive.adapters.add];
- na linha 3, o objeto [param] deixará de estar vazio, passando a conter {"value":100};
Para compreender o código das linhas 3 a 33, é necessário saber que, quando existem vários validadores num 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 deste último que é associada ao elemento inválido;
Analisemos o código:
- linha 12: verifica-se se existe um número. Se o validador [int] tiver sido executado antes do validador [max], isto é necessariamente verdade, uma vez que um valor inválido interrompe a execução dos validadores;
- linhas 13-22: se não tivermos 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 o seu trabalho e declare o elemento inválido com a sua própria mensagem de erro;
- linhas 23-24: calcula a validade de [value];
Eis alguns registos:
Valor introduzido | registos |
| |
| |
|
6.3.14. Validador [min]
![]() |
A linha [1] é gerada pela seguinte sequência da vista [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>
Estas linhas dizem respeito 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 a utilizar em conjunto com [int] ou [number]
$.validator.addMethod("min", function(value, element, param) {
// registos
if (logs.min) {
console.log(jSON.stringify({
"[min] value" : value,
"[min] param" : param
}));
}
// validade
var val = Globalize.parseFloat(value);
if (isNaN(val)) {
// registos
if (logs.min) {
console.log(jSON.stringify({
"[min] valide" : true
}));
}
// resultado
return true;
}
var min = Globalize.parseFloat(param.value);
var valide = val >= min;
// registos
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("''", "'");
});
Eis alguns registos de execução:
Valor introduzido | registos |
| |
| |
|
6.3.15. Validador [regex]
![]() |
A linha [1] é gerada pela seguinte sequência da vista [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>
Estas linhas dizem respeito 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" />
Esta baliza introduz o validador [regex] [data-val-regex="La chaîne doit avoir entre 4 et 6 caractères"] com o seu parâmetro [pattern] [data-val-regex-pattern="^.{4,6}$"]. O parâmetro [pattern] é a expressão regular que deve verificar o valor a validar. Neste caso, a expressão regular verifica se a cadeia tem entre 4 e 6 caracteres quaisquer. O validador [regex] está predefinido na biblioteca de validação MS. Por conseguinte, não há nada a acrescentar no ficheiro [client-validation.js].
6.3.16. Validador [email]
![]() |
A linha [1] é gerada pela seguinte sequência da vista [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>
Estas linhas dizem respeito 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" />
Esta baliza introduz o validador [email] [data-val-email="Adresse mail invalide"]. O validador [email] está predefinido na biblioteca de validação MS. Por conseguinte, não há nada a acrescentar no ficheiro [client-validation.js].
6.3.17. Validador [range]
![]() |
A linha [1] é gerada pela seguinte sequência da vista [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>
Estas linhas dizem respeito 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" />
Esta baliza introduz um novo validador [range] [data-val-range="La valeur doit être dans l''intervalle [10,14]"] que tem dois parâmetros: [min] [data-val-range-min="10"] e [max] [data-val-range-max="14"].
No ficheiro [client-validation.js], definimos o validador [range] da seguinte forma:
// -------------- intervalo a utilizar em conjunto com [int] ou [number]
logs.range=true
$.validator.addMethod("range", function(value, element, param) {
// registos
if (logs.range) {
console.log(jSON.stringify({
"[range] value" : value,
"[range] param" : param
}));
}
// validade
var val = Globalize.parseFloat(value);
if (isNaN(val)) {
// registos
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;
// registos
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("''", "'");
});
É muito semelhante aos validadores [min] e [max] já analisados.
Eis alguns exemplos de registos:
Valor introduzido | registos |
| |
| |
|
6.3.18. Validador [number]
![]() |
A linha [1] é gerada pela seguinte sequência da vista [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>
Estas linhas dizem respeito 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 baliza introduz um novo validador [number] com o atributo [data-val-number="Format invalide"]. Este validador está definido da seguinte forma no ficheiro [client-validation.js]:
// -------------- número
logs.number = true;
$.validator.addMethod("number", function(value, element, param) {
var valide = !isNaN(Globalize.parseFloat(value));
// registos
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("''", "'");
});
Eis alguns exemplos de registos:
Valor introduzido | registos |
Sabe-se que os números reais são sensíveis à cultura. Acima, estamos na cultura [fr-FR]. Quando se introduz [2.5] (notação anglo-saxónica), o número é aceite. A culpa é do [Globalize.parseFloat], que aceita ambas as notações:
Passemos para o inglês e introduzamos os valores [+2,5] e [+2.5]. Os registos são os seguintes:
Valor introduzido | registos |
Existe um problema com o [2,5]. Foi declarado como um valor real válido, quando, na verdade, deveria ser escrito [2.5]. A culpa é do [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 incluir um ponto decimal e vírgulas, que são por vezes utilizadas para separar os milhares.
É possível melhorar a situação da seguinte forma:
// -------------- número
logs.number = true;
$.validator.addMethod("number", function(value, element, param) {
// só se gerem 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));
}
// registos
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 referidas;
- linhas 9-16: a verificação da validade do valor introduzido;
- linha 15: previu-se o caso em que a cultura não fosse nem [fr-FR], nem [en-US];
Os registos apresentam agora o seguinte:
Cultura [fr-FR]
Valor introduzido | registos |
| |
| |
| |
|
Cultura [en-US]
Valor introduzido | registos |
| |
| |
|
6.3.19. Validador [custom3]
![]() |
A linha [1] é gerada pela seguinte sequência da vista [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>
Estas linhas dizem respeito ao campo [double3] do formulário [Form01]:
@NotNull
private Double double3;
Pretendemos aqui analisar um validador que já não valida um valor introduzido, mas sim uma relação entre dois valores introduzidos. Neste caso, pretendemos que [double1+double3] se encontre no 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" />
Esta linha introduz o novo validador [custom3] declarado pelo atributo [data-val-custom3="[double3+double1] must be in [10,13]"]. Este validador tem os seguintes parâmetros:
- [field], declarado pelo atributo [data-val-custom3-field="double1"]. Este parâmetro designa o campo cujo valor participa no cálculo da validade de [double3];
- [min] declarado pelo atributo [data-val-custom3-min="10.0"]. Este parâmetro é o valor mínimo do intervalo [min, max], no qual [double1+double3] deve estar incluído;
- [max] declarado pelo atributo [data-val-custom3-max="13.0"]. Este parâmetro é o valor máximo do intervalo [min, max], no qual [double1+double3] deve estar incluído;
Este validador é gerido da seguinte forma em [client-validation.js]:
// -------------- custom3 utilizado em conjunto com [number]
logs.custom3 = true;
$.validator.addMethod("custom3", function(value1, element, param) {
// segundo valor
var value2 = $("#" + param.field).val();
// registos
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 efetuar 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;
// registos
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("''", "'");
});
Eis alguns exemplos de registos:
Valores introduzidos [double1,double3] | registos |
| |
| |
| |
|
6.3.20. Validador [url]
![]() |
A linha [1] é gerada pela seguinte sequência da vista [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>
Estas linhas dizem respeito 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" />
Esta linha introduz o validador [url] com o atributo [data-val-url]. Este validador está predefinido na biblioteca de validação jQuery. Não há nada a acrescentar 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 é visível, uma vez que os valores enviados só chegam ao servidor se tiverem sido declarados válidos do lado do cliente. Para ver a validação do lado do servidor em funcionamento, é necessário desativar a validação do lado do cliente. A vista [vue-01.xml] disponibiliza dois links para gerir esta 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>
Estes dois links não estão visíveis ao mesmo tempo:
![]() | ![]() |
A tradução HTML destes 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 ficheiro [local.js] (ver acima). Na função [$(document).ready] deste ficheiro, são utilizados os links de validação:
// documento pronto
$(document).ready(function() {
// referências globais
...
activateValidationTrue = $("#clientValidationTrue");
activateValidationFalse = $("#clientValidationFalse");
clientValidation = $("#clientValidation");
...
// ligações 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 sob a forma de um valor booleano [true : validation client activée, false : validation client désactivée]. Este campo encontra-se na vista [vue-01.xml] com o seguinte formato:
<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) {
// ligações
if (activate) {
// a validação do cliente está ativa
activateValidationTrue.hide();
activateValidationFalse.show();
// estão a ser analisados os validadores do formulário
$.validator.unobtrusive.parse(formulaire);
} else {
// a validação do cliente está desativada
activateValidationFalse.hide();
activateValidationTrue.show();
// estão a ser desativados 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 do lado do cliente; caso contrário, é «false»;
- linhas 5-6: o link de desativação é apresentado, 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 à procura de validadores [data-val-X]. O parâmetro da função [$.validator.unobtrusive.parse] é o identificador jS do formulário a analisar;
- linhas 11-12: o link de ativação é exibido, 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 esta função [setClientValidation2]? Serve para gerir os POST. Como o campo [clientValidation] é um campo oculto, é enviado e regressa com o formulário devolvido pelo servidor. Utiliza-se então o seu valor para restabelecer a validação do lado do cliente tal como estava antes do POST. Com efeito, não há memória jS entre as solicitações. Por isso, é necessário que o servidor transmita, na nova vista, as informações que permitem inicializar o jS dessa vista. Isto é normalmente feito na função [$(document).ready].
Voltemos à função [setClientValidation], que gere 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 utilizador é guardada 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
// limpa-se todas as mensagens de erro do cliente
clearClientErrors();
}
}
- linha 4: utiliza-se a função [setClientValidation2] que acabámos de ver;
- linha 6: guardamos a escolha do utilizador no campo oculto para a recuperar quando a próxima função POST for executada;
- linha 11: se a validação do cliente estiver ativa, apagam-se as mensagens de erro da coluna [serveur] da vista. Descrevemos a função [clearServerErrors] no parágrafo 6.3.7;
- linha 13: os validadores jS são executados para apresentar eventuais mensagens de erro na coluna [client] da vista;
- linha 17: se a validação do cliente estiver desativada, então apagam-se as mensagens de erro da coluna [client] da vista. Vamos analisar, na consola 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 tem 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"];
Isto aplica-se a todos os elementos com erros. Utilizamos estas duas informações na seguinte função [clearClientErrors]:
// limpar erros do cliente
function clearClientErrors() {
// limpar as mensagens de erro do cliente
$(".field-validation-error").each(function(index) {
$(this).text("");
});
// alterar a classe CSS das entradas erradas
$(".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 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. Assim, o elemento com erro que tinha sido destacado a vermelho recupera o seu estilo original;


















































