6. Валідація на стороні клієнта за допомогою JavaScript
У попередньому розділі ми розглянули перевірку на стороні сервера. Повернемося до архітектури додатка Spring MVC:
![]() |
BD
До цього моменту сторінки, що надсилалися клієнту, не містили JavaScript. Тепер ми розглянемо цю технологію, яка спочатку дозволить нам здійснювати перевірку на стороні клієнта. Принцип такий:
- саме JavaScript відправляє значення на веб-сервер;
- тож перед виконанням POST він може перевірити правильність даних і запобігти виконанню POST, якщо вони є недійсними;
Ми будемо використовувати форму, яку ми перевірили на стороні сервера. Тепер ми надамо можливість перевіряти її як на стороні клієнта, так і на стороні сервера.
Примітка: тема є складною. Читач, якого ця тема не цікавить, може перейти безпосередньо до параграфа 7.
6.1. Функціональні можливості проєкту
Ми наводимо кілька екранних знімків проекту, щоб продемонструвати його функціональні можливості. Початкова сторінка формується за допомогою URL [http://localhost:8080/js01.html]
![]() |
Перевірки були впроваджені з обох боків: на стороні клієнта та на стороні сервера. Оскільки POST відбувається лише тоді, коли значення були визнані дійсними на стороні клієнта, перевірки на стороні сервера завжди проходять успішно. Тому ми додали посилання для вимкнення перевірок на стороні клієнта. У цьому режимі ми повертаємося до способу роботи, який ми вже розглядали. Ось приклад:
123 ![]() |
- у [1] — введені значення;
- у [2] — повідомлення про помилки, пов’язані з введеними даними;
- у [3] — підсумок помилок, де для кожної з них вказано:
- назву поля, що перевіряється,
- код помилки,
- стандартне повідомлення для цього коду помилки;
Тепер увімкнемо перевірку на стороні клієнта:
![]() |
- у [1] — введені значення. Можна помітити, що неправильні введення мають особливий стиль;
- у [2] — повідомлення про помилки, пов’язані з неправильними даними. Вони ідентичні тим, що генеруються сервером;
- у [3-4] нічого немає, оскільки доти, доки є помилкові введення, запит POST до сервера не надсилається;
6.2. Серверна перевірка
6.2.1. Налаштування
Почнемо зі створення нового проекту Maven [springmvc-validation-client]:
![]() |
Ми розвиваємо проект наступним чином:
![]() |
Клас [Config] налаштовує проект. Він ідентичний тому, що був у попередніх проектах:
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;
}
}
Клас [Main] є виконуваним класом проекту:
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) {
// запускаємо додаток
ApplicationContext context = SpringApplication.run(Config.class, args);
// відображення списку бінів, знайдених Spring
System.out.println("Liste des beans Spring");
String[] beanNames = context.getBeanDefinitionNames();
Arrays.sort(beanNames);
for (String beanName : beanNames) {
System.out.println(beanName);
}
}
}
- у рядку 13 Spring Boot запускається з конфігураційним файлом [Config];
- рядки 15–20: у цьому прикладі ми показуємо, як вивести список об’єктів, що керуються Spring. Це може бути корисно, якщо іноді здається, що Spring не керує одним із наших компонентів. Це спосіб перевірити це. Це також спосіб перевірити автоконфігурацію, виконану Spring Boot. У консолі ми отримуємо список, подібний до такого:
Ми виділили об’єкти, визначені в класі [Config].
6.2.2. Модель форми
Продовжимо вивчення проєкту:
![]() |
Клас [Form01] — це клас, який прийматиме відправлені значення. Він має такий вигляд:
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 {
// відправлені значення
@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;
// перевірка на стороні клієнта
private boolean clientValidation = true;
// локалізація
private String lang;
...
}
Ми знову зустрічаємо валідатори, з якими вже стикалися раніше. Крім того, ми введемо поняття специфічної валідації. Це валідація, яку неможливо формалізувати за допомогою попередньо визначеного валідатора. Тут ми вимагатимемо, щоб [double1+double2] знаходився в діапазоні [10,13].
6.2.3. Контролер
Контролер [JsController] має такий вигляд:
![]() |
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";
}
...
// підготовка моделі для подання vue-01
private void setModel(Form01 formulaire, Model model, Locale locale, String message) {
...
}
}
- у рядку 9 — дія [/js01];
- рядок 10: об’єкт типу [Form01] інстанціюється та автоматично вставляється в шаблон, пов’язаний із ключем [form01];
- рядок 10: локаль та шаблон вводяться в параметри;
- рядок 11: на основі цієї інформації готується модель;
- рядок 12: відображається представлення [vue-01.xml];
Метод [setModel] виглядає наступним чином:
// підготовка моделі для подання vue-01
private void setModel(Form01 formulaire, Model model, Locale locale, String message) {
// підтримуються лише локалі 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));
// можливе повідомлення
if (message != null) {
model.addAttribute("message", message);
}
}
- мета методу [setModel] — внести в шаблон:
- інформацію про локаль,
- повідомлення, передане як останній параметр;
- рядок 14: у шаблон вноситься інформація про локаль (мова, країна);
- рядки 16–18: у локаль вставляється повідомлення, яке, за наявності, передається як параметр;
- рядки 8, 12: інформація про локаль також зберігається у формі [Form01]. JavaScript використовуватиме цю інформацію;
Значення, введені у форму [vue-01.xml], будуть відправлені до наступної дії [/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);
...
}
- рядок 2: анотація [@Valid Form01 formulaire] забезпечує те, що відправлені значення будуть перевірені валідаторами класу [Form01]. Ми знаємо, що в інтервалі [10,13] існує спеціальна перевірка [double1+double2]. Коли ми доходимо до рядка 3, ця перевірка ще не виконана;
- рядок 3: створюється такий об’єкт [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 {
// діапазон перевірки
private double min;
private double max;
// конструктор
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) {
// перевірений об’єкт
Form01 form01 = (Form01) form;
// значення [double1]
Double double1 = form01.getDouble1();
if (double1 == null) {
return;
}
// значення [double2]
Double double2 = form01.getDouble2();
if (double2 == null) {
return;
}
// [double1+double2]
double somme = double1 + double2;
// перевірка
if (somme < min || somme > max) {
errors.rejectValue("double2", "form01.double2", new Double[] { min, max }, null);
}
}
}
- рядок 8: для реалізації специфічної перевірки ми створюємо клас, що реалізує інтерфейс Spring [Validator]. Цей інтерфейс має два методи: [supports] у рядку 21 та [validate] у рядку 26;
- рядки 21–23: метод [supports] приймає об’єкт типу [Class]. Він повинен повертати true, щоб вказати, що підтримує цей клас, або false в іншому випадку;
- рядок 22: ми зазначаємо, що клас [Form01Validator] перевіряє лише об’єкти типу [Form01];
- рядки 15–18: нагадаємо, що ми хочемо реалізувати обмеження [double1+double2] в інтервалі [10,13]. замість того, щоб обмежуватися цим інтервалом, ми перевірятимемо обмеження [double1+double2] в інтервалі [min, max]. Саме тому ми маємо конструктор із цими двома параметрами;
- рядок 26: метод [validate] викликається з екземпляром перевіреного об’єкта, тобто в даному випадку з екземпляром [Form01], та з колекцією наразі відомих помилок [Errors errors]. Якщо перевірка, виконана методом [validate], завершилася невдало, вона повинна створити новий елемент у колекції [Errors errors];
- рядок 43: перевірка завершилася невдало. До колекції [Errors errors] додається елемент за допомогою методу [Errors.rejectValue], параметри якого такі:
- параметр 1: зазвичай це назва поля, в якому виявлено помилку. У цьому випадку перевірялися поля [double1, double2]. Можна вказати одне з них,
- повідомлення про помилку, пов’язане з ним, або, точніше, його ключ у файлах зовнішніх повідомлень:
[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}
Тут ми маємо повідомлення, що містять параметри {0} та {1}. Отже, для цього повідомлення потрібно вказати два значення. Саме це робить третій параметр методу [Errors.rejectValue].
- Четвертий параметр — це повідомлення про помилку за замовчуванням;
Повернемося до дії [/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";
}
}
- рядок 4: валідатор [Form01Validator] виконується з такими параметрами:
- параметр 1: об’єкт, що перевіряється,
- параметр 2: список помилок цього об’єкта. Це об’єкт [BindingResult result], переданий як параметр дії. Якщо перевірка завершиться невдало, цей об’єкт матиме ще одну помилку;
- рядок 5: перевіряємо, чи є помилки валідації;
- рядки 7–10: пробігаємо список помилок, щоб запам’ятати для кожної з них:
- назву об’єкта, що перевірявся,
- його код помилки,
- його повідомлення про помилку за замовчуванням;
- рядок 10: на основі цієї інформації будується модель подання [vue-01.xml]. Цього разу там міститься повідомлення — об’єднана та скорочена версія різних повідомлень про помилки;
- рядки 12–15: якщо всі надіслані значення є правильними, клієнта перенаправляють до дії [/js01], помістивши надіслані значення в атрибут Flash;
6.2.4. Вигляд
Вигляд [vue-01.xml] є складним. Ми розглянемо лише невелику його частину:
<!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>
<!-- заголовок -->
<h3>
<span th:text="#{form01.title}"></span>
<span th:text="${locale}"></span>
</h3>
<!-- меню -->
<p>
...
</p>
<!-- форма -->
<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>
<!-- обов’язкове поле -->
<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>
<!-- кнопка підтвердження -->
<input type="submit" th:value="#{form01.valider}" value="Valider" onclick="javascript:postForm01()" />
</p>
</form>
<!-- повідомлення серверних валідаторів -->
<br/>
<fieldset class="fieldset">
<legend>
<span th:text="#{server.error.message}"></span>
</legend>
<span th:text="${message}" class="error"></span>
</fieldset>
</body>
</html>
Ця сторінка використовує низку повідомлень, що містяться у файлах зовнішніх повідомлень:
[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
Повернемося до коду сторінки:
- рядок 8: велика кількість імпортів бібліотек JavaScript, які ми можемо тут проігнорувати;
- рядок 14: відображає локаль, вставлену в шаблон сервером;
- рядок 59: виводить повідомлення, вставлене в шаблон сервером;
Код у рядках 33–44 є новим. Розглянемо його:
<!-- обов'язкове -->
<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>
Мабуть, найпростіше буде подивитися на код HTML, згенерований цим сегментом Thymeleaf:
<!-- обов'язкове поле -->
<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>
На стороні клієнта ми будемо використовувати бібліотеку валідації під назвою [jquery.validate]. Усі атрибути [data-x] призначені саме для неї. Коли валідація на стороні клієнта буде вимкнена, ці атрибути не використовуватимуться. Тому наразі немає сенсу їх розбирати. Можна просто зупинитися на наступному рядку Thymeleaf:
<input type="text" th:field="*{strNotEmpty}" data-val="true" th:attr="data-val-required=#{NotNull}" />
яка генерує такий рядок HTML:
<input type="text" data-val="true" data-val-required="Le champ est obligatoire" id="strNotEmpty" name="strNotEmpty" value="" />
У наведеному вище прикладі виникає складність із генерацією атрибута [data-val-required="Le champ est obligatoire"]. Справа в тому, що значення, пов’язане з цим атрибутом, береться з зовнішніх файлів повідомлень. Тому для його отримання доводиться використовувати вираз Thymeleaf. Це такий вираз: [th:attr="data-val-required=#{NotNull}"]. Цей вираз обчислюється, а його значення вставляється без змін у згенерований тег HTML. Вона називається [th:attr], оскільки використовується для генерації атрибутів, що не є попередньо визначеними в Thymeleaf. Ми зустрічали попередньо визначені атрибути [th:text, th:value, th:class, ...], але атрибута [th:data-val-required] не існує.
6.2.5. Таблиця стилів
Вище наведено класи CSS, такі як [class="field-validation-valid"]. Деякі з цих класів використовуються бібліотекою валідації на JavaScript. Вони визначені у такому файлі: [form01.css]:
![]() |
@CHARSET "UTF-8";
/*власні стилі*/
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;
}
/* Стилі для допоміжних функцій перевірки
-----------------------------------------------------------*/
.field-validation-error {
color: #f00;
}
.field-validation-valid {
display: none;
}
.input-validation-error {
border: 1px solid #f00;
background-color: #fee;
}
.validation-summary-errors {
font-weight: bold;
color: #f00;
}
.validation-summary-valid {
display: none;
}
6.3. Валідація на стороні клієнта
6.3.1. Основи jQuery та JavaScript
Валідація на стороні клієнта здійснюється за допомогою JavaScript. Ми скористаємося фреймворком jQuery, який надає численні функції, що полегшують розробку на JavaScript. Ми розглянемо основи jQuery, які необхідно знати для розуміння скриптів цього та наступних розділів.
Ми створюємо статичний файл HTML [JQuery-01.html], який розміщуємо в папці [static / vues]:
![]() |
Цей файл матиме такий вміст:
<!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>
- рядок 6: імпорт jQuery;
- рядки 10–12: елемент сторінки з ідентифікатором [element1]. Ми попрацюємо з цим елементом.
Нам потрібно завантажити файл [jquery-1.11.1.min.js]. Останню версію jQuery можна знайти в URL [http://jquery.com/download/]:

Завантажений файл слід помістити в папку [static / js]:
![]() |
Після цього відкриваємо статичний вигляд [jQuery-01.html] у браузері Chrome [1-2]:
![]() |
У Google Chrome виконайте [Ctrl-Maj-I], щоб відкрити інструменти розробки [3]. Вкладка [Console] [4] дозволяє виконувати код JavaScript. Нижче наведено команди JavaScript, які слід ввести, та їх пояснення.
JS | результат |
|
: повертає колекцію всіх елементів з ідентифікатором [element1], тобто, як правило, колекцію з 0 або 1 елемента, оскільки на одній сторінці не може бути двох однакових ідентифікаторів HTML. | ![]() |
|
: присвоює текст [blabla] усім елементам колекції. Це призводить до зміни вмісту, що відображається на сторінці | ![]() |
|
приховує елементи колекції. Текст [blabla] більше не відображається. | ![]() |
|
: знову відображає колекцію. Це дозволяє нам побачити, що елемент з ідентифікатором [element1] має атрибут CSS style='display: none;', завдяки якому елемент приховано. | |
|
: відображає елементи колекції. Текст [blabla] з'являється знову. Це забезпечується атрибутом CSS style='display: block;'. | ![]() |
|
: встановлює атрибут для всіх елементів колекції. Атрибутом тут є [style], а його значення — [color: red]. Текст [blabla] стає червоним. | ![]() |
![]() | |
![]() |
Слід зауважити, що значення URL у браузері не змінилося під час усіх цих операцій. Обміну даними з веб-сервером не відбувалося. Усе відбувається всередині браузера. Тепер переглянемо вихідний код сторінки:
<!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>
Це вихідний текст. Він жодним чином не відображає маніпуляції, які ми провели з елементом у рядках 10–12. Про це важливо пам’ятати під час налагодження JavaScript. Тому часто немає сенсу переглядати вихідний код відображеної сторінки.
Ми знаємо достатньо, щоб зрозуміти скрипти jS, які будуть наведені далі.
6.3.2. Бібліотеки валідації jS
Ми будемо використовувати бібліотеки екосистеми jQuery. Навколо jQuery існує низка проєктів, які, у свою чергу, породжують бібліотеки. Ми будемо використовувати бібліотеку валідації [jquery.validate.unobstrusive], створену компанією Microsoft і передану фонду jQuery. Надалі ми будемо називати її бібліотекою валідації MS або, простіше, бібліотекою MS. Щоб її отримати, потрібне середовище Microsoft Visual Studio. Я не бачив, як її можна отримати іншим чином. Можна використовувати безкоштовну версію типу [Visual Studio Community] або [http://www.visualstudio.com/en-us/news/vs2013-community-vs.aspx] (грудень 2014 р.). Читач, який не зацікавлений у виконанні наведених нижче кроків, може завантажити цю бібліотеку та ті, на яких вона базується, у прикладах, розміщених на сайті цього документа.
Створюємо консольний проєкт у Visual Studio [1-4]:
|
![]() |
- у [5] — консольний проєкт;
- у [6-7]: додаємо пакети [NuGet] до проекту. [NuGet] — це функція Visual Studio, що дозволяє завантажувати бібліотеки у форматі DLL, а також бібліотеки jS.
![]() |
- у [9-10] — виконайте пошук за ключовим словом [jQuery];
- у [11-13] завантажте у вказаному порядку бібліотеки jS, необхідні для перевірки на стороні клієнта;
- у [14] також завантажте бібліотеку [Microsoft jQuery Unobtrusive Ajax], яку ми незабаром будемо використовувати;
![]() |
- у [15-16] виконайте пошук пакетів за ключовим словом [globalize];
- щодо [17], завантажте бібліотеку [jQuery.Validation.Globalize];
![]() |
Ці різні завантаження встановили певну кількість бібліотек jS у папку [Scripts] проєкту [18]. Не всі вони є корисними. Кожен файл існує у двох екземплярах:
- [js] — читабельна версія бібліотеки;
- [min.js] — нечитабельна, так звана мініфікована версія бібліотеки. Вона не є справді нечитабельною. Це текст. Але її неможливо зрозуміти. Саме цю версію слід використовувати у виробничому середовищі, оскільки цей файл менший за відповідну версію [js] і, отже, прискорює обмін даними між клієнтом і сервером;
Версії [min.map] не є обов’язковими. У папці [cultures] можна залишити лише ті культури, які підтримуються додатком.
За допомогою Провідника Windows скопіюйте ці файли в папку [static / js / jquery] проекту [springmvc-validation-client] і залиште лише потрібні файли [20]:
![]() |
У [21] залишаємо лише дві локалізації:
- [fr-FR]: французька (Франція);
- [en-US]: англійська мова з USA;
6.3.3. Імпорт бібліотек jS для перевірки
Щоб ці бібліотеки можна було використовувати, їх потрібно імпортувати за допомогою подання [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>
- рядок 11: імпорт файлу jS, про який ми ще не згадували;
- рядки 13–18: скрипт jS, що інтерпретується Thymelaf. Він керує локалізацією на стороні клієнта;
6.3.4. Управління локалізацією на стороні клієнта
Локалізація на стороні клієнта здійснюється за допомогою наступного скрипта jS:
<script th:inline="javascript">
/*<![CDATA[*/
var culture = [[${locale}]];
Globalize.culture(culture);
/*]]>*/
</script>
- рядки 3–4: код jS, у якому міститься вираз Thymeleaf [[${locale}]]. Зверніть увагу на особливий синтаксис цього виразу. Це пов’язано з тим, що він написаний на JavaScript. Вираз [[${locale}]] буде замінено значенням ключа [locale] з моделі подання;
Результат у потоці HTML, згенерованому з цих рядків, є таким:
<script>
/*<![CDATA[*/
var culture = 'en-US';
Globalize.culture(culture);
/*]]>*/
</script>
Рядки 3–4 визначають мовну локалізацію на стороні клієнта. Ми обробляємо лише дві з них: [fr-FR] та [en-US]. Саме тому ми імпортували лише два файли мовних локалізацій:
<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>
Мова, яку слід використовувати на стороні клієнта, визначається на стороні сервера. Повернемося до коду на стороні сервера:
@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";
}
// підготовка шаблону для подання vue-01
private void setModel(Form01 formulaire, Model model, Locale locale, String message) {
// підтримуються лише локалі 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));
...
}
- рядок 20: локаль [fr-FR] або [en-US] вставляється в шаблон подання [vue-01.xml] (рядок 4). Слід звернути увагу на джерело ускладнень. Хоча французька локаль на стороні клієнта позначається як [fr-FR], на стороні сервера вона позначається як [fr_FR]. Саме тому в рядках 14 і 18 вона зберігається у такому вигляді в об’єкті [Form01 formulaire], який приймає відправлені значення;
Слід звернути увагу на наступний важливий момент. Скрипт
<script>
/*<![CDATA[*/
var culture = 'en-US';
Globalize.culture(culture);
/*]]>*/
</script>
змінює культуру клієнта на основі локалі, переданої сервером. Це не призводить до інтернаціоналізації повідомлень, що відображаються на сторінці. Це лише змінює спосіб інтерпретації певної інформації, яка залежить від культури країни. У культурі [fr_FR] число [12,78] є допустимим, тоді як у культурі [en-US] воно є недопустимим. У цьому випадку слід написати [12.78]. Так само дата [12/01/2014] є дійсною в культурі [fr-FR], тоді як у культурі [en-US] слід вказувати [01/12/2014]. Файли з папки [jquery / globalize] вирішують подібні проблеми:
![]() |
Інтернаціоналізація повідомлень про помилки здійснюється виключно на стороні сервера. Ми побачимо, що сторінка HTML / jS містить повідомлення про помилки, що відповідають локалі, яку підтримує сервер: французькою мовою для локалі [fr_FR] та англійською мовою для локалі [en_US].
6.3.5. Файли повідомлень
Вигляд [vue-01.xml] використовує такі інтернаціоналізовані повідомлення:
![]() |
[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
Файл [messages.properties] є копією файлу з англійськими повідомленнями. У підсумку, будь-яка локалізація, відмінна від [fr], використовуватиме англійські повідомлення. Нагадаємо, що файл [messages_fr.properties] використовується для всіх локалізацій [fr_XX], таких як [fr_CA] або [fr_FR].
Вигляд [vue-01.xml] використовує ключі цих повідомлень. Якщо читач бажає дізнатися значення, пов’язані з цими ключами, йому слід повернутися до цього абзацу, щоб їх дізнатися.
6.3.6. Зміна локалі
Вигляд [vue-01.xml] містить чотири посилання:
<body>
<!-- заголовок -->
<h3>
<span th:text="#{form01.title}"></span>
<span th:text="${locale}"></span>
</h3>
<!-- меню -->
<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>
<!-- форма -->
<form action="/someURL" th:action="@{/js02.html}" method="post" th:object="${form01}" name="form" id="form">
...
деякі з яких наведено нижче [1]:
![]() |
Розглянемо два посилання, які дозволяють змінити мову на французьку або англійську:
<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>
Клік на ці посилання запускає виконання скрипта jS, що міститься у файлі [local.js] [2]. В обох випадках викликається функція jS [setLocale]:
// локаль
function setLocale(locale) {
// оновлюємо локаль
lang.val(locale);
// відправляємо форму — це не запускає валідатори на стороні клієнта — тому ми не вимкнули валідацію на стороні клієнта
document.form.submit();
}
Для розуміння рядка 4 необхідне вступне пояснення. Вигляд [vue-01.xml] містить приховане поле з назвою [lang]:
<input type="hidden" th:field="*{lang}" th:value="*{lang}" value="true" />
яке відповідає полю [lang] у [Form01]:
// локаль
private String lang;
Приховані поля зручні, коли потрібно доповнити відправлені значення. За допомогою JavaScript можна присвоїти їм значення, і це значення відправляється як звичайний запис, зроблений користувачем. Код HTML, згенерований Thymeleaf, виглядає так:
<input type="hidden" value="en_US" id="lang" name="lang" />
Значення параметра [value] відповідає значенню поля [Form01.lang] на момент генерації HTML. Важливо звернути увагу на ідентифікатор jS вузла [id="lang"]. Цей ідентифікатор використовується наступною функцією []:
// глобальні змінні
var lang;
// документ готовий
$(document).ready(function() {
// глобальні посилання
lang = $("#lang");
});
// локальні
function setLocale(locale) {
// оновлюємо локаль
lang.val(locale);
// відправляємо форму — з невідомої причини це не запускає валідатори на стороні клієнта
// тому перевірку не було вимкнено
document.form.submit();
}
- рядки 5–8: функція jS [$(document).ready(f)] — це функція, яка виконується, коли браузер завантажив весь документ, надісланий сервером. Її параметром є функція. Функцію jS [$(document).ready(f)] використовують для ініціалізації середовища jS завантаженого документа;
- рядок 7: вираз [$("#lang")] є виразом jQuery. Його значенням є посилання на вузол DOM з атрибутом [id='lang'];
- рядок 2: змінні, оголошені поза функцією, є глобальними для функцій. У даному випадку це означає, що змінна [lang], ініціалізована в [$(document).ready()], також доступна у функції [setLocale] у рядку 11;
- рядок 13: змінює атрибут [value] вузла, ідентифікованого за [lang]. Якщо значення lang дорівнює [xx_XX], то тег HTML вузла набуває такого вигляду:
<input type="hidden" value="xx_XX" id="lang" name="lang" />
JavaScript дозволяє змінювати значення елементів DOM (Document Object Model).
- рядок 16: [document] позначає DOM. [document.form] позначає першу форму, знайдену в цьому документі. Документ HTML може містити кілька тегів <form> і, отже, кілька форм. У даному випадку ми маємо лише одну. [document.form.submit] відправляє цю форму так, ніби користувач натиснув кнопку з атрибутом [type='submit']. До якої дії надсилаються значення форми? Щоб це з’ясувати, потрібно переглянути тег [form] форми в [vue-01.xml]:
<!-- форма -->
<form action="/someURL" th:action="@{/js02.html}" method="post" th:object="${form01}" name="form" id="form">
Дія, яка отримає відправлені значення, — це та, що позначена атрибутом [th:action]. Отже, це буде дія [/js02.html]. Нагадаємо, що в цій назві суфікс [.html] буде видалено, і в підсумку буде виконано дію [/js02]. Важливо зрозуміти, що нове значення [xx_XX] вузла [lang] буде відправлено у формі [lang=xx_XX]. Однак ми налаштували наш додаток так, щоб він перехоплював параметр [lang] і інтерпретував його як зміну локалі. Отже, на стороні сервера локаль стане [xx_XX]. Розглянемо дію [/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";
}
}
// підготовка шаблону подання vue-01
private void setModel(Form01 formulaire, Model model, Locale locale, String message) {
// підтримуються лише локалі 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));
...
}
- рядок 2: дія [/js02] отримає нову локаль [xx_XX], інкапсульовану в параметрі [Locale locale]:
- рядки 5–12: якщо деякі з переданих значень є недійсними, буде відображено вікно [vue-01.xml] із повідомленнями про помилки, що використовують нову локаль [xx_XX]. Крім того, рядок 11 забезпечує вставку змінної [locale=xx-XX] у шаблон. На стороні клієнта це значення буде використано для оновлення локалі на стороні клієнта. Ми вже описали цей процес;
- рядки 14–15: якщо всі надіслані значення є дійсними, відбувається перенаправлення до наступної дії [/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";
}
- у рядку 2 вводиться нова локаль [xx_XX];
- рядок 3: метод [setModel] встановить локаль клієнта на [xx-XX];
Тепер розглянемо вплив локалі на вигляд [vue-01.xml]. Наразі ми не навели його повністю, оскільки він налічує понад 300 рядків. Проте більшість рядків складається з повторення послідовності, подібної до такої:
<!-- обов’язкове -->
<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>
Цей код відображає такий фрагмент [1]:
![]() |
Повідомлення про помилку [2] походить від атрибута [th:attr="data-val-required=#{NotNull}"] у рядку 5. [#{NotNull}] — це локалізоване повідомлення. Залежно від локалі на стороні сервера рядок 5 генерує тег:
<input type="text" data-val="true" data-val-required="Field is required" id="strNotEmpty" name="strNotEmpty" />
або тег:
<input type="text" data-val="true" data-val-required="Le champ est obligatoire" id="strNotEmpty" name="strNotEmpty" />
Атрибути [data-x] використовуються бібліотекою перевірки jS.
У підсумку слід зазначити, що обидва посилання для зміни локалі:
- призводять до POST введених значень;
- змінюють локаль як на стороні сервера, так і на стороні клієнта;
- генерують сторінку HTML, яка містить повідомлення про помилки, призначені для бібліотеки валідації jS, і що ці повідомлення виводяться мовою обраної локалі;
6.3.7. POST введених значень
Розглянемо кнопку [Valider], яка відправляє введені значення з подання [vue-01.xml]. Її код HTML такий:
<!-- кнопка підтвердження -->
<input type="submit" value="Valider" onclick="javascript:postForm01()" />
Якщо в браузері увімкнено JavaScript, натискання на кнопку запустить виконання методу [postForm01]. Якщо ця функція поверне логічне значення [False], то submit не буде виконано. Якщо вона поверне інше значення, то воно буде виконано. Ця функція знаходиться у файлі [local.js]:
![]() |
Вона імпортується в представлення [vue-01.xml] у рядку 6 нижче:
<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>
У цьому файлі міститься такий код:
// глобальні змінні
var formulaire;
var clientValidation;
var double1;
var double2;
var double3;
...
$(document).ready(function() {
// глобальні посилання
formulaire = $("#form");
clientValidation = $("#clientValidation");
double1 = $("#double1");
double2 = $("#double2");
double3 = $("#double3");
...
});
....
// відправлення форми
function postForm01() {
...
}
- рядки 8–16: функція jS [$(document).ready(f)] — це функція, яка виконується, коли браузер завантажив весь документ, надісланий сервером. Її параметром є функція. Функцію jS [$(document).ready(f)] використовують для ініціалізації середовища jS завантаженого документа;
- рядки 10–14: щоб зрозуміти ці рядки, потрібно одночасно переглянути код Thymeleaf та згенерований код HTML;
Відповідний код Thymeleaf виглядає так:
<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" />
який генерує такий код 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" />
Кожен атрибут [th:field='x'] генерує два атрибути: HTML, [name='x'] та [id='x']. Атрибут [name] — це назва відправлених значень. Таким чином, наявність атрибутів [name='x'] та [value='y'] для тегу HTML <input type='text'> призведе до включення рядка x=y у відправлені значення name1=val1&name2=val2&... Атрибут [id='x'] використовується JavaScript. Він слугує для ідентифікації елемента DOM (Document Object Model). Завантажений документ HTML фактично перетворюється на дерево JavaScript, яке називається DOM, де кожен вузол ідентифікується за своїм атрибутом [id].
Повернемося до коду функції [$(document).ready()]:
// глобальні змінні
var formulaire;
var clientValidation;
var double1;
var double2;
var double3;
...
$(document).ready(function() {
// глобальні посилання
formulaire = $("#form");
clientValidation = $("#clientValidation");
double1 = $("#double1");
double2 = $("#double2");
double3 = $("#double3");
...
});
....
// відправлення форми
function postForm01() {
...
}
- рядок 10: вираз [$("#form")] є виразом jQuery. Його значенням є посилання на вузол DOM з атрибутом [id='form '];
- рядки 10–14: отримуються посилання на п’ять вузлів DOM;
- рядки 2–6: змінні, оголошені поза функцією, є глобальними для функцій. У даному випадку це означає, що змінні [formulaire, clientValidation , double1, double2, double3], ініціалізовані в [$(document).ready()], будуть доступні також у функції [postForm01] у рядку 19;
Тепер розглянемо функцію [postForm01]:
// відправлення форми
function postForm01() {
// режим перевірки на стороні клієнта
var validationActive = clientValidation.val() === "true";
if (validationActive) {
// очищення помилок сервера
clearServerErrors();
// перевірка форми
if (!formulaire.validate().form()) {
// відсутність відправки
return false;
}
}
// числа у англосаксонському форматі
var value1 = double1.val().replace(",", ".");
double1.val(value1);
var value2 = double2.val().replace(",", ".");
double2.val(value2);
var value3 = double3.val().replace(",", ".");
double3.val(value3);
// дозволяється відправлення
return true;
}
Нагадаємо, що ця функція jS виконується перед функцією [submit] форми. Якщо вона повертає логічне значення [false] (рядок 11), то відправка не відбудеться. Якщо вона повертає інше значення (рядок 22), то відправка відбудеться.
- Важливий код міститься у рядках 4–12;
- рядок 4: отримуємо значення прихованого поля [clientValidation]. Це значення дорівнює «true», якщо потрібно увімкнути перевірку на стороні клієнта, і «false» в іншому випадку;
- рядок 6: у разі клієнтської валідації ми очищаємо повідомлення про помилки сервера, які можуть бути присутніми через те, що користувач щойно змінив локаль;
- рядок 9: нагадаємо, що змінна [formulaire] представляє вузол тегу HTML <form>, тобто форму. Ця форма містить валідатори jS, які ми ще не розглядали і які будуть предметом наступних параграфів. Вираз [formulaire.validate().form()] примусово запускає виконання всіх валідаторів jS, присутніх у формі. Його значення дорівнює [true], якщо всі перевірені значення є дійсними, і [false] в іншому випадку;
- рядок 11: повертається значення [false], якщо хоча б одне з перевірених значень є недійсним. Це запобіжить передачі [submit] з форми на сервер;
- рядки 15–20: ідентифікатори [double1, double2, double3] представляють три дійсні числа з форми. Залежно від локалі, введене значення відрізняється. Для локалі [fr-FR] вводиться [10,37], тоді як для локалі [en-US] — [10.37]. Це стосується введення даних. У випадку з культурою [fr-FR] значення, відправлене для [double1], буде виглядати як [double1=10,37]. Потрапивши на сервер, значення [10,37] буде відхилено, оскільки сервер очікує [10.37] — формат дійсних чисел за замовчуванням у Java. Тому в рядках 15–20 у введеному значенні для цих чисел кома замінюється на крапку;
- рядок 15: вираз [double1.val()] повертає рядок символів, введений для вузла [double1]. Вираз [double1.val().replace(",", ".")] замінює в цьому рядку коми на крапки. Результатом є рядок [value1];
- рядок 16: інструкція [double1.val(value1)] присвоює це значення [value1] вузлу [double1].
Технічно, якщо користувач ввів [10,37] замість фактичного значення [double1], то після виконання попередніх інструкцій вузол [double1] матиме значення [10.37], а значення, яке буде відправлено, — [param1=val1&double1=10.37¶m2=val2], яке буде прийнято сервером;
- рядок 22: присвоюємо значення [true], щоб виконати [submit] з форми;
Слід зауважити, що функція jS [postForm01]:
- виконує всі валідатори jS у формі, якщо валідація на стороні клієнта увімкнена, і запобігає передачі [submit] з форми на сервер, якщо одне з введених значень було визнано недійсним;
- дозволяє виконати [submit] або тому, що перевірка на стороні клієнта не ввімкнена, або тому, що вона ввімкнена, але всі введені значення є дійсними;
Залишається інструкція у рядку [3]:
// видаляємо помилки сервера
clearServerErrors();
Функція [clearServerErrors] призначена для видалення повідомлень, що містяться у стовпці 4 подання [vue-01.xml]:
![]() |
На наведеному вище знімку екрана було натиснуто на посилання [English]. Ми побачили, що це призвело до POST введених значень без спрацьовування валідаторів jS. Після повернення результату стовпець заповнюється можливими повідомленнями про помилки. Якщо тепер натиснути кнопку [Validate] [2] при увімкнених валідаторах jS [3], то стовпець [Client Validation] [4] заповниться повідомленнями. Якщо нічого не робити, ті, що були присутні у стовпці [Server Validation], залишаться там, що створить плутанину, оскільки у разі виявлення помилок валідаторами jS сервер не залучається. Щоб уникнути цього, у функції [postForm01] очищається стовпець [Server Validation]. Цю роботу виконує функція []:
function clearServerErrors() {
// видаляються повідомлення про помилки сервера
$(".error").each(function(index) {
$(this).text("");
});
}
Особливістю повідомлень про помилки є те, що всі вони мають клас [error]. Наприклад, для першого рядка таблиці у [vue-01.html]:
<span th:if="${#fields.hasErrors('strNotEmpty')}" th:errors="*{strNotEmpty}" class="error">Donnée erronée</span>
І це єдині вузли DOM, що мають цей клас. Ми використовуємо цю властивість у функції [clearServerErrors]:
function clearServerErrors() {
// видалити повідомлення про помилки з сервера
$(".error").each(function(index) {
$(this).text("");
});
}
- рядок 3: вираз [$(".error")] повертає колекцію вузлів DOM, що мають клас [error];
- рядок 3: вираз [$(".error").each(function(index){f}] викликає функцію [f] для кожного з вузлів колекції. Функція отримує параметр [index], який тут не використовується і який є номером вузла в колекції;
- рядок 4: вираз [$(this)] позначає поточний вузол в ітерації. Це тег HTML <span>. Вираз [$(this).text("")] присвоює порожній рядок тексту, що відображається тегом <span>;
Тепер розглянемо різні валідатори jS.
6.3.8. Валідатор [required]
Розглянемо перший елемент форми:
![]() |
Рядок [1] генерується наступною послідовністю з подання [vue-01.xml]:
<!-- обов’язкове -->
<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>
Ці рядки стосуються поля [strNotEmpty] у формі [Form01]:
@NotNull
@NotBlank
private String strNotEmpty;
Обмеження [1-2] передбачають, що поле [strNotEmpty] має бути існуючим рядком [NotNull], не може бути порожнім і не може складатися виключно з пробілів [NotBlank]. Ми хочемо відтворити це обмеження на стороні клієнта за допомогою JavaScript.
Розглянемо рядки 5 і 8. Рядок 11 не викликає проблем. Він відображає повідомлення про помилку, пов’язане з полем [strNotEmpty]. Почнемо з рядка 5:
<input type="text" th:field="*{strNotEmpty}" data-val="true" th:attr="data-val-required=#{NotNull}" />
На основі цього коду Thymeleaf згенерує такий тег:
<input type="text" data-val="true" data-val-required="Field is required" id="strNotEmpty" name="strNotEmpty" value="x" />
- атрибут [data-val='true'] використовується бібліотеками валідації jQuery. Його наявність вказує на те, що значення вузла підлягає валідації;
- атрибут [data-val-X='msg'] надає дві інформації. [X] — це назва валідатора, а [msg] — повідомлення про помилку, пов’язане з недійсним значенням вузла, на якому працює валідатор. Це лише інформація. Вона не призводить до відображення повідомлення про помилку;
- [required] — це валідатор, який розпізнається бібліотекою валідації [jquery.validate.unobstrusive] від Microsoft. Його не потрібно визначати. Далі це не завжди буде так;
- теги [data-x] ігноруються HTML5. Вони корисні лише в тому випадку, якщо є JavaScript для їх використання;
Тепер розглянемо рядок 8:
<span class="field-validation-valid" data-valmsg-for="strNotEmpty" data-valmsg-replace="true"></span>
Вона слугує для відображення повідомлення про помилку валідатора [required]. Якщо трапиться помилка, бібліотека валідації jS динамічно замінить рядок HTML у масиві на такий код:
<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>
- рядок 4: клас вузла [strNotEmpty] змінився. Він став [input-validation-error], через що поле з помилкою зафарбовується червоним кольором;
- рядок 7: клас вузла [span] змінився. Він став [field-validation-error], що призведе до відображення тексту вузла [span] червоним кольором;
- рядок 8: [span], який раніше був порожнім, тепер містить текст [Le champ est obligatoire]. Цей текст походить із тегу [data-val-required="Le champ est obligatoire"] у рядку 4;
- рядок 7: щоб відобразити повідомлення про помилку вузла [strNotEmpty] з рядка 4, у рядку 7 слід використовувати атрибути [data-valmsg-for="strNotEmpty"] та [data-valmsg-replace="true"];
6.3.9. Валідатор [assertfalse]
![]() |
Рядок [1] генерується наступною послідовністю з подання [vue-01.xml]:
<!-- обов'язкове, 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>
Ці рядки стосуються поля [assertFalse] у формі [Form01]:
@NotNull
@AssertFalse
private Boolean assertFalse;
Ми хочемо відтворити це обмеження на стороні клієнта за допомогою JavaScript. Рядки 12–17 тепер є стандартними:
- рядки 12–14: у разі помилки в полі [assertFalse] відображають повідомлення, що міститься в атрибуті [data-val-assertfalse] рядка 6, або те, що міститься в атрибуті [data-val-required] того самого рядка. Нагадаємо, що ці повідомлення локалізовані, тобто відображаються мовою, яку раніше обрав користувач, або французькою, якщо він не зробив вибору;
- рядки 5–10: відображають перемикачі з JavaScript-валідаторами, які спрацьовують, щойно користувач натискає на один із них.
Обидві кнопки побудовані однаково. Розглянемо першу:
<input type="radio" th:field="*{assertFalse}" value="true" data-val="true" th:attr="data-val-required=#{NotNull},data-val-assertfalse=#{AssertFalse}" />
Після обробки Thymeleaf цей рядок перетворюється на такий:
<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" />
Ми маємо валідатори [data-val="true"]. Їх два. Один валідатор має назву [required] [data-val-required="Le champ est obligatoire"], а інший — [assertfalse] [data-val-assertfalse="Seule la valeur False est acceptée"]. Нагадаємо, що значення атрибута [data-val-X] — це повідомлення про помилку валідатора X.
Ми вже розглядали валідатор [required]. Новиною тут є те, що до введеного значення можна прив’язати кілька валідаторів. Якщо валідатор [required] відомий бібліотеці валідації MS (Microsoft), то валідатор [assertFalse] — ні. Отже, ми навчимося створювати новий валідатор. Ми створимо кілька таких валідаторів, і вони будуть розміщені у файлі [client-validation.js]:
![]() |
Цей файл, як і інші, імпортується за допомогою подання [vue-01.xml] (рядок 6 нижче):
<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>
Додавання валідатора [assertfalse] зводиться до створення двох таких функцій 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("''", "'");
});
Чесно кажучи, я не є фахівцем з JavaScript — мови, яка для мене досі залишається загадкою. Її основи прості, але бібліотеки, побудовані на цих основах, часто є дуже складними. Щоб написати наведені вище рядки коду, я скористався прикладами, знайденими в Інтернеті. Саме посилання [http://jsfiddle.net/LDDrk/] вказало мені шлях, яким слід йти. Якщо воно ще існує, читачеві варто ознайомитися з ним, оскільки воно містить повний опис із робочим прикладом. У ньому показано, як створити новий валідатор, і саме це дозволило мені створити всі валідатори, описані в цьому розділі. Повернемося до коду:
- рядки 2–4: визначають новий валідатор. Функція [$.validator.addMethod] очікує як перший параметр — ім’я валідатора, а як другий — функцію, що його визначає;
- рядок 2: функція має три параметри:
- [value]: значення, яке потрібно перевірити. Функція повинна повертати [true], якщо значення є дійсним, і [false] в іншому випадку,
- [element]: елемент HTML, до якого належить значення, що підлягає перевірці,
- [param]: об’єкт, що містить значення, пов’язані з параметрами валідатора. Ми ще не вводили це поняття. У даному випадку валідатор [assertFalse] не має параметрів. Можна визначити, чи є значення [value] дійсним, без додаткової інформації. Інша справа, якби потрібно було перевірити, чи значення [value] є дійсним числом у проміжку [min, max]. У цьому випадку нам потрібно було б знати значення [min] та [max]. Ці два значення називаються параметрами валідатора;
- рядки 6–9: функція, необхідна для бібліотеки валідації MS. Функція [$.validator.unobtrusive.adapters.add] очікує як перший параметр — ім’я валідатора, як другий — масив параметрів валідатора, як третій — функцію;
- валідатор [assertFalse] не має параметрів. Тому другим параметром є порожній масив;
- функція має лише один параметр — об’єкт [options], який містить інформацію про елемент, що підлягає перевірці, і для якого потрібно визначити дві нові властивості [rules] та [messages];
- рядок 7: визначаються правила [rules] для валідатора [assertFalse]. Ці правила є параметрами валідатора [assertFalse], такими самими, як і параметр [param] у рядку 2. Ці параметри містяться в [options.params];
- рядок 8: визначають повідомлення про помилку валідатора [assertFalse]. Воно міститься у [options.message]. З повідомленнями про помилки виникає така складність. У файлах повідомлень можна знайти таке повідомлення:
Range.form01.int1014=La valeur doit être dans l''intervalle [10,14]
Подвійний апостроф необхідний для Thymeleaf. Він інтерпретує його як одинарний апостроф. Якщо поставити одинарний апостроф, Thymeleaf його не відобразить. Тепер ці повідомлення також слугуватимуть повідомленнями про помилки для бібліотеки валідації MS. Однак JavaScript відображатиме обидва апострофи. У рядку 8 ми замінюємо подвійний апостроф у повідомленні про помилку на одинарний.
Щоб трохи зрозуміти, що відбувається, можемо додати код jS для логування:
// журнали
var logs = {
assertfalse : true
}
// -------------- assertfalse
$.validator.addMethod("assertfalse", function(value, element, param) {
// журнали
if (logs.assertfalse) {
console.log(jSON.stringify({
"[assertfalse] value" : value
}));
console.log("[assertfalse] element");
console.log(element);
console.log(jSON.stringify({
"[assertfalse] param" : param
}));
}
// перевірка правильності
return value === "false";
});
$.validator.unobtrusive.adapters.add("assertfalse", [], function(options) {
// журнали
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
}));
}
// код
options.rules["assertfalse"] = options.params;
options.messages["assertfalse"] = options.message.replace("''", "'");
});
Цей код використовує бібліотеку jSON JSON3 [http://bestiejs.github.io/json3/]. Якщо увімкнути логи (рядок 3), у консолі з’являться такі виводи:
Під час початкового завантаження сторінки з’являються такі записи в журналі:
Функція jS [$.validator.unobtrusive.adapters.add] була виконана. З цього можна дізнатися наступне:
- [options.params] — це порожній об’єкт, оскільки валідатор [assertFalse] не має параметрів;
- [options.message] — це повідомлення про помилку, яке було сформовано для валідатора [assertFalse] в атрибуті [data-val-assertFalse];
- [options.messages] — це об’єкт, що містить інші повідомлення про помилки валідованого елемента. Тут ми бачимо повідомлення про помилку, яке ми вказали в атрибуті [data-val-required];
Тепер вкажімо неправильне значення у полі [assertFalse] і виконаємо перевірку:
У результаті ми отримаємо такі записи в журналі:
![]() |
У них можна побачити наступне:
- перевіреним значенням є [true] (рядок 118);
- тестований елемент HTML — це перемикач з ідентифікатором [assertFalse1] (рядок 122);
- валідатор [assertFalse] не має параметрів (рядок 123);
Ось і все. Що з цього слід винести?
Для валідатора X jS ми повинні визначити:
- у тезі HTML, що підлягає перевірці, атрибут [data-val-X='msg'], який визначає як валідатор X, так і його повідомлення про помилку;
- дві функції jS, які слід розмістити у файлі [client-validation.js]:
- [$.validator.addMethod("X", function(value, element, param)],
- [$.validator.unobtrusive.adapters.add("X", [param1, param2], function(options)] ;
Далі ми будемо спиратися на те, що вже було зроблено для цього першого валідатора, і просто представимо нові елементи.
6.3.10. Валідатор [asserttrue]
Цей валідатор, звісно, аналогічний валідатору [assertFalse].
![]() |
Рядок [1] генерується наступною послідовністю з подання [vue-01.xml]:
<!-- обов’язкове, 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>
Ці рядки стосуються поля [assertTrue] у формі [Form01]:
@NotNull
@AssertTrue
private Boolean assertTrue;
У рядках 1–16 немає нічого нового. Вони використовують валідатор [asserrtrue], який потрібно визначити у файлі [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. Валідатори [date] та [past]
![]() |
Рядок [1] генерується наступною послідовністю з подання [vue-01.xml]:
<!-- обов'язкове, дата, минуле -->
<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>
Ці рядки стосуються поля [dateInPast] у формі [Form01]:
@NotNull
@Past
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date dateInPast;
Рядок з валідаторами дати виглядає так:
<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}" />
У ній містяться три валідатори [data-val-X]: required, date, past. Нам потрібно визначити в [client-validation.js] функції, пов’язані з цими двома новими валідаторами:
logs.date = true;
// -------------- дата
$.validator.addMethod("date", function(value, element, param) {
// термін дії
var valide = Globalize.parseDate(value, "yyyy-MM-dd") != null;
// журнали
if (logs.date) {
console.log(jSON.stringify({
"[date] value" : value,
"[date] valide" : valide
}));
}
// результат
return valide;
});
$.validator.unobtrusive.adapters.add("date", [], function(options) {
options.rules["date"] = options.params;
options.messages["date"] = options.message.replace("''", "'");
});
та
logs.past = true;
// -------------- минуле
$.validator.addMethod("past", function(value, element, param) {
// термін дії
var valide = value <= new Date().toISOString().substring(0, 10);
// журнали
if (logs.past) {
console.log(jSON.stringify({
"[past] value" : value,
"[past] valide" : valide
}));
}
// результат
return valide;
});
$.validator.unobtrusive.adapters.add("past", [], function(options) {
options.rules["past"] = options.params;
options.messages["past"] = options.message.replace("''", "'");
});
Перш ніж пояснювати код, давайте подивимося на логи, коли вводиться дата, пізніша за сьогоднішню:
Перше, на що слід звернути увагу, — це те, що дата, яку потрібно перевірити, надходить у вигляді символьного рядка у форматі [aaaa-mm-jj]. Це пояснює наступні рядки:
var valide = Globalize.parseDate(value, "yyyy-MM-dd") != null;
Бібліотека [globalize.js] викликає наведену вище функцію [Globalize.parseDate]. Перший параметр — це дата у вигляді символьного рядка, а другий — її формат. Результатом є покажчик null, якщо дата недійсна, або сама дата в іншому випадку.
Правильність роботи валідатора [past] перевіряється за допомогою такого коду:
var valide = value <= new Date().toISOString().substring(0, 10);
Ось результат обчислення виразу [new Date().toISOString().substring(0, 10)] у консолі:
![]() |
Щоб бути дійсним, рядок [value] повинен йти в алфавітному порядку перед рядком [new Date().toISOString().substring(0, 10)].
Слід зауважити, що використовувана версія Chrome надає дату у форматі [yyyy-mm-dd]. У браузері, де це не так, користувачеві слід явно вказати, що потрібно використовувати саме цей формат введення.
6.3.12. Валідатор [future]
![]() |
Рядок [1] генерується наступною послідовністю з подання [vue-01.xml]:
<!-- необхідний, дата, майбутнє -->
<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>
Ці рядки стосуються поля [dateInFuture] у формі [Form01]:
@NotNull
@Future
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date dateInFuture;
- у рядку 5 з’являється новий валідатор [data-val-future];
Цей валідатор, звісно, дуже схожий на валідатор [past]. Дві функції, які потрібно додати до [client-validation.js], такі:
// -------------- майбутнє
$.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. Валідатори [int] та [max]
![]() |
Рядок [1] генерується наступною послідовністю з подання [vue-01.xml]:
<!-- обов'язкове, ціле, 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>
Ці рядки стосуються поля [intMax100] у формі [Form01]:
@NotNull
@Max(value = 100)
private Integer intMax100;
У рядку 5 з’явилися два нових валідатори: [int] та [max]. Останній має один параметр: значення максимального значення. Розглянемо код HTML, згенерований рядком 5:
<!-- обов’язкове, ціле, 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>
Нагадаємо значення різних атрибутів [data-X]:
- [data-val="true"] вказує, що до елемента HTML прив’язані валідатори;
- [data-val-required] вводить валідатор [required] разом із його повідомленням;
- [data-val-int] вводить валідатор [int] разом із його повідомленням;
- [data-val-max] вводить валідатор [max] разом із його повідомленням;
- [data-val-max-value="100"] вводить параметр з іменем [value] для валідатора [max]. [100] є значенням цього параметра. Це перший випадок, коли ми стикаємося з поняттям параметрів валідатора.
Файл [client-validation.js] доповнено наступним валідатором [int]:
logs.int = true;
// -------------- ціле число
$.validator.addMethod("int", function(value, element, param) {
// термін дії
valide = /^\s*[-\+]?\s*\d+\s*$/.test(value);
// журнали
if (logs.int) {
console.log(jSON.stringify({
"[int] value" : value,
"[int] valide" : valide,
}));
}
// результат
return valide;
});
$.validator.unobtrusive.adapters.add("int", [], function(options) {
options.rules["int"] = options.params;
options.messages["int"] = options.message.replace("''", "'");
});
- рядок 5: використовується регулярний вираз для перевірки того, що рядок [value] дійсно представляє ціле число. Воно може бути зі знаком;
Ось кілька прикладів записів у журналі:
Валідатор [max] додається до [client-validation.js] наступним чином
// -------------- max для використання разом із [int] або [number]
logs.max = true;
$.validator.addMethod("max", function(value, element, param) {
// журнали
if (logs.max) {
console.log(jSON.stringify({
"[max] value" : value,
"[max] param" : param
}));
}
// термін дії
var val = Globalize.parseFloat(value);
if (isNaN(val)) {
// журнали
if (logs.max) {
console.log(jSON.stringify({
"[max] valide" : true
}));
}
// результат
return true;
}
var max = Globalize.parseFloat(param.value);
var valide = val <= max;
// журнали
if (logs.max) {
console.log(jSON.stringify({
"[max] valide" : valide
}));
}
// результат
return valide;
});
$.validator.unobtrusive.adapters.add("max", [ "value" ], function(options) {
options.rules["max"] = options.params;
options.messages["max"] = options.message.replace("''", "'");
});
Зараз розглянемо випадок параметра [value] валідатора [max], який вводиться атрибутом [data-val-max-value="100"].
- у рядку 35 параметр [value] включено до другого параметра функції [$.validator.unobtrusive.adapters.add];
- у рядку 3 об’єкт [param] більше не буде порожнім, а міститиме {"value":100};
Щоб зрозуміти код у рядках 3–33, потрібно знати, що коли на одному елементі HTML є кілька валідаторів:
- порядок їх виконання невідомий;
- виконання валідаторів припиняється, щойно один із них визнає елемент недійсним. У цьому випадку саме повідомлення про помилку цього валідатора асоціюється з недійсним елементом;
Розглянемо код:
- рядок 12: перевіряється наявність числа. Якщо валідатор [int] було виконано раніше, ніж валідатор [max], це обов’язково так, оскільки недійсне значення зупиняє виконання валідаторів;
- рядки 13–22: якщо число відсутнє, це означає, що валідатор [int] ще не було виконано. Тоді вказується, що перевірене значення є дійсним, щоб дозволити валідатору [int] виконати свою роботу та визнати елемент недійсним із власним повідомленням про помилку;
- рядки 23–24: обчислює правильність [value];
Ось кілька записів журналу:
Введене значення | журнали |
| |
| |
|
6.3.14. Валідатор [min]
![]() |
Рядок [1] генерується наступною послідовністю з подання [vue-01.xml]:
<!-- обов’язкове, ціле, 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>
Ці рядки стосуються поля [intMin10] у формі [Form01]:
@NotNull
@Min(value = 10)
private Integer intMin10;
У рядку 5 додається новий валідатор [min] [data-val-int=#{typeMismatch}] з параметром [value] [data-val-min-value=#{form01.intMin10.value}"]. Це аналогічний випадок до валідатора [max]. У [client-validation.js] додаємо такий код:
logs.min = true;
//-------------- min слід використовувати разом із [int] або [number]
$.validator.addMethod("min", function(value, element, param) {
// журнали
if (logs.min) {
console.log(jSON.stringify({
"[min] value" : value,
"[min] param" : param
}));
}
// термін дії
var val = Globalize.parseFloat(value);
if (isNaN(val)) {
// журнали
if (logs.min) {
console.log(jSON.stringify({
"[min] valide" : true
}));
}
// результат
return true;
}
var min = Globalize.parseFloat(param.value);
var valide = val >= min;
// журнали
if (logs.min) {
console.log(jSON.stringify({
"[min] valide" : valide
}));
}
// результат
return valide;
});
$.validator.unobtrusive.adapters.add("min", [ "value" ], function(options) {
options.rules["min"] = options.params;
options.messages["min"] = options.message.replace("''", "'");
});
Ось кілька записів журналу виконання:
Введене значення | журнали |
| |
| |
|
6.3.15. Валідатор [regex]
![]() |
Рядок [1] генерується наступною послідовністю з подання [vue-01.xml]:
<!-- обов’язкове, регулярний вираз -->
<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>
Ці рядки стосуються поля [strBetween4and6] у формі [Form01]:
@NotNull
@Size(min = 4, max = 6)
private String strBetween4and6;
Рядок 5 генерує такий 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" />
Цей тег вводить валідатор [regex] [data-val-regex="La chaîne doit avoir entre 4 et 6 caractères"] з його параметром [pattern] [data-val-regex-pattern="^.{4,6}$"]. Параметр [pattern] — це регулярний вираз, на відповідність якому має перевірятися значення, що валідується. У цьому випадку регулярний вираз перевіряє, чи рядок містить від 4 до 6 довільних символів. Валідатор [regex] попередньо визначений у бібліотеці валідації MS. Отже, у файл [client-validation.js] нічого додавати не потрібно.
6.3.16. Валідатор [email]
![]() |
Рядок [1] генерується наступною послідовністю з подання [vue-01.xml]:
<!-- обов'язкове, електронна пошта -->
<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>
Ці рядки стосуються поля [email] у формі [Form01]:
@NotNull
@Email
@NotBlank
private String email;
Рядок 5 генерує наступний рядок 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" />
Цей тег вводить валідатор [email] [data-val-email="Adresse mail invalide"]. Валідатор [email] попередньо визначений у бібліотеці валідації MS. Отже, у файл [client-validation.js] нічого додавати не потрібно.
6.3.17. Валідатор [range]
![]() |
Рядок [1] генерується наступною послідовністю з подання [vue-01.xml]:
<!-- обов'язкове, ціле число, діапазон (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>
Ці рядки стосуються поля [int1014] у формі [Form01]:
@Range(min = 10, max = 14)
@NotNull
private Integer int1014;
Рядок 5 генерує наступний рядок 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" />
Цей тег вводить новий валідатор [range] [data-val-range="La valeur doit être dans l''intervalle [10,14]"] з двома параметрами [min] [data-val-range-min="10"] та [max] [data-val-range-max="14"].
У файлі [client-validation.js] ми визначаємо валідатор [range] наступним чином:
// -------------- діапазон, який слід використовувати разом із [int] або [number]
logs.range=true
$.validator.addMethod("range", function(value, element, param) {
// журнали
if (logs.range) {
console.log(jSON.stringify({
"[range] value" : value,
"[range] param" : param
}));
}
// термін дії
var val = Globalize.parseFloat(value);
if (isNaN(val)) {
// журнали
if (logs.min) {
console.log(jSON.stringify({
"[range] valide" : true
}));
}
// завершено
return true;
}
var min = Globalize.parseFloat(param.min);
var max = Globalize.parseFloat(param.max);
var valide = val >= min && val <= max;
// журнали
if (logs.range) {
console.log(jSON.stringify({
"[range] valide" : valide
}));
}
// завершено
return valide;
});
$.validator.unobtrusive.adapters.add("range", [ "min", "max" ], function(options) {
options.rules["range"] = options.params;
options.messages["range"] = options.message.replace("''", "'");
});
Він дуже схожий на валідатори [min] та [max], які ми вже розглядали.
Ось кілька прикладів журналів:
Введене значення | журнали |
| |
| |
|
6.3.18. Валідатор [number]
![]() |
Рядок [1] генерується наступною послідовністю з подання [vue-01.xml]:
<!-- double1: обов’язкове, число, діапазон (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>
Ці рядки стосуються поля [double1] у формі [Form01]:
@NotNull
@DecimalMax(value = "3.4")
@DecimalMin(value = "2.3")
private Double double1;
Рядок 5 генерує наступний рядок 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" />
Тег вводить новий валідатор [number] з атрибутом [data-val-number="Format invalide"]. Цей валідатор визначено у файлі [client-validation.js] наступним чином:
// -------------- число
logs.number = true;
$.validator.addMethod("number", function(value, element, param) {
var valide = !isNaN(Globalize.parseFloat(value));
// журнали
if (logs.number) {
console.log(jSON.stringify({
"[number] value" : value,
"[number] valide" : valide
}));
}
// результат
return valide;
});
$.validator.unobtrusive.adapters.add("number", [], function(options) {
options.rules["number"] = options.params;
options.messages["number"] = options.message.replace("''", "'");
});
Ось кілька прикладів записів у журналі:
Введене значення | журнали |
Відомо, що дійсні числа залежать від культури. Вище наведено приклад для формату [fr-FR]. Якщо ввести [2.5] (англосаксонський формат), число приймається. Це через [Globalize.parseFloat], який приймає обидва формати:
Перейдемо на англійську мову та введемо [+2,5] та [+2.5]. Журнали мають такий вигляд:
Введене значення | записи |
Виникла проблема з [2,5]. Він був визначений як дійсний, хоча слід було вказати [2.5]. Причиною цього є [Globalize.parseFloat]:
У наведеному вище прикладі [Globalize.parseFloat] ігнорує кому і вважає, що число дорівнює 25. У системі [en-US] дійсне число може містити десяткову крапку та коми, які іноді використовуються для відокремлення тисяч.
Ситуацію можна покращити таким чином:
// -------------- число
logs.number = true;
$.validator.addMethod("number", function(value, element, param) {
// обробляються лише культури [fr-FR] та [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;
// перевірка правильності
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));
}
// журнали
if (logs.number) {
console.log(jSON.stringify({
"[number] value" : value,
"[number] culture" : culture,
"[number] valide" : valide
}));
}
// результат
return valide;
});
- рядок 5: регулярний вираз для дійсного числа у культурі [fr-FR];
- рядок 6: регулярний вираз для числа з десятковою крапкою у культурі [en-US];
- рядок 7: назва поточної культури. У нашому прикладі це буде одна з двох вищезазначених культур;
- рядки 9–16: перевірка правильності введеного значення;
- рядок 15: передбачено випадок, коли культура не є ані [fr-FR], ані [en-US];
Тепер у логах відображається таке:
Культура [fr-FR]
Введене значення | журнали |
| |
| |
| |
|
Культура [en-US]
Введене значення | журнали |
| |
| |
|
6.3.19. Валідатор [custom3]
![]() |
Рядок [1] генерується наступною послідовністю з подання [vue-01.xml]:
<!-- double3: обов’язкове, число, 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>
Ці рядки стосуються поля [double3] у формі [Form01]:
@NotNull
private Double double3;
Тут ми розглянемо валідатор, який перевіряє не введене значення, а взаємозв’язок між двома введеними значеннями. У цьому випадку потрібно, щоб [double1+double3] знаходилося в діапазоні [10,13].
Рядок 5 генерує наступний рядок 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" />
Цей рядок вводить новий валідатор [custom3], оголошений атрибутом [data-val-custom3="[double3+double1] must be in [10,13]"]. Цей валідатор має такі параметри:
- [field], оголошений атрибутом [data-val-custom3-field="double1"]. Цей параметр позначає поле, значення якого бере участь у розрахунку валідності [double3];
- [min], визначений атрибутом [data-val-custom3-min="10.0"]. Цей параметр є мінімальним значенням інтервалу [min, max], у якому має знаходитися [double1+double3];
- [max], визначений атрибутом [data-val-custom3-max="13.0"]. Цей параметр є максимальним значенням інтервалу [min, max], у якому має знаходитися [double1+double3];
Цей валідатор обробляється в [client-validation.js] наступним чином:
// -------------- custom3 використовується разом із [number]
logs.custom3 = true;
$.validator.addMethod("custom3", function(value1, element, param) {
// друге значення
var value2 = $("#" + param.field).val();
// журнали
if (logs.custom3) {
console.log(jSON.stringify({
"[custom3] value1" : value1,
"[custom3] param" : param,
"[custom3] value2" : value2
}))
}
// перше значення
var valeur1 = Globalize.parseFloat(value1);
if (isNaN(valeur1)) {
// дозволяємо валідатору [number] виконати роботу
if (logs.custom3) {
console.log(jSON.stringify({
"[custom3] valide" : true
}))
}
return true;
}
// друге значення
var valeur2 = Globalize.parseFloat(value2);
if (isNaN(valeur2)) {
// неможливо виконати розрахунок терміну дії
if (logs.custom3) {
console.log(jSON.stringify({
"[custom3] valide" : false
}))
}
return false;
}
// розрахунок терміну дії
var min = Globalize.parseFloat(param.min);
var max = Globalize.parseFloat(param.max);
var somme = valeur1 + valeur2;
var valide = somme >= min && somme <= max;
// журнали
if (logs.custom3) {
console.log(jSON.stringify({
"[custom3] valide" : valide
}))
}
// результат
return valide;
});
$.validator.unobtrusive.adapters.add("custom3", [ "field", "max", "min" ], function(options) {
options.rules["custom3"] = options.params;
options.messages["custom3"] = options.message.replace("''", "'");
});
Ось кілька прикладів журналів:
Введені значення [double1,double3] | журнали |
| |
| |
| |
|
6.3.20. Валідатор [url]
![]() |
Рядок [1] генерується наступною послідовністю з подання [vue-01.xml]:
<!-- обов’язкове, 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>
Ці рядки стосуються поля [url] у формі [Form01]:
@URL
@NotBlank
private String url;
Рядок 5 генерує наступний рядок HTML:
<input type="text" data-val="true" data-val-url="Invalid URL" data-val-required="Field is required" value="" id="url" name="url" />
Вона додає валідатор [url] з атрибутом [data-val-url]. Цей валідатор попередньо визначений у бібліотеці валідації jQuery. У [client-validation.js] нічого додавати не потрібно.
6.3.21. Увімкнення/вимкнення перевірки на стороні клієнта
Поки перевірка на стороні клієнта активна, перевірка на стороні сервера ніколи не спрацьовує, оскільки відправлені значення надходять на сервер лише в тому випадку, якщо вони були визнані дійсними на стороні клієнта. Щоб побачити, як працює перевірка на стороні сервера, необхідно вимкнути перевірку на стороні клієнта. У вікні [vue-01.xml] є два посилання для управління цією активацією/деактивацією:
<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>
Ці два посилання не відображаються одночасно:
![]() | ![]() |
Переклад цих посилань у HTML такий:
<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>
Скрипт jS [setClientValidation] визначено у файлі [local.js] (див. вище). У функції [$(document).ready] цього файлу використовуються посилання для перевірки:
// документ готовий
$(document).ready(function() {
// глобальні посилання
...
activateValidationTrue = $("#clientValidationTrue");
activateValidationFalse = $("#clientValidationFalse");
clientValidation = $("#clientValidation");
...
// посилання для перевірки
// clientValidation — це приховане поле, яке встановлюється сервером
var validate = clientValidation.val();
setClientValidation2(validate === "true");
});
- рядок 5: посилання на посилання для активації перевірки на стороні клієнта;
- рядок 6: посилання на посилання для деактивації перевірки на стороні клієнта;
- рядок 7: посилання на приховане поле форми, яке зберігає останній стан активації у вигляді логічного значення [true : validation client activée, false : validation client désactivée]. Це поле знаходиться у подання [vue-01.xml] у такому вигляді:
<input type="hidden" th:field="*{clientValidation}" th:value="*{clientValidation}" value="true" />
і відповідає полю [clientValidation] у формі [Form01]:
// перевірка на стороні клієнта
private boolean clientValidation = true;
- рядок 11: отримуємо значення прихованого поля;
- рядок 12: викликається наступна функція [setClientValidation2]:
function setClientValidation2(activate) {
// посилання
if (activate) {
// клієнтська перевірка активна
activateValidationTrue.hide();
activateValidationFalse.show();
// здійснюється аналіз валідаторів форми
$.validator.unobtrusive.parse(formulaire);
} else {
// клієнтська валідація неактивна
activateValidationFalse.hide();
activateValidationTrue.show();
// деактивуються валідатори форми
formulaire.data('validator', null);
}
}
- рядок 1: параметр [activate] дорівнює [true], якщо потрібно активувати перевірку на стороні клієнта, і false в іншому випадку;
- рядки 5–6: посилання на вимкнення відображається, а посилання на увімкнення — приховане;
- рядок 8: щоб перевірка на стороні клієнта працювала, потрібно проаналізувати документ на наявність валідаторів [data-val-X]. Параметром функції [$.validator.unobtrusive.parse] є ідентифікатор jS форми, яку потрібно проаналізувати;
- рядки 11–12: посилання на активацію відображається, посилання на деактивацію приховане;
- рядок 14: валідатори форми вимкнені. Відтепер це так, ніби у формі немає валідаторів jS;
Для чого призначена ця функція [setClientValidation2]? Вона служить для управління POST. Оскільки поле [clientValidation] є прихованим, воно надсилається і повертається разом із формою, відправленою сервером. Тоді його значення використовується для відновлення перевірки на стороні клієнта в тому стані, в якому вона була до POST. Адже між запитами не зберігається інформація про jS. Тому сервер повинен передати в новий вигляд інформацію, яка дозволяє ініціалізувати jS цього вигляду. Зазвичай це робиться у функції [$(document).ready].
Повернемося до функції [setClientValidation], яка обробляє кліки на посиланнях увімкнення/вимкнення перевірки на стороні клієнта:
// перевірка на стороні клієнта
function setClientValidation(activate) {
// управління ввімкненням/вимкненням клієнтської валідації
setClientValidation2(activate);
// зберігання вибору користувача у прихованому полі
clientValidation.val(activate ? "true" : "false");
// додаткові налаштування
if (activate) {
// перевірка на стороні клієнта увімкнена
// очищення всіх повідомлень про помилки з сервера
clearServerErrors();
// форма перевірена
formulaire.validate().form();
} else {
// перевірка на стороні клієнта вимкнена
// очищаються всі повідомлення про помилки на стороні клієнта
clearClientErrors();
}
}
- рядок 4: використовується функція [setClientValidation2], яку ми щойно розглянули;
- рядок 6: зберігаємо вибір користувача у прихованому полі, щоб отримати його після повернення з наступної функції POST;
- рядок 11: якщо перевірка на стороні клієнта активна, ми очищаємо стовпець [serveur] у поданні від повідомлень про помилки. Функцію [clearServerErrors] ми описали в розділі 6.3.7;
- рядок 13: виконуються валідатори jS для відображення можливих повідомлень про помилки у стовпці [client] подання;
- рядок 17: якщо перевірка клієнта вимкнена, то з стовпця [client] у поданні видаляються повідомлення про помилки. Давайте розглянемо в консолі розробника Chrome код HTML елемента, що містить помилку:
<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>
- у рядку 2 бачимо, що в стовпці 2 таблиці елемент із помилкою має стиль [class="input-validation-error"];
- у рядку 5 бачимо, що у стовпці 3 таблиці повідомлення про помилку має стиль [class="field-validation-error"];
Це справедливо для всіх елементів з помилками. Ці дві інформації використовуються у наступній функції [clearClientErrors]:
// очищення клієнтських помилок
function clearClientErrors() {
// очищення повідомлень про помилки на стороні клієнта
$(".field-validation-error").each(function(index) {
$(this).text("");
});
// зміна класу CSS для помилкових записів
$(".input-validation-error").each(function(index) {
$(this).removeClass("input-validation-error");
});
}
- рядки 4–6: шукаються всі елементи DOM, що мають клас [field-validation-error], і видаляється текст, який вони відображають. Таким чином видаляються повідомлення про помилки;
- рядки 8–10: шукаються всі елементи DOM, що мають клас [input-validation-error], і цей клас з них видаляється. Таким чином, елемент з помилкою, який був зафарбований у червоний колір, повертається до свого початкового стилю;


















































