6. Walidacja JavaScript po stronie klienta
W poprzednim rozdziale zajęliśmy się walidacją po stronie serwera. Wróćmy do architektury aplikacji Spring MVC:
![]() |
BD
Jak dotąd strony wysyłane do klienta nie zawierały kodu JavaScript. Teraz zajmiemy się tą technologią, która w pierwszej kolejności pozwoli nam przeprowadzać walidację po stronie klienta. Zasada działania jest następująca:
- to JavaScript wysyła wartości do serwera WWW;
- a zatem przed wywołaniem POST może on sprawdzić poprawność danych i zablokować wywołanie POST, jeśli dane są nieprawidłowe;
Wykorzystamy formularz, który zweryfikowaliśmy po stronie serwera. Teraz zapewnimy możliwość jego weryfikacji zarówno po stronie klienta, jak i po stronie serwera.
Uwaga: temat jest złożony. Czytelnik niezainteresowany tym zagadnieniem może przejść bezpośrednio do akapitu 7.
6.1. Funkcjonalności projektu
Przedstawiamy kilka widoków projektu, aby zaprezentować jego funkcje. Strona początkowa jest generowana za pomocą URL [http://localhost:8080/js01.html]
![]() |
Walidacje zostały wdrożone po obu stronach: po stronie klienta i serwera. Ponieważ POST ma miejsce tylko wtedy, gdy wartości zostały uznane za prawidłowe po stronie klienta, walidacje po stronie serwera zawsze kończą się powodzeniem. Dlatego udostępniono link umożliwiający wyłączenie walidacji po stronie klienta. W tym trybie działanie przebiega tak, jak to już omówiliśmy. Oto przykład:
123 ![]() |
- w [1] – wprowadzone wartości;
- w [2] – komunikaty o błędach związane z wprowadzonymi danymi;
- w [3] – podsumowanie błędów, a dla każdego z nich:
- nazwę sprawdzanego pola,
- kod błędu,
- domyślny komunikat dla tego kodu błędu;
Teraz włączmy walidację po stronie klienta:
![]() |
- w [1] – wprowadzone wartości. Można zauważyć, że błędne wpisy mają szczególny styl;
- w [2] – komunikaty o błędach związane z błędnymi danymi. Są one identyczne z tymi generowanymi przez serwer;
- w pliku [3-4] nie ma już nic, ponieważ dopóki występują błędne wpisy, wysyłanie pliku POST do serwera nie ma miejsca;
6.2. Weryfikacja po stronie serwera
6.2.1. Konfiguracja
Zaczynamy od utworzenia nowego projektu Maven o nazwie [springmvc-validation-client]:
![]() |
Rozbudowujemy projekt w następujący sposób:
![]() |
Klasa [Config] konfiguruje projekt. Jest identyczna jak w poprzednich projektach:
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;
}
}
Klasa [Main] jest klasą wykonalną projektu:
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) {
// uruchamiamy aplikację
ApplicationContext context = SpringApplication.run(Config.class, args);
// wyświetlanie listy beanów znalezionych przez Spring
System.out.println("Liste des beans Spring");
String[] beanNames = context.getBeanDefinitionNames();
Arrays.sort(beanNames);
for (String beanName : beanNames) {
System.out.println(beanName);
}
}
}
- w wierszu 13 uruchamiany jest Spring Boot z plikiem konfiguracyjnym [Config];
- wiersze 15–20: w tym przykładzie pokazujemy, jak wyświetlić listę obiektów zarządzanych przez Spring. Może to być przydatne, jeśli czasami wydaje się, że Spring nie obsługuje jednego z naszych komponentów. Jest to sposób na sprawdzenie tego. Jest to również sposób na sprawdzenie autokonfiguracji przeprowadzonej przez Spring Boot. W konsoli otrzymujemy listę podobną do poniższej:
Podkreśliliśmy obiekty zdefiniowane w klasie [Config].
6.2.2. Szablon formularza
Kontynuujmy przeglądanie projektu:
![]() |
Klasa [Form01] jest klasą, która będzie odbierać przesłane wartości. Wygląda ona następująco:
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 {
// wartości przesłane
@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;
// walidacja po stronie klienta
private boolean clientValidation = true;
// lokalizacja
private String lang;
...
}
Znajdujemy tu już znane nam walidatory. Ponadto wprowadzimy pojęcie walidacji specyficznej. Jest to walidacja, której nie da się sformalizować za pomocą predefiniowanego walidatora. W tym przypadku wymagamy, aby wartość [double1+double2] mieściła się w przedziale [10,13].
6.2.3. Kontroler
Kontroler [JsController] wygląda następująco:
![]() |
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";
}
...
// przygotowanie szablonu widoku vue-01
private void setModel(Form01 formulaire, Model model, Locale locale, String message) {
...
}
}
- w wierszu 9 akcja [/js01];
- wiersz 10: obiekt typu [Form01] jest instancjonowany i automatycznie umieszczany w szablonie, powiązany z kluczem [form01];
- wiersz 10: ustawienia regionalne i szablon są wprowadzane do parametrów;
- wiersz 11: na podstawie tych informacji przygotowuje się szablon;
- wiersz 12: wyświetlany jest widok [vue-01.xml];
Metoda [setModel] wygląda następująco:
// przygotowanie modelu widoku vue-01
private void setModel(Form01 formulaire, Model model, Locale locale, String message) {
// obsługiwane są wyłącznie ustawienia regionalne fr-FR, en-US
String language = locale.getLanguage();
String country = null;
if (language.equals("fr")) {
country = "FR";
formulaire.setLang("fr_FR");
}
if (language.equals("en")) {
country = "US";
formulaire.setLang("en_US");
}
model.addAttribute("locale", String.format("%s-%s", language, country));
// ewentualny komunikat
if (message != null) {
model.addAttribute("message", message);
}
}
- celem metody [setModel] jest umieszczenie w szablonie:
- informacje o ustawieniach regionalnych,
- komunikat przekazany jako ostatni parametr;
- wiersz 14: do szablonu umieszcza się informacje o ustawieniach regionalnych (język, kraj);
- wiersze 16–18: do ustawień regionalnych umieszcza się ewentualny komunikat przekazany jako parametr;
- wiersze 8, 12: informacje o ustawieniach regionalnych są również zapisywane w formularzu [Form01]. Skrypt JavaScript wykorzysta te informacje;
Wartości wprowadzone w formularzu [vue-01.xml] zostaną przesłane do następującej akcji [/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);
...
}
- wiersz 2: adnotacja [@Valid Form01 formulaire] powoduje, że przesłane wartości zostaną poddane walidacji przez walidatory klasy [Form01]. Wiemy, że w przedziale [10,13] istnieje specyficzna walidacja o nazwie [double1+double2]. Po przejściu do wiersza 3 ta walidacja nie została jeszcze przeprowadzona;
- wiersz 3: tworzony jest następujący obiekt [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 {
// przedział walidacji
private double min;
private double max;
// konstruktor
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) {
// obiekt zweryfikowany
Form01 form01 = (Form01) form;
// wartość [double1]
Double double1 = form01.getDouble1();
if (double1 == null) {
return;
}
// wartość [double2]
Double double2 = form01.getDouble2();
if (double2 == null) {
return;
}
// [double1+double2]
double somme = double1 + double2;
// weryfikacja
if (somme < min || somme > max) {
errors.rejectValue("double2", "form01.double2", new Double[] { min, max }, null);
}
}
}
- wiersz 8: aby zaimplementować specyficzną walidację, tworzymy klasę implementującą interfejs Spring [Validator]. Interfejs ten posiada dwie metody: [supports] w wierszu 21 oraz [validate] w wierszu 26;
- wiersze 21–23: metoda [supports] przyjmuje obiekt typu [Class]. Musi zwrócić true, aby wskazać, że obsługuje tę klasę, a w przeciwnym razie – false;
- wiersz 22: stwierdzamy, że klasa [Form01Validator] akceptuje wyłącznie obiekty typu [Form01];
- wiersze 15–18: przypomnijmy, że chcemy zaimplementować ograniczenie [double1+double2] w przedziale [10,13]. zamiast ograniczać się do tego przedziału, sprawdzimy ograniczenie [double1+double2] w przedziale [min, max]. Dlatego mamy konstruktor z tymi dwoma parametrami;
- wiersz 26: metoda [validate] jest wywoływana z instancją zweryfikowanego obiektu, a więc w tym przypadku z instancją [Form01] oraz z kolekcją aktualnie znanych błędów [Errors errors]. Jeśli walidacja przeprowadzona przez metodę [validate] zakończy się niepowodzeniem, musi ona utworzyć nowy element w kolekcji [Errors errors];
- wiersz 43: walidacja zakończyła się niepowodzeniem. Dodajemy element do kolekcji [Errors errors] za pomocą metody [Errors.rejectValue], której parametry są następujące:
- parametr 1: zazwyczaj nazwa błędnego pola. W tym przypadku przetestowano pola [double1, double2]. Można podać jedno z nich,
- powiązanego komunikatu o błędzie, a dokładniej jego klucza w zewnętrznych plikach komunikatów:
[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}
Mamy tu komunikaty z parametrami {0} i {1}. Należy zatem podać dwie wartości dla tego komunikatu. Tak właśnie działa trzeci parametr metody [Errors.rejectValue].
- czwarty parametr to domyślny komunikat o błędzie;
Wróćmy do akcji [/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";
}
}
- wiersz 4: walidator [Form01Validator] jest uruchamiany z następującymi parametrami:
- parametr 1: obiekt poddawany walidacji,
- parametr 2: lista błędów tego obiektu. Jest to obiekt [BindingResult result] przekazany jako parametry akcji. Jeśli walidacja zakończy się niepowodzeniem, obiekt ten będzie miał o jeden błąd więcej;
- wiersz 5: sprawdzamy, czy występują błędy walidacji;
- wiersze 7–10: przeglądamy listę błędów, aby zapisać dla każdego z nich:
- nazwę sprawdzanego obiektu,
- jego kod błędu,
- domyślny komunikat o błędzie;
- wiersz 10: na podstawie tych informacji tworzony jest szablon widoku [vue-01.xml]. Tym razem zawiera on komunikat, będący połączoną i skróconą wersją poszczególnych komunikatów o błędach;
- wiersze 12–15: jeśli wszystkie przesłane wartości są prawidłowe, przekierowujemy klienta do akcji [/js01], umieszczając przesłane wartości w atrybucie Flash;
6.2.4. Widok
Widok [vue-01.xml] jest złożony. Przedstawimy tylko jego niewielką część:
<!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>
<!-- tytuł -->
<h3>
<span th:text="#{form01.title}"></span>
<span th:text="${locale}"></span>
</h3>
<!-- menu -->
<p>
...
</p>
<!-- formularz -->
<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>
<!-- wymagane -->
<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>
<!-- przycisk zatwierdzenia -->
<input type="submit" th:value="#{form01.valider}" value="Valider" onclick="javascript:postForm01()" />
</p>
</form>
<!-- komunikat z walidatorów po stronie serwera -->
<br/>
<fieldset class="fieldset">
<legend>
<span th:text="#{server.error.message}"></span>
</legend>
<span th:text="${message}" class="error"></span>
</fieldset>
</body>
</html>
Ta strona wykorzystuje szereg komunikatów znajdujących się w zewnętrznych plikach komunikatów:
[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
Wróćmy do kodu strony:
- wiersz 8: wiele importów bibliotek JavaScript, które w tym przypadku możemy pominąć;
- wiersz 14: wyświetla ustawienia regionalne umieszczone w szablonie przez serwer;
- wiersz 59: wyświetla komunikat umieszczony w szablonie przez serwer;
Kod w wierszach 33–44 jest nowy. Przyjrzyjmy się mu:
<!-- wymagane -->
<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>
Najprościej będzie chyba przyjrzeć się kodowi HTML wygenerowanemu przez ten fragment kodu Thymeleaf:
<!-- wymagane -->
<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>
Po stronie klienta będziemy korzystać z biblioteki walidacyjnej o nazwie [jquery.validate]. Wszystkie atrybuty [data-x] są przeznaczone dla tej biblioteki. Gdy walidacja po stronie klienta zostanie wyłączona, atrybuty te nie będą wykorzystywane. Na razie nie ma więc potrzeby ich rozumienia. Możemy po prostu skupić się na następującym wierszu Thymeleaf:
<input type="text" th:field="*{strNotEmpty}" data-val="true" th:attr="data-val-required=#{NotNull}" />
która generuje następujący wiersz HTML:
<input type="text" data-val="true" data-val-required="Le champ est obligatoire" id="strNotEmpty" name="strNotEmpty" value="" />
W powyższym przykładzie pojawia się trudność z wygenerowaniem atrybutu [data-val-required="Le champ est obligatoire"]. Wynika to z faktu, że wartość powiązana z tym atrybutem pochodzi z zewnętrznych plików komunikatów. Aby ją uzyskać, konieczne jest zatem użycie wyrażenia Thymeleaf. Jest to następujące wyrażenie: [th:attr="data-val-required=#{NotNull}"]. Wyrażenie to jest obliczane, a jego wartość umieszczana w niezmienionej postaci w wygenerowanym tagu HTML. Nazywa się ona [th:attr], ponieważ służy do generowania atrybutów, które nie są predefiniowane w Thymeleaf. Spotkaliśmy się z predefiniowanymi atrybutami [th:text, th:value, th:class, ...], ale nie istnieje atrybut [th:data-val-required].
6.2.5. Arkusz stylów
Powyżej występują klasy CSS, takie jak [class="field-validation-valid"]. Niektóre z tych klas są wykorzystywane przez bibliotekę JavaScript do walidacji. Są one zdefiniowane w następującym pliku [form01.css]:
![]() |
@CHARSET "UTF-8";
/*style niestandardowe*/
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;
}
/* Style dla pomocników walidacji
-----------------------------------------------------------*/
.field-validation-error {
color: #f00;
}
.field-validation-valid {
display: none;
}
.input-validation-error {
border: 1px solid #f00;
background-color: #opłata;
}
.validation-summary-errors {
font-weight: bold;
color: #f00;
}
.validation-summary-valid {
display: none;
}
6.3. Walidacja po stronie klienta
6.3.1. Podstawy jQuery i JavaScript
Walidacja po stronie klienta odbywa się za pomocą JavaScriptu. Skorzystamy z frameworka jQuery, który oferuje wiele funkcji ułatwiających tworzenie kodu JavaScript. Przedstawiamy podstawy jQuery, które należy znać, aby zrozumieć skrypty z tego i kolejnych rozdziałów.
Tworzymy plik statyczny HTML [JQuery-01.html], który umieszczamy w folderze [static / vues]:
![]() |
Plik ten będzie miał następującą zawartość:
<!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>
- wiersz 6: import pliku jQuery;
- wiersze 10–12: element strony o identyfikatorze [element1]. Będziemy pracować z tym elementem.
Musimy pobrać plik [jquery-1.11.1.min.js]. Znajdziemy go w najnowszej wersji jQuery, a także w wersjach URL i [http://jquery.com/download/]:

Pobrany plik umieścimy w folderze [static / js]:
![]() |
Po wykonaniu tej czynności wyświetlimy widok statyczny [jQuery-01.html] w przeglądarce Chrome [1-2]:
![]() |
W przeglądarce Google Chrome należy wpisać [Ctrl-Maj-I], aby wyświetlić narzędzia programistyczne [3]. Zakładka [Console] [4] umożliwia uruchamianie kodu JavaScript. Poniżej podajemy polecenia JavaScript, które należy wpisać, wraz z ich objaśnieniami.
JS | wynik |
|
: zwraca zbiór wszystkich elementów o identyfikatorze [element1], a więc zazwyczaj zbiór zawierający 0 lub 1 element, ponieważ na jednej stronie nie mogą występować dwa identyczne identyfikatory HTML. | ![]() |
|
: przypisuje tekst [blabla] do wszystkich elementów w zbiorze. Powoduje to zmianę treści wyświetlanej na stronie | ![]() |
|
ukrywa elementy kolekcji. Tekst [blabla] nie jest już wyświetlany. | ![]() |
|
: ponownie wyświetla kolekcję. Dzięki temu widzimy, że element o identyfikatorze [element1] posiada atrybut CSS style='display: none;', który powoduje, że element jest ukryty. | |
|
: wyświetla elementy kolekcji. Ponownie pojawia się tekst [blabla]. Za to wyświetlanie odpowiada atrybut CSS style='display: block;'. | ![]() |
|
: nadaje atrybut wszystkim elementom kolekcji. Atrybutem jest tutaj [style], a jego wartością [color: red]. Tekst [blabla] zmienia kolor na czerwony. | ![]() |
![]() | |
![]() |
Warto zauważyć, że identyfikator URL przeglądarki nie uległ zmianie podczas wszystkich tych operacji. Nie doszło do żadnej komunikacji z serwerem WWW. Wszystko odbywa się wewnątrz przeglądarki. Teraz przyjrzyjmy się kodowi źródłowemu strony:
<!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>
To jest tekst początkowy. Nie odzwierciedla on w żaden sposób zmian, które wprowadziliśmy w elemencie w wierszach 10–12. Należy o tym pamiętać podczas debugowania kodu JavaScript. Często nie ma więc sensu przeglądać kodu źródłowego wyświetlanej strony.
Wiemy już wystarczająco dużo, aby zrozumieć skrypty jS, które pojawią się poniżej.
6.3.2. Biblioteki walidacyjne jS
Będziemy korzystać z bibliotek z ekosystemu jQuery. Wokół jQuery skupia się szereg projektów, które z kolei dają początek kolejnym bibliotekom. Będziemy korzystać z biblioteki walidacyjnej [jquery.validate.unobstrusive] stworzonej przez firmę Microsoft i przekazanej fundacji jQuery. W dalszej części będziemy ją nazywać biblioteką walidacyjną MS lub po prostu biblioteką MS. Aby ją uzyskać, potrzebne jest środowisko Microsoft Visual Studio. Nie znalazłem innego sposobu na jej zdobycie. Można skorzystać z bezpłatnej wersji typu [Visual Studio Community] lub [http://www.visualstudio.com/en-us/news/vs2013-community-vs.aspx] (grudzień 2014 r.). Czytelnik, który nie jest zainteresowany wykonaniem poniższych czynności, może pobrać tę bibliotekę oraz biblioteki, na których się ona opiera, z przykładów zamieszczonych na stronie internetowej niniejszego dokumentu.
Tworzymy projekt konsolowy w programie Visual Studio [1-4]:
|
![]() |
- w [5], projekt konsoli;
- w [6-7]: dodamy pakiety [NuGet] do projektu. [NuGet] to funkcja programu Visual Studio umożliwiająca pobieranie bibliotek w postaci DLL, a także bibliotek jS.
![]() |
- w [9-10], wyszukaj hasło [jQuery];
- w [11-13], pobierzcie w podanej kolejności biblioteki jS niezbędne do walidacji po stronie klienta;
- w przypadku [14] pobierz również bibliotekę [Microsoft jQuery Unobtrusive Ajax], której będziemy używać wkrótce;
![]() |
- w [15-16] wyszukaj pakiety za pomocą słowa kluczowego [globalize];
- w przypadku [17], pobierz bibliotekę [jQuery.Validation.Globalize];
![]() |
Te różne pliki do pobrania zainstalowały pewną liczbę bibliotek jS w folderze [Scripts] projektu [18]. Nie wszystkie z nich są przydatne. Każdy plik występuje w dwóch egzemplarzach:
- [js]: wersja biblioteki nadająca się do odczytu;
- [min.js]: wersja biblioteki w postaci „zminimalizowanej” (minified), która nie jest czytelna. Nie jest ona jednak całkowicie nieczytelna. To po prostu tekst. Nie jest jednak zrozumiała. Jest to wersja przeznaczona do użytku w środowisku produkcyjnym, ponieważ plik ten jest mniejszy niż odpowiadająca mu wersja [js], a tym samym poprawia szybkość komunikacji między klientem a serwerem;
Wersje [min.map] nie są niezbędne. W folderze [cultures] można zachować tylko te ustawienia regionalne, które są obsługiwane przez aplikację.
Za pomocą Eksploratora Windows należy skopiować te pliki do folderu [static / js / jquery] w projekcie [springmvc-validation-client] i zachować wyłącznie przydatne pliki [20]:
![]() |
W pliku [21] zachowujemy tylko dwie lokalizacje:
- [fr-FR]: francuski (Francja);
- [en-US]: angielski z pliku USA;
6.3.3. Import bibliotek walidacyjnych jS
Aby można było z nich korzystać, biblioteki te należy zaimportować za pomocą widoku [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>
- wiersz 11: import pliku jS, o którym jeszcze nie wspominaliśmy;
- wiersze 13–18: skrypt jS interpretowany przez Thymelaf. Zarządza on ustawieniami regionalnymi po stronie klienta;
6.3.4. Obsługa ustawień regionalnych po stronie klienta
Lokalizacja po stronie klienta odbywa się za pomocą następującego skryptu jS:
<script th:inline="javascript">
/*<![CDATA[*/
var culture = [[${locale}]];
Globalize.culture(culture);
/*]]>*/
</script>
- wiersze 3–4: kod jS, w którym znajduje się wyrażenie Thymeleaf [[${locale}]]. Należy zwrócić uwagę na specyficzną składnię tego wyrażenia. Wynika to z faktu, że jest ono napisane w języku JavaScript. Wyrażenie [[${locale}]] zostanie zastąpione wartością klucza [locale] z modelu widoku;
Wynik w strumieniu HTML wygenerowanym na podstawie tych wierszy jest następujący:
<script>
/*<![CDATA[*/
var culture = 'en-US';
Globalize.culture(culture);
/*]]>*/
</script>
Wiersze 3–4 określają ustawienia regionalne po stronie klienta. Obsługujemy tylko dwa z nich: [fr-FR] i [en-US]. Dlatego zaimportowaliśmy tylko dwa pliki ustawień regionalnych:
<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>
Ustawienia regionalne stosowane po stronie klienta są określane po stronie serwera. Wróćmy do kodu po stronie serwera:
@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";
}
// przygotowanie szablonu widoku vue-01
private void setModel(Form01 formulaire, Model model, Locale locale, String message) {
// obsługujemy wyłącznie ustawienia regionalne fr-FR i 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));
...
}
- wiersz 20: ustawienie regionalne [fr-FR] lub [en-US] jest umieszczane w szablonie widoku [vue-01.xml] (wiersz 4). Warto zwrócić uwagę na źródło komplikacji. Podczas gdy lokalizacja francuska jest oznaczona jako [fr-FR] po stronie klienta, po stronie serwera jest oznaczona jako [fr_FR]. Z tego powodu, w wierszach 14 i 18, jest ona przechowywana w tej postaci w obiekcie [Form01 formulaire], który odbiera przesłane wartości;
Należy zwrócić uwagę na następującą istotną kwestię. Skrypt
<script>
/*<![CDATA[*/
var culture = 'en-US';
Globalize.culture(culture);
/*]]>*/
</script>
zmienia ustawienia regionalne klienta na podstawie ustawień przekazanych przez serwer. Nie powoduje to internacjonalizacji komunikatów wyświetlanych na stronie. Zmienia jedynie sposób interpretacji niektórych informacji, które zależą od ustawień regionalnych danego kraju. W przypadku ustawienia regionalnego [fr_FR] liczba rzeczywista [12,78] jest prawidłowa, podczas gdy w ustawieniu regionalnym [en-US] jest nieprawidłowa. Należy zatem wpisać [12.78]. Podobnie data [12/01/2014] jest prawidłowa w kulturze [fr-FR], podczas gdy w kulturze [en-US] należy wpisać [01/12/2014]. Pliki z folderu [jquery / globalize] obsługują tego typu problemy:
![]() |
Internacjonalizacja komunikatów o błędach jest obsługiwana wyłącznie po stronie serwera. Zobaczymy, że strona HTML / jS zawiera komunikaty o błędach odpowiadające ustawieniom regionalnym obsługiwanym przez serwer: w języku francuskim dla ustawień regionalnych [fr_FR] oraz w języku angielskim dla ustawień regionalnych [en_US].
6.3.5. Pliki komunikatów
Widok [vue-01.xml] wykorzystuje następujące komunikaty zinternacjonalizowane:
![]() |
[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
Plik [messages.properties] jest kopią pliku zawierającego komunikaty w języku angielskim. W rezultacie każda lokalizacja inna niż [fr] będzie korzystać z komunikatów w języku angielskim. Przypominamy, że plik [messages_fr.properties] jest używany dla wszystkich ustawień regionalnych [fr_XX], takich jak [fr_CA] lub [fr_FR].
Widok [vue-01.xml] wykorzystuje klucze tych komunikatów. Jeśli czytelnik chce poznać wartości powiązane z tymi kluczami, prosimy o powrót do tego akapitu, aby je sprawdzić.
6.3.6. Zmiana ustawień regionalnych
Widok [vue-01.xml] zawiera cztery linki:
<body>
<!-- tytuł -->
<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>
<!-- formularz -->
<form action="/someURL" th:action="@{/js02.html}" method="post" th:object="${form01}" name="form" id="form">
...
z których niektóre przedstawiono poniżej [1]:
![]() |
Przyjrzyjmy się dwóm linkom, które umożliwiają zmianę ustawień regionalnych na język francuski lub angielski:
<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>
Kliknięcie tych linków powoduje uruchomienie skryptu jS znajdującego się w pliku [local.js] [2]. W obu przypadkach wywoływana jest funkcja jS [setLocale]:
// ustawienia regionalne
function setLocale(locale) {
// aktualizujemy ustawienia regionalne
lang.val(locale);
// przesyłamy formularz – nie uruchamia to walidatorów po stronie klienta – dlatego nie wyłączyliśmy walidacji po stronie klienta
document.form.submit();
}
Aby zrozumieć wiersz 4, konieczne jest wprowadzenie. Widok [vue-01.xml] zawiera ukryte pole o nazwie [lang]:
<input type="hidden" th:field="*{lang}" th:value="*{lang}" value="true" />
które odpowiada polu [lang] w widoku [Form01]:
// ustawienia regionalne
private String lang;
Ukryte pola są przydatne, gdy chcemy wzbogacić wartości przesyłane do serwera. Dzięki JavaScriptowi można nadać im wartość, która jest następnie przesyłana tak samo, jak zwykłe dane wprowadzone przez użytkownika. Kod HTML wygenerowany przez Thymeleaf wygląda następująco:
<input type="hidden" value="en_US" id="lang" name="lang" />
Wartość parametru [value] odpowiada wartości pola [Form01.lang] w momencie generowania HTML. Warto zwrócić uwagę na identyfikator jS węzła [id="lang"]. Identyfikator ten jest wykorzystywany przez następującą funkcję []:
// zmienne globalne
var lang;
// dokument gotowy
$(document).ready(function() {
// odwołania globalne
lang = $("#lang");
});
// ustawienia regionalne
function setLocale(locale) {
// aktualizujemy ustawienia regionalne
lang.val(locale);
// przesyłamy formularz – z nieznanych przyczyn nie uruchamia to walidatorów po stronie klienta
// dlatego nie zablokowano walidacji
document.form.submit();
}
- wiersze 5–8: funkcja jS [$(document).ready(f)] jest wykonywana, gdy przeglądarka załaduje cały dokument przesłany przez serwer. Jej parametrem jest funkcja. Funkcja jS [$(document).ready(f)] służy do zainicjowania środowiska jS dla załadowanego dokumentu;
- wiersz 7: wyrażenie [$("#lang")] jest wyrażeniem jQuery. Jego wartością jest odwołanie do węzła DOM o atrybucie [id='lang'];
- wiersz 2: zmienne zadeklarowane poza funkcją mają zasięg globalny względem funkcji. W tym przypadku oznacza to, że zmienna [lang] zainicjowana w [$(document).ready()] jest również znana w funkcji [setLocale] z wiersza 11;
- wiersz 13: modyfikuje atrybut [value] węzła oznaczonego jako [lang]. Jeśli wartość lang wynosi [xx_XX], wówczas znacznik HTML węzła przyjmuje następującą postać:
<input type="hidden" value="xx_XX" id="lang" name="lang" />
Skrypt JavaScript umożliwia zmianę wartości elementów DOM (Document Object Model).
- Wiersz 16: [document] odnosi się do DOM. [document.form] odnosi się do pierwszego formularza znalezionego w tym dokumencie. Dokument HTML może zawierać wiele tagów <form>, a zatem wiele formularzy. W tym przypadku mamy tylko jeden. [document.form.submit] wysyła ten formularz tak, jakby użytkownik kliknął przycisk z atrybutem [type='submit']. Do jakiej akcji są wysyłane wartości formularza? Aby to sprawdzić, należy przyjrzeć się znacznikowi [form] formularza w pliku [vue-01.xml]:
<!-- formularz -->
<form action="/someURL" th:action="@{/js02.html}" method="post" th:object="${form01}" name="form" id="form">
Akcją, która otrzyma przesłane wartości, jest ta oznaczona atrybutem [th:action]. Będzie to zatem akcja [/js02.html]. Należy pamiętać, że z tej nazwy zostanie usunięty sufiks [.html], a ostatecznie zostanie wykonana akcja [/js02]. Ważne jest, aby zrozumieć, że nowa wartość [xx_XX] węzła [lang] zostanie przesłana w postaci [lang=xx_XX]. Skonfigurowaliśmy jednak naszą aplikację tak, aby przechwytywała parametr [lang] i interpretowała go jako zmianę ustawień regionalnych. Zatem po stronie serwera ustawienia regionalne zmienią się na [xx_XX]. Przyjrzyjmy się akcji [/js02], która zostanie wykonana:
@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";
}
}
// przygotowanie szablonu widoku vue-01
private void setModel(Form01 formulaire, Model model, Locale locale, String message) {
// obsługujemy wyłącznie ustawienia regionalne fr-FR i 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));
...
}
- wiersz 2: akcja [/js02] otrzyma nowe ustawienie regionalne [xx_XX] zawarte w parametrze [Locale locale]:
- wiersze 5–12: jeśli niektóre z przesłanych wartości są nieprawidłowe, zostanie wyświetlony widok [vue-01.xml] wraz z komunikatami o błędach wykorzystującymi nową lokalizację [xx_XX]. Ponadto wiersz 11 powoduje, że zmienna [locale=xx-XX] jest umieszczana w szablonie. Po stronie klienta wartość ta zostanie wykorzystana do aktualizacji ustawień regionalnych po stronie klienta. Opisaliśmy już ten proces;
- wiersze 14–15: jeśli wszystkie przesłane wartości są prawidłowe, następuje przekierowanie do kolejnej akcji [/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";
}
- w wierszu 2 wstawiane są nowe ustawienia regionalne [xx_XX];
- w wierszu 3: metoda [setModel] ustawi wówczas ustawienia regionalne klienta na [xx-XX];
Przyjrzyjmy się teraz wpływowi ustawień regionalnych w widoku [vue-01.xml]. Na razie nie przedstawiliśmy go w całości, ponieważ liczy on ponad 300 wierszy. Niemniej jednak większość wierszy składa się z powtarzającej się sekwencji podobnej do poniższej:
<!-- wymagane -->
<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>
Ten kod wyświetla następujący fragment [1]:
![]() |
Komunikat o błędzie [2] pochodzi z atrybutu [th:attr="data-val-required=#{NotNull}"] w wierszu 5. [#{NotNull}] jest komunikatem zlokalizowanym. W zależności od ustawień regionalnych po stronie serwera wiersz 5 generuje tag:
<input type="text" data-val="true" data-val-required="Field is required" id="strNotEmpty" name="strNotEmpty" />
lub tag:
<input type="text" data-val="true" data-val-required="Le champ est obligatoire" id="strNotEmpty" name="strNotEmpty" />
Atrybuty [data-x] są wykorzystywane przez bibliotekę walidacyjną jS.
Podsumowując, należy pamiętać, że oba linki służące do zmiany ustawień regionalnych:
- powodują POST wprowadzonych wartości;
- zmieniają ustawienia regionalne zarówno po stronie serwera, jak i po stronie klienta;
- generują stronę HTML, która zawiera komunikaty o błędach przeznaczone dla biblioteki walidacyjnej jS, a komunikaty te są w języku wybranych ustawień regionalnych;
6.3.7. POST wprowadzonych wartości
Przyjrzyjmy się przyciskowi [Valider], który wysyła wprowadzone wartości z widoku [vue-01.xml]. Jego kod HTML wygląda następująco:
<!-- przycisk zatwierdzenia -->
<input type="submit" value="Valider" onclick="javascript:postForm01()" />
Jeśli w przeglądarce włączona jest obsługa JavaScript, kliknięcie przycisku spowoduje uruchomienie metody [postForm01]. Jeśli ta funkcja zwróci wartość logiczną [False], to metoda submit nie zostanie wywołana. Jeśli zwróci inną wartość, to zostanie ona wywołana. Funkcja ta znajduje się w pliku [local.js]:
![]() |
Jest ona importowana przez widok [vue-01.xml] w wierszu 6 poniżej:
<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>
W tym pliku znajduje się następujący kod:
// zmienne globalne
var formulaire;
var clientValidation;
var double1;
var double2;
var double3;
...
$(document).ready(function() {
// odwołania globalne
formulaire = $("#form");
clientValidation = $("#clientValidation");
double1 = $("#double1");
double2 = $("#double2");
double3 = $("#double3");
...
});
....
// przesyłanie formularza
function postForm01() {
...
}
- wiersze 8–16: funkcja jS [$(document).ready(f)] jest wykonywana, gdy przeglądarka załaduje cały dokument przesłany przez serwer. Jej parametrem jest funkcja. Funkcja jS [$(document).ready(f)] służy do zainicjowania środowiska jS dla załadowanego dokumentu;
- wiersze 10–14: aby zrozumieć te wiersze, należy zapoznać się zarówno z kodem Thymeleaf, jak i z wygenerowanym kodem HTML;
Odpowiedni kod Thymeleaf wygląda następująco:
<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" />
który generuje następujący kod 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" />
Każdy atrybut [th:field='x'] generuje dwa atrybuty: HTML, [name='x'] oraz [id='x']. Atrybut [name] to nazwa wysyłanych wartości. Zatem obecność atrybutów [name='x'] i [value='y'] dla tagu HTML <input type='text'> spowoduje umieszczenie ciągu znaków x=y w wartościach wysyłanych name1=val1&name2=val2&... Atrybut [id='x'] jest natomiast wykorzystywany przez JavaScript. Służy on do identyfikacji elementu w modelu obiektowym dokumentu (DOM). Załadowany dokument HTML jest bowiem przekształcany w drzewo JavaScript o nazwie DOM, w którym każdy węzeł jest identyfikowany przez swój atrybut [id].
Wróćmy do kodu funkcji [$(document).ready()]:
// zmienne globalne
var formulaire;
var clientValidation;
var double1;
var double2;
var double3;
...
$(document).ready(function() {
// odwołania globalne
formulaire = $("#form");
clientValidation = $("#clientValidation");
double1 = $("#double1");
double2 = $("#double2");
double3 = $("#double3");
...
});
....
// przesyłanie danych z formularza
function postForm01() {
...
}
- wiersz 10: wyrażenie [$("#form")] jest wyrażeniem jQuery. Jego wartością jest odwołanie do węzła DOM o atrybucie [id='form '];
- wiersze 10–14: pobierane są odwołania do pięciu węzłów DOM;
- wiersze 2–6: zmienne zadeklarowane poza funkcją mają zasięg globalny względem funkcji. Oznacza to, że zmienne [formulaire, clientValidation , double1, double2, double3] zainicjowane w [$(document).ready()] będą dostępne również w funkcji [postForm01] w wierszu 19;
Przeanalizujmy teraz funkcję [postForm01]:
// przesyłanie danych z formularza
function postForm01() {
// tryb walidacji po stronie klienta
var validationActive = clientValidation.val() === "true";
if (validationActive) {
// kasowanie błędów serwera
clearServerErrors();
// walidacja formularza
if (!formulaire.validate().form()) {
// brak wysłania
return false;
}
}
// dane rzeczywiste w formacie anglosaskim
var value1 = double1.val().replace(",", ".");
double1.val(value1);
var value2 = double2.val().replace(",", ".");
double2.val(value2);
var value3 = double3.val().replace(",", ".");
double3.val(value3);
// pozwalamy na przesłanie danych
return true;
}
Przypomnijmy, że ta funkcja jS jest wykonywana przed funkcją [submit] formularza. Jeśli zwraca wartość logiczną [false] (wiersz 11), to wysłanie formularza nie nastąpi. Jeśli zwraca inną wartość (wiersz 22), to wysłanie formularza nastąpi.
- Istotny fragment kodu znajduje się w wierszach 4–12;
- wiersz 4: pobieramy wartość ukrytego pola [clientValidation]. Wartość ta wynosi „true”, jeśli walidacja po stronie klienta ma być włączona, w przeciwnym razie „false”;
- wiersz 6: w przypadku walidacji po stronie klienta usuwamy komunikaty o błędach serwera, które mogą się pojawić, ponieważ użytkownik właśnie zmienił ustawienia regionalne;
- wiersz 9: przypomnijmy, że zmienna [formulaire] reprezentuje węzeł tagu HTML <form>, a więc formularz. Formularz ten zawiera walidatory jS, których jeszcze nie omówiliśmy i które będą przedmiotem kolejnych akapitów. Wyrażenie [formulaire.validate().form()] wymusza wykonanie wszystkich walidatorów jS obecnych w formularzu. Jego wartość wynosi [true], jeśli wszystkie sprawdzane wartości są prawidłowe, a w przeciwnym razie – [false];
- wiersz 11: zwracana jest wartość [false], jeśli co najmniej jedna ze sprawdzanych wartości jest nieprawidłowa. Zapobiegnie to przesłaniu [submit] z formularza na serwer;
- wiersze 15–20: identyfikatory [double1, double2, double3] reprezentują trzy liczby rzeczywiste z formularza. W zależności od ustawienia regionalnego wprowadzana wartość jest inna. Przy ustawieniu regionalnym [fr-FR] wpisuje się [10,37], natomiast przy ustawieniu regionalnym [en-US] wpisuje się [10.37]. To dotyczy wprowadzania danych. W przypadku ustawienia regionalnego [fr-FR] wartość wysłana dla [double1] będzie wyglądać jak [double1=10,37]. Po dotarciu na serwer wartość [10,37] zostanie odrzucona, ponieważ serwer oczekuje wartości [10.37], czyli domyślnego formatu liczb rzeczywistych w Javie. Dlatego w wierszach 15–20 w wartościach wprowadzonych dla tych liczb przecinek zastępuje się kropką;
- wiersz 15: wyrażenie [double1.val()] zwraca ciąg znaków wprowadzony dla węzła [double1]. Wyrażenie [double1.val().replace(",", ".")] zastępuje w tym ciągu przecinki kropkami. Wynikiem jest ciąg znaków [value1];
- wiersz 16: instrukcja [double1.val(value1)] przypisuje tę wartość [value1] do węzła [double1].
Technicznie rzecz biorąc, jeśli użytkownik wprowadził [10,37] zamiast rzeczywistej wartości [double1], to po wykonaniu powyższych instrukcji węzeł [double1] przyjmuje wartość [10.37], a wartość, która zostanie przesłana, to [param1=val1&double1=10.37¶m2=val2] – wartość ta zostanie zaakceptowana przez serwer;
- wiersz 22: zwracamy wartość [true], aby wykonała się operacja [submit] z formularza;
Należy pamiętać, że funkcja jS [postForm01]:
- uruchamia wszystkie walidatory jS w formularzu, jeśli walidacja po stronie klienta jest włączona, i uniemożliwia przesłanie [submit] z formularza na serwer, jeśli któraś z wprowadzonych wartości została uznana za nieprawidłową;
- zezwala na wykonanie [submit] albo dlatego, że walidacja po stronie klienta nie jest włączona, albo dlatego, że jest włączona, a wszystkie wprowadzone wartości są prawidłowe;
Pozostaje instrukcja w wierszu [3]:
// usuwamy błędy serwera
clearServerErrors();
Funkcja [clearServerErrors] służy do usuwania komunikatów znajdujących się w kolumnie 4 widoku [vue-01.xml]:
![]() |
Na powyższym zrzucie ekranu kliknięto link [English]. Zauważono, że spowodowało to wygenerowanie POST wprowadzonych wartości bez uruchomienia walidatorów jS. Po zakończeniu operacji POST kolumna [Server Validation] wypełnia się ewentualnymi komunikatami o błędach. Jeśli teraz klikniemy przycisk [Validate] [2] przy włączonych walidatorach jS [3], wówczas kolumna [Client Validation] [4] zostanie wypełniona komunikatami. Jeśli nie podejmie się żadnych działań, wartości obecne w kolumnie [Server Validation] pozostaną tam, co spowoduje zamieszanie, ponieważ w przypadku błędów wykrytych przez walidatory jS serwer nie zostanie obciążony. Aby temu zapobiec, w funkcji [postForm01] usuwamy kolumnę [Server Validation]. Zadanie to wykonuje funkcja []:
function clearServerErrors() {
// usuwamy komunikaty o błędach z serwera
$(".error").each(function(index) {
$(this).text("");
});
}
Cechą charakterystyczną komunikatów o błędach jest to, że wszystkie należą do klasy [error]. Na przykład w przypadku pierwszego wiersza tabeli w [vue-01.html]:
<span th:if="${#fields.hasErrors('strNotEmpty')}" th:errors="*{strNotEmpty}" class="error">Donnée erronée</span>
I są to jedyne węzły w DOM posiadające tę klasę. Wykorzystujemy tę właściwość w funkcji [clearServerErrors]:
function clearServerErrors() {
// usuwamy komunikaty o błędach z serwera
$(".error").each(function(index) {
$(this).text("");
});
}
- wiersz 3: wyrażenie [$(".error")] zwraca zbiór węzłów z DOM posiadających klasę [error];
- wiersz 3: wyrażenie [$(".error").each(function(index){f}] wywołuje funkcję [f] dla każdego węzła z tej kolekcji. Funkcja ta otrzymuje parametr [index], który nie jest tutaj wykorzystywany i który stanowi numer węzła w kolekcji;
- wiersz 4: wyrażenie [$(this)] określa bieżący węzeł w iteracji. Jest to tag HTML <span>. Wyrażenie [$(this).text("")] przypisuje pusty ciąg znaków do tekstu wyświetlanego przez tag <span>;
Przeanalizujemy teraz różne walidatory jS.
6.3.8. Walidator [required]
Przyjrzyjmy się pierwszemu elementowi formularza:
![]() |
Wiersz [1] jest generowany przez następującą sekwencję widoku [vue-01.xml]:
<!-- wymagane -->
<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>
Wiersze te dotyczą pola [strNotEmpty] w formularzu [Form01]:
@NotNull
@NotBlank
private String strNotEmpty;
Ograniczenia [1-2] powodują, że pole [strNotEmpty] musi być istniejącym ciągiem znaków [NotNull], nie może być puste ani składać się wyłącznie ze spacji [NotBlank]. Chcemy odtworzyć to ograniczenie po stronie klienta za pomocą JavaScriptu.
Przyjrzyjmy się wierszom 5 i 8. Wiersz 11 nie stanowi problemu. Wyświetla on komunikat o błędzie związany z polem [strNotEmpty]. Zacznijmy od wiersza 5:
<input type="text" th:field="*{strNotEmpty}" data-val="true" th:attr="data-val-required=#{NotNull}" />
Na podstawie tego kodu Thymeleaf wygeneruje następujący tag:
<input type="text" data-val="true" data-val-required="Field is required" id="strNotEmpty" name="strNotEmpty" value="x" />
- atrybut [data-val='true'] jest wykorzystywany przez biblioteki walidacyjne jQuery. Jego obecność oznacza, że wartość węzła podlega walidacji;
- atrybut [data-val-X='msg'] zawiera dwie informacje. [X] to nazwa walidatora, a [msg] to komunikat o błędzie powiązany z nieprawidłową wartością węzła, na którym działa walidator. Jest to jedynie informacja. Nie powoduje to wyświetlenia komunikatu o błędzie;
- [required] to walidator rozpoznawany przez bibliotekę walidacyjną [jquery.validate.unobstrusive] firmy Microsoft. Nie ma potrzeby go definiować. Nie zawsze tak będzie w dalszej części;
- tagi [data-x] są ignorowane przez HTML5. Są one przydatne tylko wtedy, gdy istnieje kod JavaScript, który je wykorzystuje;
Przyjrzyjmy się teraz wierszowi 8:
<span class="field-validation-valid" data-valmsg-for="strNotEmpty" data-valmsg-replace="true"></span>
Służy ona do wyświetlenia komunikatu o błędzie z walidatora [required]. Jeśli wystąpi błąd, biblioteka walidacyjna jS dynamicznie zastąpi wiersz HTML w tablicy następującym kodem:
<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>
- wiersz 4: zmieniła się klasa węzła [strNotEmpty]. Została ona zmieniona na [input-validation-error], co powoduje, że błędne pole jest zaznaczone na czerwono;
- wiersz 7: zmieniła się klasa węzła [span]. Została ona zmieniona na [field-validation-error], co spowoduje wyświetlenie tekstu węzła [span] na czerwono;
- wiersz 8: element [span], który wcześniej był pusty, zawiera teraz tekst [Le champ est obligatoire]. Tekst ten pochodzi z tagu [data-val-required="Le champ est obligatoire"] z wiersza 4;
- wiersz 7: aby wyświetlić komunikat o błędzie węzła [strNotEmpty] z wiersza 4, należy w wierszu 7 użyć atrybutów [data-valmsg-for="strNotEmpty"] i [data-valmsg-replace="true"];
6.3.9. Walidator [assertfalse]
![]() |
Wiersz [1] jest generowany przez następującą sekwencję z widoku [vue-01.xml]:
<!-- wymagane, 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>
Wiersze te dotyczą pola [assertFalse] w formularzu [Form01]:
@NotNull
@AssertFalse
private Boolean assertFalse;
Chcemy odtworzyć to ograniczenie po stronie klienta za pomocą JavaScriptu. Wiersze 12–17 są już standardowe:
- wiersze 12–14: w przypadku błędu w polu [assertFalse] wyświetlają komunikat zawarty w atrybucie [data-val-assertfalse] z wiersza 6 lub ten zawarty w atrybucie [data-val-required] z tego samego wiersza. Należy pamiętać, że komunikaty te są zlokalizowane, tj. wyświetlane w języku wybranym wcześniej przez użytkownika lub w języku francuskim, jeśli użytkownik nie dokonał wyboru;
- wiersze 5–10: wyświetlają przyciski opcji z walidatorami JavaScript, które uruchamiają się, gdy tylko użytkownik kliknie jeden z nich.
Oba przyciski są zbudowane w ten sam sposób. Przyjrzyjmy się pierwszemu z nich:
<input type="radio" th:field="*{assertFalse}" value="true" data-val="true" th:attr="data-val-required=#{NotNull},data-val-assertfalse=#{AssertFalse}" />
Po przetworzeniu przez Thymeleaf wiersz ten przybiera następującą postać:
<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" />
Mamy walidatory o nazwach [data-val="true"]. Są dwa. Jeden o nazwie [required] [data-val-required="Le champ est obligatoire"], a drugi o nazwie [assertfalse] [data-val-assertfalse="Seule la valeur False est acceptée"]. Przypominamy, że wartość atrybutu [data-val-X] stanowi komunikat o błędzie walidatora X.
Omówiliśmy już walidator [required]. Nowością jest tutaj to, że do wprowadzonej wartości można przypisać kilka walidatorów. Chociaż walidator [required] jest znany bibliotece walidacyjnej MS (Microsoft), to nie dotyczy to walidatora [assertFalse]. Nauczymy się zatem, jak utworzyć nowy walidator. Utworzymy kilka z nich i umieścimy je w pliku [client-validation.js]:
![]() |
Ten plik, podobnie jak pozostałe, jest importowany przez widok [vue-01.xml] (wiersz 6 poniżej):
<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>
Dodanie walidatora [assertfalse] sprowadza się do utworzenia dwóch następujących funkcji 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("''", "'");
});
Szczerze mówiąc, nie jestem specjalistą od JavaScriptu, języka, który wciąż pozostaje dla mnie zagadką. Jego podstawy są proste, ale biblioteki oparte na tych podstawach są często bardzo złożone. Pisząc powyższe linijki kodu, czerpałem inspirację z kodów znalezionych w Internecie. To właśnie link [http://jsfiddle.net/LDDrk/] wskazał mi kierunek działania. Jeśli nadal istnieje, zachęcam czytelnika do zapoznania się z nim, ponieważ zawiera on kompletny, działający przykład. Pokazuje on, jak utworzyć nowy walidator, i pozwolił mi stworzyć wszystkie walidatory w tym rozdziale. Wróćmy do kodu:
- wiersze 2–4: definiują nowy walidator. Funkcja [$.validator.addMethod] oczekuje jako pierwszego parametru nazwy walidatora, a jako drugiego – funkcji definiującej ten walidator;
- wiersz 2: funkcja ma trzy parametry:
- [value]: wartość do sprawdzenia. Funkcja musi zwrócić [true], jeśli wartość jest prawidłowa, a [false] w przeciwnym razie,
- [element]: element HTML, do którego należy wartość podlegająca walidacji,
- [param]: obiekt zawierający wartości powiązane z parametrami walidatora. Nie wprowadziliśmy jeszcze tego pojęcia. W tym przypadku walidator [assertFalse] nie ma parametrów. Można stwierdzić, czy wartość [value] jest prawidłowa bez dodatkowych informacji. Inaczej byłoby, gdybyśmy musieli sprawdzić, czy wartość [value] jest liczbą rzeczywistą z przedziału [min, max]. W takim przypadku musielibyśmy znać wartości [min] i [max]. Te dwie wartości nazywamy parametrami walidatora;
- wiersze 6–9: funkcja niezbędna dla biblioteki walidacyjnej MS. Funkcja [$.validator.unobtrusive.adapters.add] oczekuje jako pierwszego parametru nazwy walidatora, jako drugiego parametru tablicy parametrów walidatora, a jako trzeciego parametru funkcji;
- walidator [assertFalse] nie posiada parametrów. Dlatego drugim parametrem jest pusta tablica;
- funkcja ma tylko jeden parametr – obiekt [options], który zawiera informacje o elemencie podlegającym walidacji i dla którego należy zdefiniować dwie nowe właściwości: [rules] oraz [messages];
- wiersz 7: definiuje się reguły [rules] dla walidatora [assertFalse]. Reguły te stanowią parametry walidatora [assertFalse], takie same jak parametry [param] z wiersza 2. Parametry te znajdują się w [options.params];
- wiersz 8: definiują komunikat o błędzie walidatora [assertFalse]. Znajduje się on w [options.message]. W przypadku komunikatów o błędach pojawia się następująca trudność. W plikach komunikatów znajdziemy następujący komunikat:
Range.form01.int1014=La valeur doit être dans l''intervalle [10,14]
Podwójny apostrof jest niezbędny dla Thymeleaf. Interprezuje go on jako pojedynczy apostrof. Jeśli umieścimy pojedynczy apostrof, Thymeleaf go nie wyświetli. Teraz komunikaty te będą również służyć jako komunikaty o błędach dla biblioteki walidacyjnej MS. Jednak skrypt JavaScript wyświetli w nich dwa apostrofy. W wierszu 8 zastępujemy więc podwójny apostrof w komunikacie o błędzie pojedynczym apostrofem.
Aby nieco lepiej zrozumieć, co się dzieje, możemy dodać kod logowania jS:
// logi
var logs = {
assertfalse : true
}
// -------------- assertfalse
$.validator.addMethod("assertfalse", function(value, element, param) {
// logi
if (logs.assertfalse) {
console.log(jSON.stringify({
"[assertfalse] value" : value
}));
console.log("[assertfalse] element");
console.log(element);
console.log(jSON.stringify({
"[assertfalse] param" : param
}));
}
// test poprawności
return value === "false";
});
$.validator.unobtrusive.adapters.add("assertfalse", [], function(options) {
// logi
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
}));
}
// kod
options.rules["assertfalse"] = options.params;
options.messages["assertfalse"] = options.message.replace("''", "'");
});
Kod ten korzysta z biblioteki jSON JSON3 [http://bestiejs.github.io/json3/]. Jeśli włączymy logowanie (wiersz 3), w konsoli pojawią się następujące komunikaty:
Podczas pierwszego załadowania strony pojawiają się następujące wpisy w logach:
Wykonano funkcję jS [$.validator.unobtrusive.adapters.add]. Dowiadujemy się z niej, że:
- [options.params] jest obiektem pustym, ponieważ walidator [assertFalse] nie ma parametrów;
- [options.message] to komunikat o błędzie, który utworzono dla walidatora [assertFalse] w atrybucie [data-val-assertFalse];
- [options.messages] to obiekt zawierający pozostałe komunikaty o błędach dla walidowanego elementu. Znajduje się tu komunikat o błędzie, który umieściliśmy w atrybucie [data-val-required];
Teraz nadajmy błędną wartość polu [assertFalse] i przeprowadźmy walidację:
Otrzymujemy wówczas następujące logi:
![]() |
Widać w nich następujące informacje:
- testowaną wartością jest [true] (wiersz 118);
- testowanym elementem HTML jest przycisk opcji o identyfikatorze [assertFalse1] (wiersz 122);
- walidator [assertFalse] nie ma żadnych parametrów (wiersz 123);
To wszystko. Co z tego wynika?
W przypadku walidatora X o identyfikatorze jS musimy zdefiniować:
- w tagu HTML, który ma zostać zweryfikowany, atrybut [data-val-X='msg'], który definiuje zarówno walidator X, jak i jego komunikat o błędzie;
- dwie funkcje jS, które należy umieścić w pliku [client-validation.js]:
- [$.validator.addMethod("X", function(value, element, param)],
- [$.validator.unobtrusive.adapters.add("X", [param1, param2], function(options)] ;
Następnie będziemy opierać się na tym, co zostało zrobione dla tego pierwszego walidatora, i po prostu przedstawimy nowości.
6.3.10. Walidator [asserttrue]
Ten walidator jest oczywiście analogiczny do walidatora [assertFalse].
![]() |
Wiersz [1] jest generowany przez następującą sekwencję z widoku [vue-01.xml]:
<!-- wymagane, 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>
Wiersze te dotyczą pola [assertTrue] w formularzu [Form01]:
@NotNull
@AssertTrue
private Boolean assertTrue;
W wierszach 1–16 nie ma żadnych zmian. Wykorzystują one moduł walidacyjny [asserrtrue], który należy zdefiniować w pliku [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. Walidatory [date] i [past]
![]() |
Wiersz [1] jest generowany przez następującą sekwencję z widoku [vue-01.xml]:
<!-- wymagane, data, przeszłość -->
<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>
Wiersze te dotyczą pola [dateInPast] w formularzu [Form01]:
@NotNull
@Past
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date dateInPast;
Linia zawierająca walidatory daty wygląda następująco:
<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}" />
Znajdują się w niej trzy walidatory [data-val-X]: required, date, past. Musimy zdefiniować w pliku [client-validation.js] funkcje powiązane z tymi dwoma nowymi walidatorami:
logs.date = true;
// -------------- data
$.validator.addMethod("date", function(value, element, param) {
// ważność
var valide = Globalize.parseDate(value, "yyyy-MM-dd") != null;
// logi
if (logs.date) {
console.log(jSON.stringify({
"[date] value" : value,
"[date] valide" : valide
}));
}
// wynik
return valide;
});
$.validator.unobtrusive.adapters.add("date", [], function(options) {
options.rules["date"] = options.params;
options.messages["date"] = options.message.replace("''", "'");
});
oraz
logs.past = true;
// -------------- przeszłość
$.validator.addMethod("past", function(value, element, param) {
// ważność
var valide = value <= new Date().toISOString().substring(0, 10);
// logi
if (logs.past) {
console.log(jSON.stringify({
"[past] value" : value,
"[past] valide" : valide
}));
}
// wynik
return valide;
});
$.validator.unobtrusive.adapters.add("past", [], function(options) {
options.rules["past"] = options.params;
options.messages["past"] = options.message.replace("''", "'");
});
Zanim wyjaśnimy kod, przyjrzyjmy się logom pojawiającym się po wprowadzeniu daty późniejszej niż dzisiejsza:
Pierwszą rzeczą, na którą należy zwrócić uwagę, jest to, że data do sprawdzenia pojawia się jako ciąg znaków w formacie [aaaa-mm-jj]. To wyjaśnia następujące wiersze:
var valide = Globalize.parseDate(value, "yyyy-MM-dd") != null;
Biblioteka [globalize.js] udostępnia powyższą funkcję [Globalize.parseDate]. Pierwszym parametrem jest data jako ciąg znaków, a drugim jej format. Wynikiem jest wskaźnik null, jeśli data jest nieprawidłowa, w przeciwnym razie – wynikowa data.
Poprawność walidatora [past] jest sprawdzana za pomocą następującego kodu:
var valide = value <= new Date().toISOString().substring(0, 10);
Oto wynik oceny wyrażenia [new Date().toISOString().substring(0, 10)] na konsoli:
![]() |
Aby ciąg znaków [value] był prawidłowy, musi alfabetycznie poprzedzać ciąg [new Date().toISOString().substring(0, 10)].
Należy zauważyć, że używana wersja przeglądarki Chrome podaje datę w formacie [yyyy-mm-dd]. W przypadku przeglądarki, w której tak nie jest, należałoby wyraźnie poinformować użytkownika o konieczności stosowania tego formatu wprowadzania danych.
6.3.12. Walidator [future]
![]() |
Wiersz [1] jest generowany przez następującą sekwencję widoku [vue-01.xml]:
<!-- wymagane, data, przyszłość -->
<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>
Wiersze te dotyczą pola [dateInFuture] w formularzu [Form01]:
@NotNull
@Future
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date dateInFuture;
- w wierszu 5 pojawia się nowy walidator o nazwie [data-val-future];
Ten walidator jest oczywiście bardzo podobny do walidatora [past]. Dwie funkcje, które należy dodać do [client-validation.js], to:
// -------------- przyszłość
$.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. Walidatory [int] i [max]
![]() |
Wiersz [1] jest generowany przez następującą sekwencję z widoku [vue-01.xml]:
<!-- wymagane, 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>
Wiersze te dotyczą pola [intMax100] w formularzu [Form01]:
@NotNull
@Max(value = 100)
private Integer intMax100;
W wierszu 5 znajdują się dwa nowe walidatory: [int] i [max]. Ten ostatni posiada jeden parametr: wartość maksymalną. Przyjrzyjmy się kodowi HTML wygenerowanemu przez wiersz 5:
<!-- wymagane, int, max(100) -->
<tr>
<td class="col1">required, int, max(100)</td>
<td class="col2">
<input type="text" data-val="true" data-val-int="Format invalide" data-val-max-value="100" data-val-required="Le champ est obligatoire" data-val-max="La valeur doit être inférieure ou égale à 100" value="" id="intMax100" name="intMax100" />
</td>
<td class="col3">
<span class="field-validation-valid" data-valmsg-for="intMax100" data-valmsg-replace="true"></span>
</td>
<td class="col4">
</td>
</tr>
Przypomnijmy znaczenie poszczególnych atrybutów [data-X]:
- [data-val="true"] wskazuje, że do elementu HTML przypisane są walidatory;
- [data-val-required] wprowadza walidator [required] wraz z jego komunikatem;
- [data-val-int] wprowadza walidator [int] wraz z jego komunikatem;
- [data-val-max] wprowadza walidator [max] wraz z komunikatem;
- [data-val-max-value="100"] wprowadza parametr o nazwie [value] dla walidatora [max]. [100] jest wartością tego parametru. Po raz pierwszy spotykamy się z pojęciem parametrów walidatora.
Plik [client-validation.js] został wzbogacony o następujący walidator [int]:
logs.int = true;
// -------------- liczba całkowita
$.validator.addMethod("int", function(value, element, param) {
// ważność
valide = /^\s*[-\+]?\s*\d+\s*$/.test(value);
// logi
if (logs.int) {
console.log(jSON.stringify({
"[int] value" : value,
"[int] valide" : valide,
}));
}
// wynik
return valide;
});
$.validator.unobtrusive.adapters.add("int", [], function(options) {
options.rules["int"] = options.params;
options.messages["int"] = options.message.replace("''", "'");
});
- wiersz 5: stosuje się wyrażenie regularne w celu sprawdzenia, czy ciąg znaków [value] rzeczywiście reprezentuje liczbę całkowitą. Liczba ta może być ze znakiem;
Oto kilka przykładów wpisów w logach:
Walidator [max] jest dodawany w następujący sposób do [client-validation.js]
// -------------- max do użycia w połączeniu z [int] lub [number]
logs.max = true;
$.validator.addMethod("max", function(value, element, param) {
// logi
if (logs.max) {
console.log(jSON.stringify({
"[max] value" : value,
"[max] param" : param
}));
}
// ważność
var val = Globalize.parseFloat(value);
if (isNaN(val)) {
// logi
if (logs.max) {
console.log(jSON.stringify({
"[max] valide" : true
}));
}
// wynik
return true;
}
var max = Globalize.parseFloat(param.value);
var valide = val <= max;
// logi
if (logs.max) {
console.log(jSON.stringify({
"[max] valide" : valide
}));
}
// wynik
return valide;
});
$.validator.unobtrusive.adapters.add("max", [ "value" ], function(options) {
options.rules["max"] = options.params;
options.messages["max"] = options.message.replace("''", "'");
});
Zajmiemy się teraz przypadkiem parametru [value] walidatora [max], wprowadzonego przez atrybut [data-val-max-value="100"].
- w wierszu 35 parametr [value] jest włączony do drugiego parametru funkcji [$.validator.unobtrusive.adapters.add];
- w wierszu 3 obiekt [param] nie będzie już pusty, lecz będzie zawierał {"value":100};
Aby zrozumieć kod w wierszach 3–33, należy wiedzieć, że gdy na tym samym elemencie HTML znajduje się kilka walidatorów:
- nie znamy kolejności ich działania;
- wykonanie walidatorów zatrzymuje się, gdy tylko jeden z nich uzna element za nieprawidłowy. Wówczas to komunikat o błędzie tego walidatora jest przypisywany do nieprawidłowego elementu;
Przeanalizujmy kod:
- wiersz 12: sprawdzamy, czy mamy liczbę. Jeśli walidator [int] został uruchomiony przed walidatorem [max], to warunek ten jest z konieczności spełniony, ponieważ nieprawidłowa wartość zatrzymuje wykonywanie walidatorów;
- wiersze 13–22: jeśli nie mamy liczby, oznacza to, że walidator [int] nie został jeszcze uruchomiony. Następnie wskazujemy, że sprawdzana wartość jest prawidłowa, aby walidator [int] mógł wykonać swoje zadanie i oznaczyć element jako nieprawidłowy wraz z własnym komunikatem o błędzie;
- wiersze 23–24: oblicza poprawność walidatora [value];
Oto kilka wpisów z dziennika:
Wprowadzona wartość | logi |
| |
| |
|
6.3.14. Narzędzie do sprawdzania poprawności [min]
![]() |
Wiersz [1] jest generowany przez następującą sekwencję z widoku [vue-01.xml]:
<!-- wymagane, 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>
Wiersze te dotyczą pola [intMin10] w formularzu [Form01]:
@NotNull
@Min(value = 10)
private Integer intMin10;
Wiersz 5 wprowadza nowy walidator [min] [data-val-int=#{typeMismatch}] z parametrem [value] [data-val-min-value=#{form01.intMin10.value}"]. Mamy tu do czynienia z przypadkiem analogicznym do walidatora [max]. W [client-validation.js] dodajemy następujący kod:
logs.min = true;
//-------------- min do użycia w połączeniu z [int] lub [number]
$.validator.addMethod("min", function(value, element, param) {
// logi
if (logs.min) {
console.log(jSON.stringify({
"[min] value" : value,
"[min] param" : param
}));
}
// ważność
var val = Globalize.parseFloat(value);
if (isNaN(val)) {
// logi
if (logs.min) {
console.log(jSON.stringify({
"[min] valide" : true
}));
}
// wynik
return true;
}
var min = Globalize.parseFloat(param.value);
var valide = val >= min;
// logi
if (logs.min) {
console.log(jSON.stringify({
"[min] valide" : valide
}));
}
// wynik
return valide;
});
$.validator.unobtrusive.adapters.add("min", [ "value" ], function(options) {
options.rules["min"] = options.params;
options.messages["min"] = options.message.replace("''", "'");
});
Oto kilka logów wykonania:
Wprowadzona wartość | logi |
| |
| |
|
6.3.15. Narzędzie do sprawdzania poprawności [regex]
![]() |
Wiersz [1] jest generowany przez następującą sekwencję z widoku [vue-01.xml]:
<!-- wymagane, wyrażenie regularne -->
<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>
Wiersze te dotyczą pola [strBetween4and6] w formularzu [Form01]:
@NotNull
@Size(min = 4, max = 6)
private String strBetween4and6;
Wiersz 5 generuje następujący formularz 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" />
Ten tag wprowadza walidator [regex] [data-val-regex="La chaîne doit avoir entre 4 et 6 caractères"] wraz z jego parametrem [pattern] [data-val-regex-pattern="^.{4,6}$"]. Parametr [pattern] to wyrażenie regularne, które musi spełniać sprawdzana wartość. W tym przypadku wyrażenie regularne sprawdza, czy ciąg znaków zawiera od 4 do 6 dowolnych znaków. Walidator [regex] jest predefiniowany w bibliotece walidacji MS. Nie ma zatem potrzeby dodawania niczego do pliku [client-validation.js].
6.3.16. Walidator [email]
![]() |
Wiersz [1] jest generowany przez następującą sekwencję z widoku [vue-01.xml]:
<!-- wymagane, adres 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>
Wiersze te dotyczą pola [email] w formularzu [Form01]:
@NotNull
@Email
@NotBlank
private String email;
Wiersz 5 generuje następujący wiersz 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" />
Ten tag wprowadza walidator [email] [data-val-email="Adresse mail invalide"]. Walidator [email] jest predefiniowany w bibliotece walidacji MS. Nie ma zatem potrzeby dodawania niczego do pliku [client-validation.js].
6.3.17. Walidator [range]
![]() |
Wiersz [1] jest generowany przez następującą sekwencję z widoku [vue-01.xml]:
<!-- wymagane, liczba całkowita, zakres (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>
Wiersze te dotyczą pola [int1014] w formularzu [Form01]:
@Range(min = 10, max = 14)
@NotNull
private Integer int1014;
Wiersz 5 generuje następujący wiersz 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" />
Ten tag wprowadza nowy walidator [range] [data-val-range="La valeur doit être dans l''intervalle [10,14]"], który ma dwa parametry: [min] [data-val-range-min="10"] oraz [max] [data-val-range-max="14"].
W pliku [client-validation.js] definiujemy walidator [range] w następujący sposób:
// -------------- zakres do użycia w połączeniu z [int] lub [number]
logs.range=true
$.validator.addMethod("range", function(value, element, param) {
// logi
if (logs.range) {
console.log(jSON.stringify({
"[range] value" : value,
"[range] param" : param
}));
}
// ważność
var val = Globalize.parseFloat(value);
if (isNaN(val)) {
// logi
if (logs.min) {
console.log(jSON.stringify({
"[range] valide" : true
}));
}
// zakończone
return true;
}
var min = Globalize.parseFloat(param.min);
var max = Globalize.parseFloat(param.max);
var valide = val >= min && val <= max;
// logi
if (logs.range) {
console.log(jSON.stringify({
"[range] valide" : valide
}));
}
// zakończone
return valide;
});
$.validator.unobtrusive.adapters.add("range", [ "min", "max" ], function(options) {
options.rules["range"] = options.params;
options.messages["range"] = options.message.replace("''", "'");
});
Jest on bardzo podobny do walidatorów [min] i [max], które już omówiliśmy.
Oto kilka przykładów logów:
Wprowadzona wartość | logi |
| |
| |
|
6.3.18. Walidator [number]
![]() |
Wiersz [1] jest generowany przez następującą sekwencję z widoku [vue-01.xml]:
<!-- double1: wymagane, liczba, zakres (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>
Wiersze te dotyczą pola [double1] w formularzu [Form01]:
@NotNull
@DecimalMax(value = "3.4")
@DecimalMin(value = "2.3")
private Double double1;
Wiersz 5 generuje następujący wiersz 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" />
Tag ten wprowadza nowy walidator [number] z atrybutem [data-val-number="Format invalide"]. Walidator ten jest zdefiniowany w następujący sposób w pliku [client-validation.js]:
// -------------- liczba
logs.number = true;
$.validator.addMethod("number", function(value, element, param) {
var valide = !isNaN(Globalize.parseFloat(value));
// logi
if (logs.number) {
console.log(jSON.stringify({
"[number] value" : value,
"[number] valide" : valide
}));
}
// wynik
return valide;
});
$.validator.unobtrusive.adapters.add("number", [], function(options) {
options.rules["number"] = options.params;
options.messages["number"] = options.message.replace("''", "'");
});
Oto kilka przykładów wpisów w dzienniku:
Wprowadzona wartość | logi |
Wiadomo, że liczby rzeczywiste są zależne od kultury. Powyżej mamy do czynienia z formatem [fr-FR]. Po wpisaniu [2.5] (notacja anglosaska) liczba jest akceptowana. Wina leży po stronie [Globalize.parseFloat], który akceptuje obie notacje:
Przejdźmy na język angielski i wprowadźmy wartości [+2,5] oraz [+2.5]. Logi wyglądają następująco:
Wprowadzona wartość | logi |
Wystąpił błąd dotyczący kodu [2,5]. Został on zgłoszony jako prawidłowy, podczas gdy należy wpisać [2.5]. Błąd wynika z kodu [Globalize.parseFloat]:
W powyższym przykładzie [Globalize.parseFloat] ignoruje przecinek i uznaje, że liczba wynosi 25. W systemie [en-US] liczba rzeczywista może zawierać kropkę dziesiętną oraz przecinki, które są czasami używane do oddzielania tysięcy.
Można to poprawić w następujący sposób:
// -------------- liczba
logs.number = true;
$.validator.addMethod("number", function(value, element, param) {
// obsługujemy wyłącznie uprawy [fr-FR] i [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;
// test poprawności
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));
}
// logi
if (logs.number) {
console.log(jSON.stringify({
"[number] value" : value,
"[number] culture" : culture,
"[number] valide" : valide
}));
}
// wynik
return valide;
});
- wiersz 5: wyrażenie regularne liczby rzeczywistej dla kultury [fr-FR];
- wiersz 6: wyrażenie regularne liczby rzeczywistej dla kultury [en-US];
- wiersz 7: nazwa aktualnej kultury. W naszym przykładzie będzie to jedna z dwóch wyżej wymienionych kultur;
- wiersze 9–16: sprawdzanie poprawności wprowadzonej wartości;
- wiersz 15: uwzględniono przypadek, w którym kultura nie jest ani [fr-FR], ani [en-US];
W logach pojawia się teraz następująca informacja:
Kultura [fr-FR]
Wprowadzona wartość | logi |
| |
| |
| |
|
Kultura [en-US]
Wprowadzona wartość | logi |
| |
| |
|
6.3.19. Narzędzie do sprawdzania poprawności [custom3]
![]() |
Wiersz [1] jest generowany przez następującą sekwencję z widoku [vue-01.xml]:
<!-- double3: wymagane, liczba, 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>
Wiersze te dotyczą pola [double3] w formularzu [Form01]:
@NotNull
private Double double3;
W tym miejscu chcemy przeanalizować walidator, który nie sprawdza już wprowadzonej wartości, ale relację między dwiema wprowadzonymi wartościami. W tym przypadku chcemy, aby [double1+double3] mieściło się w przedziale [10,13].
Wiersz 5 generuje następujący wiersz: 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" />
Ten wiersz wprowadza nowy walidator [custom3] zadeklarowany przez atrybut [data-val-custom3="[double3+double1] must be in [10,13]"]. Walidator ten ma następujące parametry:
- [field] zadeklarowany przez atrybut [data-val-custom3-field="double1"]. Parametr ten określa pole, którego wartość bierze udział w obliczaniu poprawności [double3];
- [min] zadeklarowany przez atrybut [data-val-custom3-min="10.0"]. Parametr ten stanowi minimalną wartość przedziału [min, max], w którym musi znajdować się [double1+double3];
- [max] zadeklarowany przez atrybut [data-val-custom3-max="13.0"]. Parametr ten stanowi wartość maksymalną przedziału [min, max], w którym musi znajdować się [double1+double3];
Ten walidator jest obsługiwany w następujący sposób w [client-validation.js]:
// -------------- custom3 używane w połączeniu z [number]
logs.custom3 = true;
$.validator.addMethod("custom3", function(value1, element, param) {
// druga wartość
var value2 = $("#" + param.field).val();
// logi
if (logs.custom3) {
console.log(jSON.stringify({
"[custom3] value1" : value1,
"[custom3] param" : param,
"[custom3] value2" : value2
}))
}
// pierwsza wartość
var valeur1 = Globalize.parseFloat(value1);
if (isNaN(valeur1)) {
// pozwalamy, aby walidator [number] wykonał zadanie
if (logs.custom3) {
console.log(jSON.stringify({
"[custom3] valide" : true
}))
}
return true;
}
// druga wartość
var valeur2 = Globalize.parseFloat(value2);
if (isNaN(valeur2)) {
// nie można wykonać obliczeń ważności
if (logs.custom3) {
console.log(jSON.stringify({
"[custom3] valide" : false
}))
}
return false;
}
// obliczenie ważności
var min = Globalize.parseFloat(param.min);
var max = Globalize.parseFloat(param.max);
var somme = valeur1 + valeur2;
var valide = somme >= min && somme <= max;
// logi
if (logs.custom3) {
console.log(jSON.stringify({
"[custom3] valide" : valide
}))
}
// wynik
return valide;
});
$.validator.unobtrusive.adapters.add("custom3", [ "field", "max", "min" ], function(options) {
options.rules["custom3"] = options.params;
options.messages["custom3"] = options.message.replace("''", "'");
});
Oto kilka przykładów wpisów w logach:
Wprowadzone wartości [double1,double3] | logi |
| |
| |
| |
|
6.3.20. Narzędzie do sprawdzania poprawności [url]
![]() |
Wiersz [1] jest generowany przez następującą sekwencję z widoku [vue-01.xml]:
<!-- wymagane, adres 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>
Wiersze te dotyczą pola [url] w formularzu [Form01]:
@URL
@NotBlank
private String url;
Wiersz 5 generuje następujący wiersz HTML:
<input type="text" data-val="true" data-val-url="Invalid URL" data-val-required="Field is required" value="" id="url" name="url" />
Wprowadza ona walidator [url] z atrybutem [data-val-url]. Walidator ten jest predefiniowany w bibliotece walidacji jQuery. Nie ma potrzeby dodawania niczego do [client-validation.js].
6.3.21. Włączanie / wyłączanie walidacji po stronie klienta
Dopóki walidacja po stronie klienta jest aktywna, walidacja po stronie serwera nigdy nie jest widoczna, ponieważ wartości przesyłane do serwera docierają tam tylko wtedy, gdy zostały uznane za prawidłowe po stronie klienta. Aby zobaczyć działanie walidacji po stronie serwera, należy wyłączyć walidację po stronie klienta. Widok [vue-01.xml] zawiera dwa linki służące do zarządzania tą aktywacją/dezaktywacją:
<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>
Te dwa linki nie są widoczne jednocześnie:
![]() | ![]() |
Tłumaczenie tych linków w widoku HTML jest następujące:
<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>
Skrypt jS [setClientValidation] jest zdefiniowany w pliku [local.js] (patrz wyżej). W funkcji [$(document).ready] tego pliku wykorzystywane są linki walidacyjne:
// dokument gotowy
$(document).ready(function() {
// odwołania globalne
...
activateValidationTrue = $("#clientValidationTrue");
activateValidationFalse = $("#clientValidationFalse");
clientValidation = $("#clientValidation");
...
// linki weryfikacyjne
// clientValidation to ukryte pole ustawione przez serwer
var validate = clientValidation.val();
setClientValidation2(validate === "true");
});
- wiersz 5: odwołanie do linku aktywującego walidację po stronie klienta;
- wiersz 6: odwołanie do linku dezaktywującego walidację po stronie klienta;
- wiersz 7: odwołanie do ukrytego pola formularza, które zapisuje ostatni stan aktywacji w postaci wartości logicznej [true : validation client activée, false : validation client désactivée]. Pole to znajduje się w widoku [vue-01.xml] w następującej postaci:
<input type="hidden" th:field="*{clientValidation}" th:value="*{clientValidation}" value="true" />
i odpowiada polu [clientValidation] w formularzu [Form01]:
// walidacja po stronie klienta
private boolean clientValidation = true;
- wiersz 11: pobieramy wartość pola ukrytego;
- wiersz 12: wywołuje się następującą funkcję [setClientValidation2]:
function setClientValidation2(activate) {
// linki
if (activate) {
// walidacja po stronie klienta jest aktywna
activateValidationTrue.hide();
activateValidationFalse.show();
// analizowane są walidatory formularza
$.validator.unobtrusive.parse(formulaire);
} else {
// walidacja po stronie klienta jest nieaktywna
activateValidationFalse.hide();
activateValidationTrue.show();
// wyłączamy walidatory formularza
formulaire.data('validator', null);
}
}
- wiersz 1: parametr [activate] przyjmuje wartość [true], jeśli należy włączyć walidację po stronie klienta, w przeciwnym razie przyjmuje wartość false;
- wiersze 5–6: link dezaktywujący jest widoczny, a link aktywujący ukryty;
- wiersz 8: aby walidacja po stronie klienta działała, należy przeanalizować dokument w poszukiwaniu walidatorów o identyfikatorze [data-val-X]. Parametrem funkcji [$.validator.unobtrusive.parse] jest identyfikator jS formularza, który ma zostać przeanalizowany;
- wiersze 11–12: link aktywacyjny jest widoczny, a link dezaktywacyjny ukryty;
- wiersz 14: walidatory formularza są wyłączone. Od tego momentu jest tak, jakby w formularzu nie było walidatorów jS;
Do czego służy ta funkcja [setClientValidation2]? Służy do zarządzania POST. Ponieważ pole [clientValidation] jest polem ukrytym, jest ono wysyłane i powraca wraz z formularzem zwróconym przez serwer. Wartość ta jest następnie wykorzystywana do przywrócenia walidacji po stronie klienta do stanu sprzed działania funkcji POST. Nie ma bowiem pamięci jS pomiędzy żądaniami. Serwer musi zatem przesłać do nowego widoku informacje umożliwiające zainicjowanie jS w tym widoku. Odbywa się to zazwyczaj w funkcji [$(document).ready].
Wróćmy do funkcji [setClientValidation], która obsługuje kliknięcie linków aktywujących/dezaktywujących walidację po stronie klienta:
// walidacja po stronie klienta
function setClientValidation(activate) {
// zarządzamy włączaniem/wyłączaniem walidacji po stronie klienta
setClientValidation2(activate);
// zapisywanie wyboru użytkownika w polu ukrytym
clientValidation.val(activate ? "true" : "false");
// dodatkowe dostosowania
if (activate) {
// walidacja po stronie klienta jest aktywna
// usuwane są wszystkie komunikaty o błędach z serwera
clearServerErrors();
// formularz jest zatwierdzany
formulaire.validate().form();
} else {
// walidacja po stronie klienta jest wyłączona
// usuwane są wszystkie komunikaty o błędach po stronie klienta
clearClientErrors();
}
}
- wiersz 4: wykorzystujemy funkcję [setClientValidation2], którą właśnie omówiliśmy;
- wiersz 6: zapisujemy wybór użytkownika w polu ukrytym, aby odzyskać go po powrocie z kolejnej funkcji POST;
- wiersz 11: jeśli walidacja po stronie klienta jest aktywna, usuwamy komunikaty o błędach z kolumny [serveur] w widoku. Funkcję [clearServerErrors] opisaliśmy w paragrafie 6.3.7;
- wiersz 13: uruchamiane są walidatory jS w celu wyświetlenia ewentualnych komunikatów o błędach w kolumnie [client] widoku;
- wiersz 17: jeśli walidacja po stronie klienta jest wyłączona, to usuwa się komunikaty o błędach z kolumny [client] w widoku. Przyjrzyjmy się w konsoli programistycznej przeglądarki Chrome kodowi HTML elementu zawierającego błąd:
<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>
- w wierszu 2 widać, że w kolumnie 2 tabeli element zawierający błąd ma styl [class="input-validation-error"];
- w wierszu 5 widać, że w kolumnie 3 tabeli komunikat o błędzie ma styl [class="field-validation-error"];
Dotyczy to wszystkich błędnych elementów. Te dwie informacje wykorzystujemy w następującej funkcji [clearClientErrors]:
// kasowanie błędów po stronie klienta
function clearClientErrors() {
// usuwamy komunikaty o błędach po stronie klienta
$(".field-validation-error").each(function(index) {
$(this).text("");
});
// zmiana klasy CSS błędnych wpisów
$(".input-validation-error").each(function(index) {
$(this).removeClass("input-validation-error");
});
}
- wiersze 4–6: wyszukujemy wszystkie elementy DOM należące do klasy [field-validation-error] i usuwamy wyświetlany przez nie tekst. W ten sposób usuwane są komunikaty o błędach;
- wiersze 8–10: wyszukuje się wszystkie elementy z DOM należące do klasy [input-validation-error] i usuwa się z nich tę klasę. W ten sposób błędny element, który był zaznaczony na czerwono, odzyskuje swój pierwotny styl;


















































