Skip to content

6. التحقق من صحة البيانات باستخدام جافا سكريبت من جانب العميل

في الفصل السابق، تناولنا التحقق من الصحة من جانب الخادم. لنعد إلى بنية تطبيق Spring MVC:

BD

حتى الآن، لم تكن الصفحات المرسلة إلى العميل تحتوي على جافا سكريبت. سنتناول الآن هذه التقنية التي ستسمح لنا في البداية بإجراء عمليات التحقق من صحة البيانات من جانب العميل. والمبدأ هو كما يلي:

  • جافا سكريبت هي التي ترسل القيم إلى خادم الويب؛
  • وبالتالي، قبل تنفيذ 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. على وحدة التحكم، نحصل على قائمة مشابهة لما يلي:
Liste des beans Spring
basicErrorController
beanNameHandlerMapping
beanNameViewResolver
config
defaultServletHandlerMapping
defaultTemplateResolver
defaultViewResolver
dispatcherServlet
dispatcherServletRegistration
embeddedServletContainerCustomizerBeanPostProcessor
error
errorAttributes
faviconHandlerMapping
faviconRequestHandler
handlerExceptionResolver
hiddenHttpMethodFilter
http.mappers.CONFIGURATION_PROPERTIES
httpRequestHandlerAdapter
jacksonObjectMapper
jsController
layoutDialect
localeChangeInterceptor
localeResolver
mappingJackson2HttpMessageConverter
mbeanExporter
mbeanServer
messageConverters
messageSource
multipart.CONFIGURATION_PROPERTIES
multipartConfigElement
multipartResolver
mvcContentNegotiationManager
mvcConversionService
mvcUriComponentsContributor
mvcValidator
objectNamingStrategy
org.springframework.boot.autoconfigure.AutoConfigurationPackages
org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration$JacksonObjectMapperAutoConfiguration
org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration
org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration$Empty
org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration
org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration$DefaultTemplateResolverConfiguration
org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration$ThymeleafViewResolverConfiguration
org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration$ThymeleafWebLayoutConfiguration
org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration
org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration$DispatcherServletConfiguration
org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration
org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration$EmbeddedTomcat
org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration
org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration$WhitelabelErrorViewConfiguration
org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration
org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration$ObjectMappers
org.springframework.boot.autoconfigure.web.MultipartAutoConfiguration
org.springframework.boot.autoconfigure.web.ServerPropertiesAutoConfiguration
org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration
org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter
org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter$FaviconConfiguration
org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor
org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor.store
org.springframework.context.annotation.ConfigurationClassPostProcessor.enhancedConfigurationProcessor
org.springframework.context.annotation.ConfigurationClassPostProcessor.importAwareProcessor
org.springframework.context.annotation.MBeanExportConfiguration
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalRequiredAnnotationProcessor
org.springframework.web.servlet.config.annotation.DelegatingWebMvcConfiguration
propertySourcesPlaceholderConfigurer
requestContextListener
requestMappingHandlerAdapter
requestMappingHandlerMapping
resourceHandlerMapping
serverProperties
simpleControllerHandlerAdapter
spring.mvc.CONFIGURATION_PROPERTIES
spring.resources.CONFIGURATION_PROPERTIES
templateEngine
templateResolver
thymeleafResourceResolver
thymeleafViewResolver
tomcatEmbeddedServletContainerFactory
viewControllerHandlerMapping
viewResolver

لقد قمنا بتمييز الكائنات المُعرَّفة في الفئة [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]. سيستخدم جافا سكريبت هذه المعلومات؛

سيتم إرسال القيم التي تم إدخالها في النموذج [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]. ونعلم أن هناك عملية تحقق محددة [double1+double2] ضمن النطاق [10,13]. وعند الوصول إلى السطر 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: عدد كبير من عمليات استيراد مكتبات جافا سكريبت التي يمكننا تجاهلها هنا؛
  • السطر 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"]. تُستخدم بعض هذه الفئات من قِبل مكتبة جافا سكريبت الخاصة بالتحقق من الصحة. وهي مُعرَّفة في الملف [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 وجافا سكريبت

يتم التحقق من الصحة من جانب العميل باستخدام جافا سكريبت. سنستعين بإطار العمل jQuery الذي يوفر العديد من الوظائف التي تسهل تطوير جافا سكريبت. نقدم هنا أساسيات 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/]:

Image

سنضع الملف الذي تم تنزيله في المجلد [static / js]:

  

بعد ذلك، نطلب عرض الصورة الثابتة [jQuery-01.html] باستخدام متصفح Chrome [1-2]:

باستخدام Google Chrome، أدخل [Ctrl-Maj-I] لإظهار أدوات التطوير [3]. تتيح علامة التبويب [Console] [4] تنفيذ كود جافا سكريبت. نقدم في ما يلي أوامر جافا سكريبت التي يجب كتابتها مع شرح لها.

JS
النتيجة
$("#element1")
: يعرض مجموعة جميع العناصر ذات المعرف [element1]، وبالتالي عادةً ما تكون مجموعة مكونة من عنصر واحد أو صفر عنصر، لأنه لا يمكن وجود معرفين متطابقين في صفحة واحدة HTML.
$("#element1").text("blabla")
: يطبق النص [blabla] على جميع عناصر المجموعة. ويؤدي ذلك إلى تغيير المحتوى المعروض في الصفحة
$("#element1").hide()
يخفي عناصر المجموعة. لم يعد النص [blabla] معروضًا.
$("#element1")
: يعرض المجموعة مرة أخرى. وهذا يتيح لنا رؤية أن العنصر ذو المعرف [element1] يحتوي على السمة CSS style='display: none;' التي تؤدي إلى إخفاء العنصر.
$("#element1").show()
: يعرض عناصر المجموعة. يظهر النص [blabla] مرة أخرى. السمة CSS style='display : block;' هي التي تضمن هذا العرض.
$("#element1").attr('style','color: red')
: يعيّن سمة لجميع عناصر المجموعة. السمة هنا هي [style] وقيمتها [color: red]. يتحول لون النص [blabla] إلى اللون الأحمر.
Tableau
Dictionnaire

تجدر الإشارة إلى أن معرّف المتصفح 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. من المهم تذكر ذلك عند إجراء تصحيح أخطاء جافا سكريبت. لذا غالبًا ما يكون من غير المجدي عرض الكود المصدري للصفحة المعروضة.

لدينا ما يكفي من المعلومات لفهم البرامج النصية 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]:

12
34
  • في [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]: النسخة غير القابلة للقراءة، والمعروفة باسم النسخة «المصغرة» (minified) للمكتبة. وهي ليست غير قابلة للقراءة تمامًا. فهي عبارة عن نص. لكنها غير مفهومة. هذه هي النسخة التي يجب استخدامها في بيئة الإنتاج لأن هذا الملف أصغر حجمًا من النسخة المقابلة [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}]]. لاحظ الصيغة الخاصة لهذا التعبير. ويرجع ذلك إلى أنه مكتوب بلغة جافا سكريبت. سيتم استبدال التعبير [[${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. قيمته هي مرجع إلى العقدة ذات السمة [id='lang'] في DOM؛
  • السطر 2: المتغيرات المُعلنة خارج نطاق الدالة تُعتبر متغيرات عامة بالنسبة للدوال. وهذا يعني هنا أن المتغير [lang] الذي تم تهيئته في [$(document).ready()] معروف أيضًا في الدالة [setLocale] في السطر 11؛
  • السطر 13: يُعدّل السمة [value] للعقدة المُعرَّفة بـ [lang]. إذا كانت قيمة lang تساوي [xx_XX]، فإن العلامة HTML الخاصة بالعقدة تصبح:

<input type="hidden" value="xx_XX" id="lang" name="lang" />

تسمح لغة جافا سكريبت بتعديل قيمة عناصر DOM (نموذج كائن المستند).

  • السطر 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()" />

إذا كان جافا سكريبت نشطًا في المتصفح، فإن النقر على الزر سيؤدي إلى تشغيل الأسلوب [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'] فتُستخدم بواسطة جافا سكريبت. وهي تُستخدم لتعريف عنصر من نموذج كائن المستند (DOM). يتم بالفعل تحويل المستند HTML الذي تم تحميله إلى شجرة جافا سكريبت تُسمى 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&param2=val2]، وهي القيمة التي سيقبلها الخادم؛

  • السطر 22: يتم إرجاع القيمة [true] حتى يتم تنفيذ [submit] في النموذج؛

تجدر الإشارة إلى أن الدالة jS [postForm01]:

  • تقوم بتنفيذ جميع أدوات التحقق من الصحة jS في النموذج إذا تم تفعيل التحقق من الصحة من جانب العميل، وتمنع وظيفة [submit] في النموذج من إرسال البيانات إلى الخادم إذا تم اعتبار إحدى القيم المدخلة غير صالحة؛
  • يسمح بتنفيذ [submit] إما لأن التحقق من صحة البيانات من جانب العميل غير مفعل، أو لأنه مفعل وجميع القيم المدخلة صالحة؛

ويبقى الأمر الخاص بالسطر [3]:


    // يتم مسح أخطاء الخادم
clearServerErrors();

تهدف الدالة [clearServerErrors] إلى مسح الرسائل الموجودة في العمود 4 من العرض [vue-01.xml]:

في لقطة الشاشة أعلاه، تم النقر على الرابط [English]. وقد لاحظنا أن ذلك أدى إلى POST للقيم المدخلة دون تشغيل أدوات التحقق jS. عند العودة من POST، يتم ملء العمود [Server Validation] بأي رسائل خطأ محتملة. وإذا نقرنا الآن على الزر [Validate] [2] مع تفعيل أدوات التحقق من الصحة jS [3]، فإن العمود [Client Validation] [4] سيُملأ بالرسائل. وإذا لم يتم اتخاذ أي إجراء، فستبقى القيم الموجودة في العمود [Server Validation] كما هي، مما سيؤدي إلى حدوث ارتباك؛ لأنه في حالة اكتشاف أخطاء بواسطة أدوات التحقق jS، لن يتم استدعاء الخادم. لتجنب ذلك، نقوم بمسح العمود [Server Validation] في الدالة [postForm01]. الدالة [] هي التي تقوم بهذه المهمة:


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]. نريد إعادة إنتاج هذا القيد على جانب العميل باستخدام جافا سكريبت.

دعونا ندرس السطرين 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] التابعة لشركة مايكروسوفت. ولا داعي لتعريفها. ولن يكون هذا هو الحال دائمًا في ما يلي؛
  • يتم تجاهل العلامات [data-x] بواسطة HTML5. وهي لا تكون مفيدة إلا في حالة وجود جافا سكريبت لاستغلالها؛

لنلقِ نظرة الآن على السطر 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، يجب استخدام السمات [data-valmsg-for="strNotEmpty"] و [data-valmsg-replace="true"] في السطر 7؛

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;

نريد إعادة إنتاج هذا القيد على جانب العميل باستخدام جافا سكريبت. أصبحت الأسطر 12-17 الآن قياسية:

  • الأسطر 12-14: تعرض، في حالة حدوث خطأ في الحقل [assertFalse]، الرسالة التي يحملها السمة [data-val-assertfalse] في السطر 6 أو تلك التي يحملها السمة [data-val-required] في نفس السطر. تجدر الإشارة إلى أن هذه الرسائل مترجمة، أي باللغة التي اختارها المستخدم مسبقًا أو باللغة الفرنسية إذا لم يقم باختيار أي لغة؛
  • الأسطر 5-10: تعرض أزرار الاختيار مع عناصر التحقق من صحة js التي يتم تشغيلها بمجرد أن ينقر المستخدم على أحدها.

تم إنشاء الزرين بنفس الطريقة. سنقوم بفحص الزر الأول:


<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("''", "'");
});

بصراحة تامة، أنا لست متخصصاً في جافا سكريبت، وهي لغة لا تزال غامضة تماماً بالنسبة لي. أساسياتها بسيطة، لكن المكتبات المبنية على هذه الأساسيات غالباً ما تكون معقدة للغاية. لكتابة أسطر الكود المذكورة أعلاه، استلهمتُ من أكواد وجدتها على الإنترنت. وقد أرشدني الرابط [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. لكن جافا سكريبت سيعرض العلامتين المزدوجتين. في السطر 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]:


<!-- مطلوب، عدد صحيح، الحد الأقصى (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:


<!-- مطلوب، عدد صحيح، الحد الأقصى (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] تمثل بالفعل عددًا صحيحًا. وقد يكون هذا العدد موجبًا أو سالبًا؛

فيما يلي بعض الأمثلة على السجلات:

1
2
3
{"[int] value":"x","[int] valide":false}
{"[int] value":"11","[int] valide":true}
{"[int] value":"11x","[int] valide":false}

يتم إضافة أداة التحقق [max] بالطريقة التالية في [client-validation.js]


// -------------- الحد الأقصى يُستخدم بالاقتران مع [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

فيما يلي بعض السجلات:

القيمة المدخلة
سجلات
x

{"[max] value":"x","[max] param":{"value":"100"}}
{"[max] valide":true}
{"[int] value":"x","[int] valide":false}
111

{"[max] value":"111","[max] param":{"value":"100"}}
{"[max] valide":false}
111x

{"[max] value":"111x","[max] param":{"value":"100"}}
{"[max] valide":true}
{"[int] value":"111x","[int] valide":false}

6.3.14. أداة التحقق من صحة [min]

يتم إنشاء السطر [1] من خلال التسلسل التالي من العرض [vue-01.xml]:


<!-- مطلوب، عدد صحيح، الحد الأدنى (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("''", "'");
});

فيما يلي بعض سجلات التنفيذ:

القيمة المدخلة
سجلات
x

{"[min] value":"x","[min] param":{"value":"10"}}
{"[min] valide":true}
{"[int] value":"x","[int] valide":false}
11

{"[min] value":"11","[min] param":{"value":"10"}}
{"[min] valide":true}
{"[int] value":"11","[int] valide":true}
8x

{"[min] value":"8x","[min] param":{"value":"10"}}
{"[min] valide":true}
{"[int] value":"8x","[int] valide":false}

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&#39;&#39;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&#39;&#39;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] التي تمت دراستها سابقًا.

فيما يلي بعض الأمثلة على سجلات التشغيل:

القيمة المدخلة
السجلات
x

{"[range] value":"x","[range] param":{"min":"10","max":"14"}}
{"[int] value":"x","[int] valide":false}
8

{"[range] value":"8","[range] param":{"min":"10","max":"14"}}
{"[range] valide":false}
11

{"[range] valide":true}
{"[int] value":"11","[int] valide":true}

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&#39;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("''", "'");
});

فيما يلي بعض الأمثلة على السجلات:

القيمة المدخلة
سجلات
x
 {"[number] value":"x","[number] valide":false}
-2,5
{"[number] value":"-2,5","[number] valide":true}
{"[range] value":"-2,5","[range] param":{"min":"2.3","max":"3.4"}}
{"[range] valide":false}
2,5
{"[number] value":"+2,5","[number] valide":true}
{"[range] value":"+2,5","[range] param":{"min":"2.3","max":"3.4"}}
{"[range] valide":true}
+2.5
{"[number] value":"+2.5","[number] valide":true}
{"[range] value":"+2.5","[range] param":{"min":"2.3","max":"3.4"}}
{"[range] valide":true}

من المعروف أن الأعداد الحقيقية تتأثر بالثقافة. في المثال أعلاه، نحن في نظام [fr-FR]. عند إدخال [2.5] (الكتابة الأنجلوساكسونية)، يتم قبول العدد. والسبب في ذلك هو أن [Globalize.parseFloat] يقبل كلا النظامين:

Globalize.parseFloat("3.3")
3.3
Globalize.parseFloat("3,3")
3.3

لننتقل إلى اللغة الإنجليزية وندخل [+2,5] و [+2.5]. السجلات هي كما يلي:

القيمة المدخلة
السجلات
x
 {"[number] value":"x","[number] valide":false}
2,5
{"[number] value":"+2,5","[number] valide":true}
{"[range] value":"+2,5","[range] param":{"min":"2.3","max":"3.4"}}
{"[range] valide":false}
+2.5
{"[number] value":"+2.5","[number] valide":true}
{"[range] value":"+2.5","[range] param":{"min":"2.3","max":"3.4"}}
{"[range] valide":true}

هناك مشكلة في [2,5]. فقد تم الإبلاغ عنه على أنه قيمة حقيقية صالحة، في حين أنه يجب كتابة [2.5]. والسبب في ذلك هو [Globalize.parseFloat]:

Globalize.parseFloat("2,5")
25

في المثال أعلاه، يتجاهل [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]

القيمة المدخلة
السجلات
x

 {"[number] value":"x","[number] culture":"fr-FR","[number] valide":false}
-2,5

{"[number] value":"-2,5","[number] culture":"fr-FR","[number] valide":true}
{"[range] value":"-2,5","[range] param":{"min":"2.3","max":"3.4"}}
{"[range] valide":false}
2,5

{"[number] value":"+2,5","[number] culture":"fr-FR","[number] valide":true}
{"[range] value":"+2,5","[range] param":{"min":"2.3","max":"3.4"}}
{"[range] valide":true}
+2.5

{"[number] value":"+2.5","[number] culture":"fr-FR","[number] valide":false}

الثقافة [en-US]

القيمة المدخلة
السجلات
x

{"[number] value":"x","[number] culture":"en-US","[number] valide":false}
2,5

{"[number] value":"+2,5","[number] culture":"en-US","[number] valide":false}
+2.5

{"[number] value":"+2.5","[number] culture":"en-US","[number] valide":true}
{"[range] value":"+2.5","[range] param":{"min":"2.3","max":"3.4"}}
{"[range] valide":true}

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]
السجلات
[x,1]

{"[custom3] value1":"1","[custom3] param":{"field":"double1","max":"13.0","min":"10.0"},"[custom3] value2":"x"}
{"[custom3] valide":false}
[1,x]

{"[number] value":"x","[number] culture":"en-US","[number] valide":false}
[1,20]

{"[custom3] value1":"20","[custom3] param":{"field":"double1","max":"13.0","min":"10.0"},"[custom3] value2":"1"}
{"[custom3] valide":false}
[1,10]

{"[number] value":"10","[number] culture":"en-US","[number] valide":true}
{"[custom3] value1":"10","[custom3] param":{"field":"double1","max":"13.0","min":"10.0"},"[custom3] value2":"1"}
{"[custom3] valide":true}

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: إشارة إلى حقل مخفي في النموذج يحفظ الحالة الأخيرة للتفعيل في شكل قيمة منطقية (Boolean) [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.
  • السطر 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] وإزالة هذه الفئة منها. وبذلك يستعيد العنصر الخاطئ الذي كان ملونًا باللون الأحمر نمطه الأصلي؛