Skip to content

2. Java Server Faces

سنقدم الآن إطار عمل Java Server Faces. سيتم استخدام الإصدار 2، لكن الأمثلة توضح بشكل أساسي ميزات الإصدار 1. وسنغطي فقط ميزات الإصدار 2 الضرورية للتطبيق النموذجي التالي.

2.1. دور JSF في تطبيق الويب

أولاً، دعونا نضع JSF في سياق تطوير تطبيق الويب. غالبًا ما يتم بناؤه على بنية متعددة المستويات مثل ما يلي:

  • طبقة [الويب] هي الطبقة التي تتعامل مع مستخدم تطبيق الويب. يتفاعل المستخدم مع تطبيق الويب من خلال صفحات الويب التي يعرضها المتصفح. وتقع JSF في هذه الطبقة، وفيها فقط؛
  • تقوم طبقة [الأعمال] بتنفيذ قواعد العمل الخاصة بالتطبيق، مثل حساب الراتب أو الفاتورة. تستخدم هذه الطبقة البيانات الواردة من المستخدم عبر طبقة [الويب] ومن نظام إدارة قواعد البيانات (DBMS) عبر طبقة [DAO
  • تقوم طبقة [DAO] (كائنات الوصول إلى البيانات) وطبقة [JPA] (واجهة برمجة تطبيقات الاستمرارية في Java) ومحرك JDBC بإدارة الوصول إلى بيانات نظام إدارة قواعد البيانات. تعمل طبقة [JPA] كأداة ORM (أداة التعيين العلائقي للكائنات). وهي تعمل كجسر بين الكائنات التي تتعامل معها طبقة [DAO] والصفوف والأعمدة من البيانات في قاعدة البيانات العلائقية؛
  • يمكن تحقيق تكامل هذه الطبقات باستخدام حاوية Spring أو EJB3 (Enterprise JavaBeans).

ستستخدم الأمثلة الواردة أدناه لتوضيح JSF طبقة واحدة فقط، وهي طبقة [web]:

بمجرد إتقان أساسيات JSF، سنقوم ببناء تطبيقات Java EE متعددة الطبقات.

2.2. نموذج تطوير JSF MVC

تطبق JSF نمط الهندسة المعمارية MVC (النموذج – العرض – وحدة التحكم) على النحو التالي:

تطبق هذه البنية نمط تصميم MVC (النموذج، العرض، وحدة التحكم). تتم معالجة طلب العميل وفقًا للخطوات الأربع التالية:

  1. الطلب – يرسل متصفح العميل طلبًا إلى وحدة التحكم [Faces Servlet]. تتولى وحدة التحكم معالجة جميع طلبات العملاء. وهي نقطة الدخول إلى التطبيق. وهذا هو الحرف C في MVC،
  2. المعالجة – تقوم وحدة التحكم C بمعالجة هذا الطلب. وللقيام بذلك، تساعدها معالجات الأحداث الخاصة بالتطبيق [2a]. قد تحتاج هذه المعالجات إلى مساعدة من طبقة الأعمال [2b]. بمجرد معالجة طلب العميل، قد يؤدي ذلك إلى استجابات متنوعة. ومن الأمثلة الكلاسيكية على ذلك:
    • صفحة خطأ إذا تعذر معالجة الطلب بشكل صحيح؛
    • صفحة تأكيد في الحالات الأخرى،
  3. التنقل - تختار وحدة التحكم الاستجابة (= العرض) التي سيتم إرسالها إلى العميل. يتضمن اختيار الاستجابة التي سيتم إرسالها إلى العميل عدة خطوات:
    • اختيار Facelet الذي سيقوم بإنشاء الاستجابة. وهذا ما يُسمى العرض V، وهو حرف V في MVC. يعتمد هذا الاختيار عمومًا على نتيجة تنفيذ الإجراء الذي طلبه المستخدم؛
    • تزويد Facelet بالبيانات التي يحتاجها لتوليد هذا الرد. في الواقع، غالبًا ما يحتوي هذا الرد على معلومات محسوبة بواسطة وحدة التحكم. تشكل هذه المعلومات ما يسمى بنموذج M للعرض، وهو الحرف M في MVC،

لذلك تتكون الخطوة 3 من اختيار عرض V وإنشاء النموذج M المطلوب له.

  1. الاستجابة - يوجه وحدة التحكم C Facelet المحدد لعرض نفسه. يستخدم Facelet النموذج M الذي أعدته وحدة التحكم C لتهيئة الأجزاء الديناميكية من الاستجابة التي يجب أن يرسلها إلى العميل. قد يختلف الشكل الدقيق لهذه الاستجابة: فقد يكون دفق HTML أو ملف PDF أو Excel، إلخ.

في مشروع JSF:

  • وحدة التحكم C هي servlet [javax.faces.webapp.FacesServlet]. توجد هذه في مكتبة [javaee.jar
  • يتم تنفيذ طرق العرض V بواسطة صفحات تستخدم تقنية Facelets،
  • يتم تنفيذ نماذج M ومعالجات الأحداث بواسطة فئات Java التي غالبًا ما تسمى "backing beans" أو ببساطة beans.

الآن، دعونا نوضح العلاقة بين بنية الويب MVC والبنية الطبقية. هذان مفهومان مختلفان يتم الخلط بينهما أحيانًا. لنفكر في تطبيق ويب JSF أحادي الطبقة:

إذا قمنا بتنفيذ الطبقة [الويب] باستخدام JSF، فسنحصل بالفعل على بنية ويب MVC ولكن ليس بنية متعددة الطبقات. هنا، ستتولى الطبقة [الويب] كل شيء: العرض، والمنطق التجاري، والوصول إلى البيانات. مع JSF، ستقوم الحبوب (beans) بهذا العمل.

الآن، دعونا ننظر في بنية ويب متعددة الطبقات:

يمكن تنفيذ طبقة [الويب] بدون إطار عمل وبدون اتباع نموذج MVC. وبذلك نحصل على بنية متعددة الطبقات، لكن طبقة الويب لا تنفذ نموذج MVC.

في MVC، قلنا إن نموذج M هو نموذج عرض V، أي مجموعة البيانات التي يعرضها عرض V. وغالبًا ما يُعطى تعريف آخر لنموذج M في MVC:

يعتبر العديد من المؤلفين أن ما يقع على يمين طبقة [الويب] يشكل النموذج M في نموذج MVC. لتجنب الغموض، سنشير إلى:

  • نموذج المجال عند الإشارة إلى كل ما يقع على يمين طبقة [الويب]،
  • نموذج العرض عند الإشارة إلى البيانات التي تعرضها طريقة العرض V.

فيما يلي، سيشير مصطلح "نموذج M" حصريًا إلى نموذج عرض V.

2.3. مثال mv-jsf2-01: مكونات مشروع JSF

ستقتصر الأمثلة الأولى على طبقة الويب التي تم تنفيذها باستخدام JSF 2:

وبمجرد الانتهاء من تغطية الأساسيات، سنقوم بدراسة أمثلة أكثر تعقيدًا باستخدام بنى متعددة الطبقات.

2.3.1. إنشاء المشروع

نقوم بإنشاء أول مشروع JSF2 باستخدام NetBeans 7.

  
  • في [1]، قم بإنشاء مشروع جديد،
  • في [2]، حدد فئة [Maven] ونوع المشروع [Web Application
  • في [3]، حدد المجلد الأصلي للمشروع الجديد،
  • في [4]، قم بتسمية المشروع،
  • في [5]، اختر خادمًا. باستخدام NetBeans 7، يمكنك الاختيار بين خادمي Apache Tomcat و GlassFish. الفرق بينهما هو أن GlassFish يدعم EJBs (Enterprise Java Beans) بينما Tomcat لا يدعمها. لن تستخدم أمثلة JSF الخاصة بنا EJBs. لذا، يمكنك هنا اختيار أي من الخادمين،
  • في [6]، حدد إصدار Java EE 6 Web،
  • في [7]، المشروع الذي تم إنشاؤه.

دعونا نفحص عناصر المشروع ونشرح دور كل منها.

  • في [1]: الفروع المختلفة للمشروع:
    • [صفحات الويب]: ستحتوي على صفحات الويب (.xhtml، .jsp، .html)، والموارد (الصور، والوثائق المختلفة)، وتكوين طبقة الويب، وتكوين إطار عمل JSF؛
    • [حزم المصدر]: فئات Java الخاصة بالمشروع؛
    • [التبعيات]: أرشيفات .jar التي يتطلبها المشروع وتديرها بنية Maven؛
    • [تبعيات Java]: أرشيفات .jar التي يتطلبها المشروع ولا يديرها إطار عمل Maven؛
    • [ملفات المشروع]: ملفات تكوين Maven و NetBeans،
  • في [2]: فرع [صفحات الويب]،

يحتوي على صفحة [index.jsp] التالية:


<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
   "http://www.w3.org/TR/HTML4/loose.dtd">
 
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>JSP Page</title>
    </head>
    <body>
        <h1>Hello World!</h1>
    </body>
</html>

هذه صفحة ويب تعرض السلسلة "Hello World" بخط كبير.

فيما يلي ملف [META-INF/context.xml]:


<?xml version="1.0" encoding="UTF-8"?>
<Context antiJARLocking="true" path="/mv-jsf2-01"/>

يشير السطر 2 إلى أن سياق التطبيق (أو اسمه) هو /mv-jsf2-01. وهذا يعني أن صفحات الويب الخاصة بالمشروع سيتم طلبها عبر عنوان URL بالصيغة http://machine:port/mv-jsf2-01/page. السياق هو، بشكل افتراضي، اسم المشروع. لن نحتاج إلى تعديل هذا الملف.

  • في [3]، فرع [Source Packages

يحتوي هذا الفرع على شفرة المصدر لفئات Java الخاصة بالمشروع. هنا لا توجد لدينا أي فئات. قام NetBeans بإنشاء حزمة افتراضية يمكن حذفها [4].

  • في [5]، فرع [Dependencies

يعرض هذا الفرع جميع المكتبات التي يتطلبها المشروع وتديرها Maven. سيتم تنزيل جميع المكتبات المدرجة هنا تلقائيًا بواسطة Maven. ولهذا السبب يتطلب مشروع Maven اتصالاً بالإنترنت. سيتم تخزين المكتبات التي تم تنزيلها محليًا. إذا احتاج مشروع آخر إلى مكتبة موجودة بالفعل محليًا، فلن يتم تنزيلها. سنرى أن قائمة المكتبات هذه، بالإضافة إلى المستودعات التي يمكن العثور عليها فيها، محددة في ملف تكوين مشروع Maven.

  • في [6]، المكتبات التي يتطلبها المشروع ولا يديرها Maven،
  • في [7]، ملفات تكوين مشروع Maven:
    • [nb-configuration.xml] هو ملف تكوين NetBeans. لن نهتم به.
    • [pom.xml]: ملف تكوين Maven. POM هي اختصار لـ Project Object Model. قد نحتاج أحيانًا إلى تعديل هذا الملف مباشرةً.

الملف [pom.xml] الذي تم إنشاؤه هو كما يلي:


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
 
  <groupId>istia.st</groupId>
  <artifactId>mv-jsf2-01</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>war</packaging>
 
  <name>mv-jsf2-01</name>
 
  <properties>
    <endorsed.dir>${project.build.directory}/endorsed</endorsed.dir>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>
 
  <dependencies>
    <dependency>
      <groupId>javax</groupId>
      <artifactId>javaee-web-api</artifactId>
      <version>6.0</version>
      <scope>provided</scope>
    </dependency>
  </dependencies>
 
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>2.3.2</version>
        <configuration>
          <source>1.6</source>
          <target>1.6</target>
          <compilerArguments>
            <endorseddirs>${endorsed.dir}</endorseddirs>
          </compilerArguments>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-war-plugin</artifactId>
        <version>2.1.1</version>
        <configuration>
          <failOnMissingWebXml>false</failOnMissingWebXml>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-dependency-plugin</artifactId>
        <version>2.1</version>
        <executions>
          <execution>
            <phase>validate</phase>
            <goals>
              <goal>copy</goal>
            </goals>
            <configuration>
              <outputDirectory>${endorsed.dir}</outputDirectory>
              <silent>true</silent>
              <artifactItems>
                <artifactItem>
                  <groupId>javax</groupId>
                  <artifactId>javaee-endorsed-api</artifactId>
                  <version>6.0</version>
                  <type>jar</type>
                </artifactItem>
              </artifactItems>
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
 
</project>
  • تحدد الأسطر 5–8 عنصر Java الذي سيتم إنشاؤه بواسطة مشروع Maven. تأتي هذه المعلومات من المعالج المستخدم عند إنشاء المشروع:

يتم تعريف عنصر Maven من خلال أربع خصائص:

  • [groupId]: معلومات تشبه اسم الحزمة. على سبيل المثال، مكتبات إطار عمل Spring لها groupId=org.springframework، بينما مكتبات إطار عمل JSF لها groupId=javax.faces،
  • [artifactId]: اسم عنصر Maven. في مجموعة [org.springframework]، نجد معرّفات العناصر التالية: spring-context، spring-core، spring-beans، ... وفي مجموعة [javax.faces]، نجد معرّف العنصر jsf-api،
  • [version]: رقم إصدار عنصر Maven. وبالتالي، فإن العنصر org.springframework.spring-core له الإصدارات التالية: 2.5.4، 2.5.5، 2.5.6، 2.5.6.SECO1، ...
  • [packaging]: تنسيق الأرتيفاكت، وغالبًا ما يكون war أو jar.

وبالتالي، سيقوم مشروع Maven الخاص بنا بإنشاء ملف [war] (السطر 8) في المجموعة [istia.st] (السطر 5)، باسم [mv-jsf2-01] (السطر 6) وبإصدار [1.0-SNAPSHOT] (السطر 7). يجب أن تحدد هذه المعلومات الأربع بشكل فريد عنصر Maven.

تسرد الأسطر 17–24 تبعيات مشروع Maven، أي قائمة المكتبات التي يتطلبها المشروع. يتم تعريف كل مكتبة من خلال المعلومات الأربع (groupId، artifactId، version، packaging). عندما تكون معلومات التعبئة والتغليف مفقودة، كما هو الحال هنا، يتم استخدام تعبئة jar. يتم إضافة معلومة أخرى: scope، والتي تحدد المراحل التي تحتاج فيها المكتبة خلال دورة حياة المشروع. القيمة الافتراضية هي compile، والتي تشير إلى أن المكتبة مطلوبة لكل من التجميع والتنفيذ. القيمة provided تعني أن المكتبة مطلوبة أثناء التجميع ولكن ليس أثناء التنفيذ. هنا، في وقت التشغيل، سيتم توفيرها بواسطة خادم Tomcat 7.

2.3.2. تشغيل المشروع

نقوم بتشغيل المشروع:

في [1]، يتم تنفيذ مشروع Maven. ثم يتم تشغيل خادم Tomcat إذا لم يكن قد تم تشغيله بالفعل. كما يتم تشغيل متصفح، ويتم طلب عنوان URL لسياق المشروع [2]. ونظرًا لعدم طلب أي مستند، يتم استخدام صفحة index.html أو index.jsp أو index.xhtml إذا كانت موجودة. وهنا، ستكون الصفحة هي [index.jsp].

2.3.3. نظام الملفات لمشروع Maven

  • [1]: يوجد نظام ملفات المشروع في علامة التبويب [Files
  • [2]: توجد مصادر Java في المجلد [src/main/java
  • [3]: توجد صفحات الويب في المجلد [src/main/webapp
  • [4]: تم إنشاء المجلد [target] بواسطة عملية بناء المشروع،
  • [5]: هنا، أنشأ بناء المشروع أرشيفًا [mv-jsf2-01-1.0-SNAPSHOT.war]. هذا هو الأرشيف الذي تم تنفيذه بواسطة خادم Tomcat.

2.3.4. تكوين مشروع لـ JSF

مشروعنا الحالي ليس مشروع JSF. فهو يفتقد مكتبات إطار عمل JSF. لتحويل المشروع الحالي إلى مشروع JSF، اتبع الخطوات التالية:

  • في [1]، قم بالوصول إلى خصائص المشروع،
  • في [2]، حدد فئة [Frameworks
  • في [3]، أضف إطار عمل،
  • في [4]، حدد Java Server Faces،
  • في [5]، يقدم NetBeans الإصدار 2.1 من إطار العمل. اقبله،
  • في [6]، يتم بعد ذلك تحسين المشروع بتبعيات جديدة.

تم تحديث ملف [pom.xml] ليعكس هذا التكوين الجديد:


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
 
  <groupId>istia.st</groupId>
  <artifactId>mv-jsf2-01</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>war</packaging>
 
  <name>mv-jsf2-01</name>
 
  ...
  <dependencies>
    <dependency>
      <groupId>com.sun.faces</groupId>
      <artifactId>jsf-api</artifactId>
      <version>2.1.1-b04</version>
    </dependency>
    <dependency>
      <groupId>com.sun.faces</groupId>
      <artifactId>jsf-impl</artifactId>
      <version>2.1.1-b04</version>
    </dependency>
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>jstl</artifactId>
      <version>1.1.2</version>
    </dependency>
    <dependency>
      <groupId>taglibs</groupId>
      <artifactId>standard</artifactId>
      <version>1.1.2</version>
    </dependency>
    <dependency>
      <groupId>javax</groupId>
      <artifactId>javaee-web-api</artifactId>
      <version>6.0</version>
      <scope>provided</scope>
    </dependency>
  </dependencies>
 
  <build>
    ...
  </build>
  <repositories>
    <repository>
      <URL>http://download.java.net/maven/2/</URL>
      <id>jsf20</id>
      <layout>default</layout>
      <name>Repository for library Library[jsf20]</name>
    </repository>
    <repository>
      <URL>http://repo1.maven.org/maven2/</URL>
      <id>jstl11</id>
      <layout>default</layout>
      <name>Repository for library Library[jstl11]</name>
    </repository>
  </repositories>
</project>

الأسطر 14–33: تمت إضافة تبعيات جديدة. يقوم Maven بتنزيلها تلقائيًا. ويحصل عليها من ما يُسمى بالمستودعات. يتم استخدام المستودع المركزي بشكل افتراضي. يمكن إضافة مستودعات إضافية باستخدام علامة <repository>. هنا، تمت إضافة مستودعين:

  • الأسطر 46–51: مستودع لمكتبة JSF
  • الأسطر 52–57: مستودع لمكتبة JSTL 1.1.

كما تم تحسين المشروع بإضافة صفحة ويب جديدة:

الصفحة [ index.HTML] هي كما يلي:


<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html">
  <h:head>
    <title>Facelet Title</title>
  </h:head>
  <h:body>
    Hello from Facelets
  </h:body>
</html>

لدينا هنا ملف XML (السطر 1). يحتوي على علامات HTML، ولكن بتنسيق XML. وهذا ما يُسمى XHTML. تُسمى التقنية المستخدمة لإنشاء صفحات الويب باستخدام JSF 2 بـ Facelets. ولذلك، يُشار إلى صفحة XHTML أحيانًا بصفحة Facelet.

تحدد السطران 3 و4 علامة <html> بمساحات أسماء XML (xmlns=XML Name Space).

  • يحدد السطر 3 مساحة الاسم الرئيسية http://www.w3.org/1999/xhtml،
  • ويحدد السطر 4 مساحة الاسم http://java.sun.com/jsf/html لعلامات HTML. ستُسبق هذه العلامات بـ h: كما هو موضح في xmlns:h. يمكن العثور على هذه العلامات في الأسطر 5 و7 و8 و10.

عندما يصادف خادم الويب إعلان مساحة اسم، سيبحث في الدلائل [META-INF] في مسار فئات التطبيق عن الملفات ذات اللاحقة .tld (تعريف TagLib). هنا، سيجدها في أرشيف [jsf-impl.jar] [1،2]:

دعونا نلقي نظرة على [3] ملف [HTML_basic.tld]:

<?xml version="1.0" encoding="UTF-8"?>

<taglib xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-jsptaglibrary_2_1.xsd" version="2.1">

<!-- ============== Tag Library Description Elements ============= -->

    <description>
        This tag library contains JavaServer Faces component tags for all
        UIComponent + HTML RenderKit Renderer combinations defined in the
        JavaServer Faces Specification.
    </description>
    <tlib-version>
        2.1
    </tlib-version>
    <short-name>
        h
    </short-name>
    <uri>
        http://java.sun.com/jsf/html
    </uri>

<!-- ============== Tag Library Validator ============= -->
...
  • في السطر 19، عنوان URI لمكتبة العلامات،
  • في السطر 16، اسمها المختصر.

توجد تعريفات العلامات المختلفة <h:xx> في هذا الملف. تتم إدارة هذه العلامات بواسطة فئات Java الموجودة أيضًا في ملف [jsf-impl.jar].

لنعد إلى مشروع JSF الخاص بنا. لقد تم توسيعه بفرع جديد:

يحتوي فرع [Other Sources] [1] على ملفات يجب أن تكون موجودة في مسار فئات المشروع وليست كود Java. ينطبق هذا على ملفات رسائل JSF. لقد رأينا أنه بدون إضافة إطار عمل JSF إلى المشروع، فإن هذا الفرع يكون مفقودًا. لإنشائه، ما عليك سوى إنشاء المجلد [src/main/resources] [3] في علامة التبويب [Files] [2].

وأخيرًا، ظهر مجلد جديد في فرع [Web Pages]:

تم إنشاء مجلد [WEB-INF]، الذي يحتوي على ملف [ web.xml]. يقوم هذا الملف بتكوين تطبيق الويب:


<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
    <context-param>
        <param-name>javax.faces.PROJECT_STAGE</param-name>
        <param-value>Development</param-value>
    </context-param>
    <servlet>
        <servlet-name>Faces Servlet</servlet-name>
        <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>Faces Servlet</servlet-name>
        <URL-pattern>/faces/*</URL-pattern>
    </servlet-mapping>
    <session-config>
        <session-timeout>
            30
        </session-timeout>
    </session-config>
    <welcome-file-list>
        <welcome-file>faces/index.xhtml</welcome-file>
    </welcome-file-list>
</web-app>
  • تحدد الأسطر من 7 إلى 10 سيرفلت، أي فئة جافا قادرة على معالجة طلبات العملاء. ويعمل تطبيق JSF على النحو التالي:

تطبق هذه البنية نمط تصميم MVC (النموذج، العرض، وحدة التحكم). دعونا نستعرض ما تم ذكره سابقًا. تتضمن معالجة طلب العميل الخطوات الأربع التالية:

1 - الطلب - يرسل متصفح العميل طلبًا إلى وحدة التحكم [Faces Servlet]. تتولى وحدة التحكم معالجة جميع طلبات العميل. وهي نقطة دخول التطبيق. وهذا هو الحرف C في MVC،

2 - المعالجة - تقوم وحدة التحكم C بمعالجة هذا الطلب. وللقيام بذلك، تساعدها معالجات الأحداث الخاصة بالتطبيق [2a]. قد تحتاج هذه المعالجات إلى مساعدة من طبقة الأعمال [2b]. بمجرد معالجة طلب العميل، قد يؤدي ذلك إلى استجابات متنوعة. ومن الأمثلة الكلاسيكية على ذلك:

  • صفحة خطأ إذا تعذر معالجة الطلب بشكل صحيح؛
  • صفحة تأكيد في الحالات الأخرى،

3 - التنقل - تختار وحدة التحكم الاستجابة (= العرض) التي سيتم إرسالها إلى العميل. يتضمن اختيار الاستجابة التي سيتم إرسالها إلى العميل عدة خطوات:

  • اختيار Facelet الذي سيقوم بإنشاء الاستجابة. وهذا ما يُسمى العرض V، وهو حرف V في MVC. يعتمد هذا الاختيار عمومًا على نتيجة تنفيذ الإجراء الذي طلبه المستخدم؛
  • تزويد Facelet بالبيانات التي يحتاجها لإنشاء هذا الرد. في الواقع، غالبًا ما يحتوي هذا الرد على معلومات محسوبة بواسطة وحدة التحكم. تشكل هذه المعلومات ما يسمى بنموذج M للعرض، وهو الحرف M في MVC،

لذلك تتكون الخطوة 3 من اختيار عرض V وإنشاء النموذج M المطلوب له.

4 - الاستجابة - يوجه وحدة التحكم C Facelet المحدد لعرض نفسه. يستخدم Facelet النموذج M الذي أعدته وحدة التحكم C لتهيئة الأجزاء الديناميكية من الاستجابة التي يجب أن يرسلها إلى العميل. قد يختلف التنسيق الدقيق لهذه الاستجابة: فقد يكون دفق HTML أو ملف PDF أو Excel، إلخ.

في مشروع JSF:

  • المحرك C هو servlet [javax.faces.webapp.FacesServlet
  • يتم تنفيذ طرق العرض V بواسطة صفحات تستخدم تقنية Facelets،
  • يتم تنفيذ نماذج M ومعالجات الأحداث بواسطة فئات Java التي يشار إليها غالبًا باسم "backing beans" أو، ببساطة، Beans.

دعونا نلقي نظرة أخرى على محتويات ملف [web.xml]:


<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
    <context-param>
        <param-name>javax.faces.PROJECT_STAGE</param-name>
        <param-value>Development</param-value>
    </context-param>
    <servlet>
        <servlet-name>Faces Servlet</servlet-name>
        <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>Faces Servlet</servlet-name>
        <URL-pattern>/faces/*</URL-pattern>
    </servlet-mapping>
    <session-config>
        <session-timeout>
            30
        </session-timeout>
    </session-config>
    <welcome-file-list>
        <welcome-file>faces/index.xhtml</welcome-file>
    </welcome-file-list>
</web-app>
  • الأسطر 12–15: تُستخدم علامة <servlet-mapping> لربط سيرفلت بعنوان URL يطلبه متصفح العميل. هنا، تحدد أن عناوين URL التي تتخذ شكل [/faces/*] يجب أن يتم التعامل معها بواسطة السيرفلت المسمى [Faces Servlet]. يتم تعريف هذا السيرفلت في الأسطر 7–10. ونظرًا لعدم وجود علامات <servlet-mapping> أخرى في الملف، فهذا يعني أن [Faces Servlet] سيتولى معالجة عناوين URL التي تتخذ الشكل [/faces/*] فقط. وقد رأينا أن سياق التطبيق يسمى [/mv-jsf2-01]. وبالتالي، ستكون عناوين URL الخاصة بالعميل التي تتعامل معها [Faces Servlet] بالشكل [http://machine:port/mv-jsf2-01/faces/*]. وسيتم التعامل مع صفحات .html و.jsp افتراضيًا بواسطة حاوية السيرفلت نفسها، وليس بواسطة سيرفلت محدد. ويرجع ذلك إلى أن حاوية السيرفلت تعرف كيفية التعامل معها،
  • الأسطر 7–10: تحدد [Faces Servlet]. نظرًا لأن جميع عناوين URL المقبولة يتم توجيهها إليه، فهو وحدة التحكم C في نموذج MVC،
  • السطر 10: يشير إلى أنه يجب تحميل السيرفلت في الذاكرة بمجرد بدء تشغيل خادم الويب. بشكل افتراضي، يتم تحميل السيرفلت فقط عند استلام أول طلب موجه إليه،
  • الأسطر 3–6: تحدد معلمة لـ [Faces Servlet]. تحدد المعلمة javax.faces.PROJECT_STAGE مرحلة المشروع قيد التشغيل. في مرحلة التطوير، يعرض [Faces Servlet] رسائل خطأ مفيدة لتصحيح الأخطاء. في مرحلة الإنتاج، لا يتم عرض هذه الرسائل بعد الآن،
  • الأسطر 17-19: مدة الجلسة بالدقائق. يتفاعل العميل مع التطبيق من خلال سلسلة من دورات الطلب/الاستجابة. تستخدم كل دورة اتصال TCP/IP خاص بها، والذي يتم إنشاؤه من جديد مع كل دورة. لذلك، إذا أرسل العميل C طلبين، D1 و D2، فلا يمكن للخادم S معرفة أن الطلبين ينتميان إلى نفس العميل C. لا يمتلك الخادم S ذاكرة " " الخاصة بالعميل. هذه إحدى ميزات بروتوكول HTTP (بروتوكول نقل النص التشعبي): يتواصل العميل مع الخادم من خلال سلسلة من دورات طلب العميل/استجابة الخادم، يستخدم كل منها اتصال TCP-IP جديد. ويُعرف هذا ببروتوكول عديم الحالة. في بروتوكولات أخرى، مثل FTP (بروتوكول نقل الملفات)، يستخدم العميل C نفس الاتصال طوال مدة تفاعله مع الخادم S. وبالتالي، يرتبط الاتصال بعميل معين. يعرف الخادم S دائمًا مع من يتعامل. من أجل التعرف على أن الطلب ينتمي إلى عميل معين، يمكن لخادم الويب استخدام تقنية الجلسة:
    • عندما يرسل العميل طلبه الأول، يرسل الخادم S الرد المتوقع بالإضافة إلى رمز مميز، وهو سلسلة عشوائية من الأحرف فريدة لهذا العميل؛
    • مع كل طلب لاحق، يرسل العميل C الرمز الذي تلقّاه إلى الخادم S، مما يسمح للخادم S بالتعرف عليه.

يمكن للتطبيق الآن أن يطلب من الخادم تخزين المعلومات المرتبطة بعميل معين. ويُشار إلى هذا باسم جلسة العميل. يشير السطر 18 إلى أن مدة الجلسة هي 30 دقيقة. وهذا يعني أنه إذا لم يقم العميل C بتقديم طلب جديد في غضون 30 دقيقة، فسيتم إنهاء جلسته وستفقد المعلومات التي تحتويها. وعند تقديم طلبه التالي، ستسير الأمور كما لو كان عميلاً جديداً، وستبدأ جلسة جديدة؛

  • الأسطر 21–23: قائمة الصفحات التي سيتم عرضها عندما يطلب المستخدم السياق دون تحديد صفحة، على سبيل المثال هنا [http://machine:port/mv-jsf2-01]. في هذه الحالة، يتحقق خادم الويب (وليس السيرفلت) مما إذا كان التطبيق قد عرّف علامة <welcome-file-list>. إذا كان الأمر كذلك، فإنه يعرض أول صفحة موجودة في القائمة. إذا لم تكن تلك الصفحة موجودة، فإنه يعرض الصفحة الثانية، وهكذا دواليك حتى يتم العثور على صفحة موجودة. هنا، عندما يطلب العميل عنوان URL [http://machine:port/mv-jsf2-01]، سيتم تقديم عنوان URL [http://machine:port/mv-jsf2-01/index.xhtml].

2.3.5. تشغيل المشروع

عند تشغيل المشروع الجديد، تكون النتيجة المعروضة في المتصفح كما يلي:

  • في [1]، تم طلب السياق دون تحديد مستند،
  • في [2]، كما هو موضح، يتم عرض الصفحة الرئيسية (ملف الترحيب) [index.xhtml].

قد تشعر بالفضول للاطلاع على شفرة المصدر المستلمة [3]:

1
2
3
4
5
6
7
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"><head>
    <title>Facelet Title</title></head><body>
    Hello from Facelets
  </body>
</html>

لقد تلقينا بعض HTML. تمت ترجمة جميع علامات <h:xx> في index.xhtml إلى علامات HTML المقابلة لها.

2.3.6. مستودع Maven المحلي

ذكرنا أن Maven يقوم بتنزيل التبعيات المطلوبة للمشروع وتخزينها محليًا. يمكنك استكشاف هذا المستودع المحلي:

  • في [1]، حدد الخيار [Window / Other / Maven Repository Browser
  • في [2]، تفتح علامة تبويب [Maven Repositories
  • في [3]، تحتوي على فرعين، أحدهما للمستودع المحلي والآخر للمستودع المركزي. هذا الأخير ضخم. لعرض محتوياته، يجب تحديث فهرسه [4]. يستغرق هذا التحديث عدة عشرات من الدقائق.
  • في [5]، المكتبات الموجودة في المستودع المحلي،
  • في [6]، ستجد فرعًا [istia.st] يتوافق مع [groupId] لمشروعنا،
  • في [7]، يمكنك الوصول إلى خصائص المستودع المحلي،
  • في [8]، ترى المسار إلى المستودع المحلي. من المفيد معرفة ذلك لأنه في بعض الأحيان (نادرًا) لا يستخدم Maven أحدث إصدار من المشروع. تقوم بإجراء تغييرات وتلاحظ أنها لا يتم تطبيقها. يمكنك بعد ذلك حذف الفرع يدويًا في المستودع المحلي المطابق لـ [groupId] الخاص بك. هذا يجبر Maven على إعادة إنشاء الفرع من أحدث إصدار من المشروع.

2.3.7. البحث عن أداة باستخدام Maven

الآن دعونا نتعلم كيفية البحث عن أداة باستخدام Maven. لنبدأ بقائمة التبعيات الحالية في ملف [pom.xml]:


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
 
  <groupId>istia.st</groupId>
  <artifactId>mv-jsf2-01</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>war</packaging>
 
  <name>mv-jsf2-01</name>
 
  ...
  <dependencies>
    <dependency>
      <groupId>com.sun.faces</groupId>
      <artifactId>jsf-api</artifactId>
      <version>2.1.1-b04</version>
    </dependency>
    <dependency>
      <groupId>com.sun.faces</groupId>
      <artifactId>jsf-impl</artifactId>
      <version>2.1.1-b04</version>
    </dependency>
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>jstl</artifactId>
      <version>1.1.2</version>
    </dependency>
    <dependency>
      <groupId>taglibs</groupId>
      <artifactId>standard</artifactId>
      <version>1.1.2</version>
    </dependency>
    <dependency>
      <groupId>javax</groupId>
      <artifactId>javaee-web-api</artifactId>
      <version>6.0</version>
      <scope>provided</scope>
    </dependency>
  </dependencies>
 
  <build>
    ...
  </build>
  <repositories>
    <repository>
      <url>http://download.java.net/maven/2/</url>
      <id>jsf20</id>
      <layout>default</layout>
      <name>Repository for library Library[jsf20]</name>
    </repository>
    <repository>
      <url>http://repo1.maven.org/maven2/</url>
      <id>jstl11</id>
      <layout>default</layout>
      <name>Repository for library Library[jstl11]</name>
    </repository>
  </repositories>
</project>

تحدد الأسطر 13–40 التبعيات، وتحدد الأسطر 45–58 المستودعات التي يمكن العثور عليها فيها، بالإضافة إلى المستودع المركزي الذي يُستخدم دائمًا. سنقوم بتعديل التبعيات لاستخدام المكتبات في أحدث إصداراتها.

أولاً، نزيل التبعيات الحالية [1]. ثم يتم تعديل ملف [pom.xml]:


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
...
    <dependencies>
        <dependency>
            <groupId>javax</groupId>
            <artifactId>javaee-web-api</artifactId>
            <version>6.0</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>
...
    <repositories>
        <repository>
            <url>http://download.java.net/maven/2/</url>
            <id>jsf20</id>
            <layout>default</layout>
            <name>Repository for library Library[jsf20]</name>
        </repository>
        <repository>
            <url>http://repo1.maven.org/maven2/</url>
            <id>jstl11</id>
            <layout>default</layout>
            <name>Repository for library Library[jstl11]</name>
        </repository>
    </repositories>
</project>

الأسطر 5–12: لم تعد التبعيات التي تمت إزالتها تظهر في [pom.xml]. والآن، دعونا نبحث عنها في مستودعات Maven.

  • في [1]، نضيف تبعية إلى المشروع؛
  • في [2]، يجب تحديد معلومات حول الأداة التي نبحث عنها (groupId، artifactId، version، packaging (Type)، و scope). نبدأ بتحديد [groupId] [3]،
  • في [4]، نضغط على [مسافة] لعرض قائمة العناصر المحتملة. هنا، [jsf-api] و [jsf-impl]. نختار [jsf-api
  • في [5]، باتباع نفس الإجراء، نختار أحدث إصدار. نوع التعبئة هو jar.

نواصل بهذه الطريقة مع جميع العناصر:

في [6]، تظهر التبعيات المضافة في المشروع. يعكس ملف [pom.xml] هذه التغييرات:


<dependencies>
        <dependency>
            <groupId>com.sun.faces</groupId>
            <artifactId>jsf-api</artifactId>
            <version>2.1.7</version>
            <type>jar</type>
        </dependency>
        <dependency>
            <groupId>com.sun.faces</groupId>
            <artifactId>jsf-impl</artifactId>
            <version>2.1.7</version>
            <type>jar</type>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
            <type>jar</type>
        </dependency>
        <dependency>
            <groupId>taglibs</groupId>
            <artifactId>standard</artifactId>
            <version>1.1.2</version>
            <type>jar</type>
        </dependency>
        <dependency>
            <groupId>javax</groupId>
            <artifactId>javaee-web-api</artifactId>
            <version>6.0</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

الآن لنفترض أننا لا نعرف [groupId] للقطعة التي نريدها. على سبيل المثال، نريد استخدام Hibernate كـ ORM (مُخطِط علاقات الكائنات)، وهذا كل ما نعرفه. يمكننا عندئذٍ الانتقال إلى الموقع [http://mvnrepository.com/]:

في [1]، يمكنك إدخال كلمات رئيسية. اكتب hibernate وقم بتشغيل البحث.

  • في [2]، حدد [groupId] org.hibernate و[artifactId] hibernate-core،
  • في [3]، حدد الإصدار 4.1.2-Final،
  • في [4]، نحصل على كود Maven لنلصقه في ملف [pom.xml]. لنفعل ذلك.

<dependencies>
    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-core</artifactId>
      <version>4.1.2.Final</version>
    </dependency>
    <dependency>
      <groupId>com.sun.faces</groupId>
      <artifactId>jsf-api</artifactId>
      <version>2.1.7</version>
      <type>jar</type>
    </dependency>
    ...
  </dependencies>

نحفظ ملف [pom.xml]. ثم يقوم Maven بتنزيل التبعيات الجديدة. يتطور المشروع على النحو التالي:

  • في [5]، التبعية [hibernate-core-4.1.2-Final]. في المستودع الذي تم العثور عليه فيه، يتم وصف هذا [artifactId] أيضًا بواسطة ملف [pom.xml]. تمت قراءة هذا الملف، واكتشف Maven أن [artifactId] يحتوي على تبعية. يقوم بتنزيلها أيضًا. وسيقوم بذلك لكل [artifactId] تم تنزيله. في النهاية، نجد في [6] تبعيات لم نطلبها مباشرة. يتم الإشارة إليها برمز مختلف عن رمز [artifactId] الرئيسي.

في هذا المستند، نستخدم Maven بشكل أساسي لهذه الميزة. هذا يوفر علينا الحاجة إلى معرفة جميع التبعيات الخاصة بالمكتبة التي نريد استخدامها. نترك Maven يديرها. علاوة على ذلك، من خلال مشاركة ملف [pom.xml] بين المطورين، نضمن أن كل مطور يستخدم بالفعل نفس المكتبات.

في الأمثلة التالية، سنقدم ببساطة ملف [pom.xml] المستخدم. لا يحتاج القارئ سوى استخدامه لتكرار الشروط الموضحة في الوثيقة. علاوة على ذلك، تدعم بيئات تطوير Java الرئيسية (Eclipse، NetBeans، IntelliJ، JDeveloper) مشاريع Maven. وبالتالي، يمكن للقارئ استخدام بيئة التطوير المفضلة لديه لاختبار الأمثلة.

2.4. المثال mv-jsf2-02: معالج الأحداث – التدويل – التنقل بين الصفحات

2.4.1. التطبيق

التطبيق كما يلي:

  • [1]، الصفحة الرئيسية للتطبيق،
  • في [2]، رابطان لتغيير لغة صفحات التطبيق،
  • في [3]، رابط تنقل إلى صفحة أخرى،
  • عند النقر على [3]، يتم عرض الصفحة [4]،
  • الرابط [5] يعيدك إلى الصفحة الرئيسية.
  • في الصفحة الرئيسية [1]، تتيح لك الروابط [2] تغيير اللغة،
  • في [3]، الصفحة الرئيسية باللغة الإنجليزية.

2.4.2. مشروع NetBeans

سنقوم بإنشاء مشروع ويب جديد كما هو موضح في القسم 2.3.1. وسنسميه mv-jsf2-02:

  • في [1]، المشروع الذي تم إنشاؤه،
  • في [2]، قمنا بإزالة الحزمة [istia.st.mvjsf202] والملف [index.jsp
  • في [3]، أضفنا تبعيات Maven باستخدام ملف [pom.xml] التالي:

<dependencies>
        <dependency>
            <groupId>com.sun.faces</groupId>
            <artifactId>jsf-api</artifactId>
            <version>2.1.7</version>
        </dependency>
        <dependency>
            <groupId>com.sun.faces</groupId>
            <artifactId>jsf-impl</artifactId>
            <version>2.1.7</version>
        </dependency>
        <dependency>
            <groupId>javax</groupId>
            <artifactId>javaee-web-api</artifactId>
            <version>6.0</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

التبعيات المضافة هي تبعيات إطار عمل JSF. ما عليك سوى نسخ الأسطر أعلاه إلى ملف [pom.xml] لاستبدال التبعيات القديمة.

  • في [4، 5]: قم بإنشاء مجلد [src/main/resources] في علامة التبويب [Files
  • في [6]، في علامة التبويب [Projects]، أدى ذلك إلى إنشاء فرع [Other Sources].

لدينا الآن مشروع JSF. سنقوم بإنشاء أنواع مختلفة من الملفات فيه:

  • صفحات ويب بتنسيق XHTML،
  • فئات Java،
  • ملفات الرسائل،
  • ملف تكوين مشروع JSF.

دعونا نرى كيفية إنشاء كل نوع من أنواع الملفات:

  • في [1]، نقوم بإنشاء صفحة JSF
  • في [2]، نقوم بإنشاء صفحة [index.xhtml] بتنسيق [Facelets] [3]،
  • في [4]، تم إنشاء ملفين: [index.xhtml] و [WEB-INF/web.xml].

يقوم ملف [ web.xml] بتكوين تطبيق JSF. ويبدو كما يلي:


<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
    <context-param>
        <param-name>javax.faces.PROJECT_STAGE</param-name>
        <param-value>Development</param-value>
    </context-param>
    <servlet>
        <servlet-name>Faces Servlet</servlet-name>
        <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>Faces Servlet</servlet-name>
        <URL-pattern>/faces/*</URL-pattern>
    </servlet-mapping>
    <session-config>
        <session-timeout>
            30
        </session-timeout>
    </session-config>
    <welcome-file-list>
        <welcome-file>faces/index.xhtml</welcome-file>
    </welcome-file-list>
</web-app>

لقد ناقشنا هذا الملف بالفعل في القسم 2.3.4. دعونا نستعرض خصائصه الرئيسية:

  • يتم التعامل مع جميع عناوين URL من النوع faces/* بواسطة السيرفلت [javax.faces.webapp.FacesServlet
  • والصفحة [index.xhtml] هي الصفحة الرئيسية للتطبيق.

الملف [index.xhtml] الذي تم إنشاؤه هو كما يلي:


<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html">
  <h:head>
    <title>Facelet Title</title>
  </h:head>
  <h:body>
    Hello from Facelets
  </h:body>
</html>

لقد سبق أن تناولنا هذا الملف في القسم 2.3.4.

الآن لنقم بإنشاء فئة Java:

  • في [1]، أنشئ فئة Java في فرع [Source Packages
  • في [2]، نسميها ونضعها في حزمة [3]،
  • في [4]، تظهر الفئة التي تم إنشاؤها في المشروع.

رمز الفئة التي تم إنشاؤها هو هيكل الفئة:


/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package istia.st;
 
/**
 *
 * @author Serge Tahé
 */
public class Form {
  
}

أخيرًا، لنقم بإنشاء ملف رسالة:

  • في [1]، أنشئ ملف [Properties
  • في [2]، أدخل اسم الملف، وفي [3]، المجلد الخاص به،
  • في [4]، تم إنشاء ملف [messages.properties].

في بعض الأحيان، يكون من الضروري إنشاء ملف [WEB-INF/faces-config.xml] لتكوين مشروع JSF. كان هذا الملف مطلوبًا مع JSF 1. وهو اختياري مع JSF 2. ومع ذلك، فهو ضروري إذا كان موقع JSF متعدد اللغات. وسيكون هذا هو الحال لاحقًا. لذا سنوضح لك الآن كيفية إنشاء ملف التكوين هذا.

  • في [1]، نقوم بإنشاء ملف تكوين JSF،
  • في [2]، ندخل اسمه، وفي [3] مجلده،
  • في [4]، الملف الذي تم إنشاؤه.

ملف [faces-config.xml] الذي تم إنشاؤه هو كما يلي:


<?xml version='1.0' encoding='UTF-8'?>
 
<!-- =========== FULL CONFIGURATION FILE ================================== -->
 
<faces-config version="2.0"
    xmlns="http://java.sun.com/xml/ns/javaee" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd">
 
 
</faces-config>

العلامة الجذرية هي <faces-config>. نص هذه العلامة فارغ. سنحتاج إلى ملئه لاحقًا.

لدينا الآن جميع العناصر اللازمة لإنشاء مشروع JSF. في الأمثلة التالية، نقدم مشروع JSF الكامل ثم نوضح عناصره بالتفصيل واحدة تلو الأخرى. نقدم الآن مشروعًا لشرح المفاهيم:

  • معالجة أحداث النماذج،
  • تدويل الصفحات على موقع JSF،
  • التنقل بين الصفحات.

يبدو مشروع [mv-jsf2-02] كما يلي. يمكن للقراء العثور عليه على موقع الأمثلة (انظر القسم 1.2).

  • في [1]، ملفات تكوين مشروع JSF،
  • في [2]، صفحات JSF الخاصة بالمشروع،
  • في [3]، فئة Java الواحدة،
  • في [4]، ملفات الرسائل.

2.4.3. صفحة [index.xhtml]

يرسل ملف [index.xhtml] [1] الصفحة [2] إلى متصفح العميل:

الرمز الذي يولد هذه الصفحة هو كما يلي:


<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core">
  <f:view locale="#{changeLocale.locale}">
    <head>
      ...
    </head>
    <body>
      ....
    </body>
  </f:view>
</html>
  • الأسطر 7–9: مساحات الأسماء/مكتبات العلامات المستخدمة في الصفحة. العلامات التي تبدأ بـ h هي علامات HTML، بينما العلامات التي تبدأ بـ f هي علامات خاصة بـ JSF،
  • السطر 10: تُستخدم علامة <f:view> لتحديد الكود الذي يجب على محرك JSF معالجته، حيث تظهر علامات <f:xx>. تسمح لك سمة locale بتحديد لغة العرض للصفحة. هنا، سنستخدم لغتين: الإنجليزية والفرنسية. يتم التعبير عن قيمة سمة locale كتعبير EL (لغة التعبير) #{expression}. يمكن أن يختلف شكل التعبير. غالبًا ما نعبر عنها على أنها bean['key'] أو bean.field. في أمثلةنا، سيكون bean إما فئة Java أو ملف رسائل. مع JSF 1، كان لا بد من إعلان هذه الفاصوليا في ملف [faces-config.xml]. مع JSF 2، لم يعد هذا إلزاميًا لفئات Java. يمكننا الآن استخدام التعليقات التوضيحية التي تحول فئة Java إلى bean معترف بها من قبل JSF 2. يجب الإعلان عن ملف الرسائل في ملف التكوين [faces-config.xml].

2.4.4. حبة [ changeLocale]

في تعبير EL "#{changeLocale.locale}":

  • changeLocale هو اسم bean، وفي هذه الحالة فئة Java ChangeLocale،
  • locale هو حقل من فئة ChangeLocale. يتم تقييم التعبير على أنه [ChangeLocale].getLocale(). بشكل عام، يتم تقييم التعبير #{bean.field} على أنه [Bean].getField()، حيث [Bean] هو مثيل لفئة Java المسمى bean، و getField هو حقل getter المرتبط بحقل bean.

فئة ChangeLocale هي كما يلي:


package utils;
 
import java.io.Serializable;
import javax.faces.bean.ManagedBean;
import javax.enterprise.context.SessionScoped;
 
@ManagedBean
@SessionScoped
public class ChangeLocale implements Serializable{
  // page locale
  private String locale="fr";
  
  public ChangeLocale() {
  }
  
  ...
  public String getLocale() {
    return locale;
  }
  
}
  • السطر 11: حقل locale،
  • السطر 17: حقل الحصول عليه،
  • السطر 7: تعليق ManagedBean يجعل فئة Java ChangeLocale حبة معترف بها من قبل JSF. يتم تحديد الحبة بواسطة اسم. يمكن تعيين هذا باستخدام سمة الاسم الخاصة بالتعليق: @ManagedBean(name="xx"). إذا تم حذف سمة الاسم، يتم استخدام اسم الفئة مع تحويل الحرف الأول إلى حرف صغير. وبالتالي، فإن اسم bean ChangeLocale هو changeLocale. لاحظ أن تعليق ManagedBean ينتمي إلى حزمة javax.faces.bean.ManagedBean وليس إلى حزمة javax.annotations.ManagedBean.
  • السطر 8: تحدد علامة SessionScoped نطاق الفول. هناك عدة نطاقات. سنستخدم عادةً النطاقات الثلاثة التالية:
    • RequestScoped: مدة حياة bean هي مدة دورة طلب المتصفح/استجابة الخادم. إذا كانت هناك حاجة إلى هذا bean مرة أخرى لمعالجة طلب جديد من نفس المتصفح أو من متصفح آخر، فسيتم إنشاء مثيل له مرة أخرى،
    • SessionScoped: مدة حياة الفول هي مدة جلسة عميل معين. يتم إنشاء الفول في البداية لمعالجة أحد طلبات ذلك العميل. ثم يبقى في الذاكرة ضمن جلسة ذلك العميل. عادةً ما يخزن هذا الفول بيانات خاصة بعميل معين. سيتم إتلافه عند إتلاف جلسة العميل،
    • ApplicationScoped: مدة حياة الفول هي مدة حياة التطبيق نفسه. غالبًا ما يتم مشاركة الفول الذي له هذا النطاق بين جميع عملاء التطبيق. يتم تهيئته عمومًا عند بدء تشغيل التطبيق.

توجد هذه التعليقات التوضيحية في حزمتين: javax.enterprise.context.SessionScoped (JSF 2) و javax.faces.bean.SessionScoped (JSF 1). هنا، نستخدم حزمة JSF 2. وهذا يتطلب منا إنشاء ملف [WEB-INF/beans.xml]:

  

يتم إنشاء هذا الملف تلقائيًا بواسطة NetBeans عند استيراد الحزمة [javax.enterprise.context.SessionScoped]. ومحتوياته هي كما يلي:


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://java.sun.com/xml/ns/javaee"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/beans_1_0.xsd">
</beans>

خارج العلامة الجذرية <beans>، يكون الملف فارغًا. وهذا كافٍ. فوجوده فقط هو المطلوب.

أخيرًا، لاحظ أن فئة [ChangeLocale] تنفذ واجهة [Serializable]. وهذا مطلوب للفاصوليا ذات نطاق الجلسة، والتي قد يحتاج خادم الويب إلى تسلسلها في ملفات. سنعود إلى فاصوليا [ChangeLocale] لاحقًا.

2.4.5. ملف الرسائل

لنعد إلى ملف [index.xhtml]:


<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core">
  <f:view locale="#{changeLocale.locale}">
    <head>
      <title><h:outputText value="#{msg['welcome.titre']}" /></title>
    </head>
    <body>
    ...
    </body>
  </f:view>
</html>
  • السطر 8: تعرض علامة <h:outputText> قيمة تعبير EL #{msg['welcome.title']} في الصيغة #{bean['field']}. bean هو إما اسم فئة Java أو اسم ملف رسالة. هنا، هو اسم ملف رسالة. يجب إعلان ملف الرسالة في ملف التكوين [faces-config.xml]. يتم إعلان bean msg على النحو التالي:

<?xml version='1.0' encoding='UTF-8'?>
 
<!-- =========== FULL CONFIGURATION FILE ================================== -->
 
<faces-config version="2.0"
              xmlns="http://java.sun.com/xml/ns/javaee" 
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
              xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd">
 
 
  <application>
    <resource-bundle>
      <base-name>
        messages
      </base-name>
      <var>msg</var>
    </resource-bundle>
  </application>
</faces-config>
  • الأسطر 11–18: تُستخدم علامة <application> لتكوين تطبيق JSF،
  • الأسطر 12–17: تُستخدم علامة <resource-bundle> لتعريف الموارد للتطبيق، وفي هذه الحالة ملف الرسائل،
  • الأسطر 13–15: تحدد علامة <base-name> اسم ملف الرسائل،
  • السطر 14: سيتم تسمية الملف messages[_LanguageCode][_CountryCode].properties. تحدد علامة <base-name> الجزء الأول من الاسم فقط. أما الباقي فهو ضمني. قد يكون هناك عدة ملفات رسائل، واحد لكل لغة:
  • في [1]، نرى أربعة ملفات رسائل تتوافق مع الاسم الأساسي `messages` المحدد في [faces-config.xml
    • messages_fr.properties: يحتوي على رسائل باللغة الفرنسية (الرمز fr)؛
    • messages_en.properties: يحتوي على رسائل باللغة الإنجليزية (الرمز en)؛
    • messages_es_ES.properties: يحتوي على رسائل باللغة الإسبانية (الرمز es) من إسبانيا (الرمز ES). هناك أنواع أخرى من الإسبانية، مثل تلك المستخدمة في بوليفيا (es_BO
    • messages.properties: يستخدمه الخادم عندما لا يكون للغة الجهاز الذي يعمل عليه ملف رسائل مرتبط به. سيتم استخدامه، على سبيل المثال، إذا كان التطبيق يعمل على جهاز في ألمانيا حيث اللغة الافتراضية هي الألمانية (de). نظرًا لعدم وجود ملف [messages_de.properties]، سيستخدم التطبيق ملف [messages.properties
  • في [2]: تخضع رموز اللغات لمعيار دولي،
  • في [3]: ينطبق الأمر نفسه على رموز البلدان.

يتم تعريف اسم ملف الرسائل في السطر 14. سيتم البحث عنه في مسار فئة المشروع (Classpath). إذا كان داخل حزمة، فيجب تعريف تلك الحزمة في السطر 14، على سبيل المثال resources.messages، إذا كان ملف [messages.properties] موجودًا في مجلد [resources] في مسار فئة المشروع. نظرًا لأن الاسم في السطر 14 لا يتضمن حزمة، يجب وضع ملف [messages.properties] في جذر مجلد [src/main/resources]:

في [1]، في علامة التبويب [Projects] لمشروع NetBeans، يتم عرض ملف [messages.properties] كقائمة بإصدارات الرسائل المختلفة المحددة. يتم تحديد الإصدارات من خلال تسلسل يتكون من رمز واحد إلى ثلاثة رموز [languageCode_countryCode_variantCode]. في [1]، تم استخدام [رمز اللغة] فقط: en للغة الإنجليزية، وfr للغة الفرنسية. يتم تخزين كل إصدار في ملف منفصل في نظام الملفات.

في مثالنا، سيحتوي ملف الرسائل الفرنسي [messages_fr.properties] على ما يلي:


welcome.titre=Tutoriel JSF (JavaServer Faces)
welcome.langue1=Fran\u00e7ais
welcome.langue2=Anglais
welcome.page1=Page 1
page1.titre=page1
page1.entete=Page 1
page1.welcome=Page d'accueil

سيبدو ملف [messages_en.properties] كما يلي:


welcome.titre=JSF (JavaServer Faces) Tutorial
welcome.langue1=French
welcome.langue2=English
welcome.page1=Page 1
page1.titre=page1
page1.entete=Page 1
page1.welcome=Welcome page

ملف [messages.properties] مطابق لملف [messages_en.properties]. وفي النهاية، سيكون لمتصفح العميل الخيار بين الصفحات باللغة الفرنسية والصفحات باللغة الإنجليزية.

لنعد إلى ملف [faces-config.xml]، الذي يعلن عن ملف الرسائل:


...
 
  <application>
    <resource-bundle>
      <base-name>
        messages
      </base-name>
      <var>msg</var>
    </resource-bundle>
  </application>
</faces-config>

يشير السطر 8 إلى أن سطراً في ملف الرسائل سيتم الإشارة إليه بواسطة المعرف msg في صفحات JSF. ويُستخدم هذا المعرف في ملف [index.xhtml] الذي تمت مناقشته:


<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core">
  <f:view locale="#{changeLocale.locale}">
    <head>
      <title><h:outputText value="#{msg['welcome.titre']}" /></title>
    </head>
    <body>
      ...
    </body>
  </f:view>
</html>

ستعرض علامة <h:outputText> في السطر 8 قيمة الرسالة (وجود معرّف msg) ذات المفتاح welcome.title. يتم البحث عن هذه الرسالة والعثور عليها في ملف [messages.properties] الخاص باللغة النشطة حاليًا. على سبيل المثال، بالنسبة للغة الفرنسية:


welcome.titre=Tutoriel JSF (JavaServer Faces)

تتبع الرسالة تنسيق المفتاح=القيمة. يصبح السطر 8 من ملف [index.xhtml] كما يلي بعد تقييم التعبير #{msg['welcome.title']}:


      <title><h:outputText value="Tutoriel JSF (JavaServer Faces)" /></title>

تسهل آلية ملف الرسائل هذه تغيير لغة الصفحات في مشروع JSF. ويُشار إلى ذلك باسم تدويل المشروع، أو بشكل أكثر شيوعًا باختصاره i18n، لأن كلمة تدويل تبدأ بحرف i وتنتهي بحرف n، وهناك 18 حرفًا بين حرفي i و n.

2.4.6. النموذج

لنواصل استكشاف محتويات ملف [index.xhtml]:


<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core">
  <f:view locale="#{changeLocale.locale}">
    <head>
      <title><h:outputText value="#{msg['welcome.titre']}" /></title>
    </head>
    <body>
      <h:form id="formulaire">
        <h:panelGrid columns="2">
          <h:commandLink value="#{msg['welcome.langue1']}" action="#{changeLocale.setFrenchLocale}"/>
          <h:commandLink value="#{msg['welcome.langue2']}" action="#{changeLocale.setEnglishLocale}"/>
        </h:panelGrid>
        <h1><h:outputText value="#{msg['welcome.titre']}" /></h1>
        <h:commandLink value="#{msg['welcome.page1']}" action="page1"/>
      </h:form>
    </body>
  </f:view>
</html>
  • الأسطر 11-18: تضع علامة <h:form> نموذجًا. يتكون النموذج عمومًا من:
    • علامات حقول الإدخال (نص، أزرار اختيار، مربعات اختيار، قوائم منسدلة، إلخ)؛
    • علامات التحقق من صحة النموذج (أزرار، روابط). يقوم المستخدم بإرسال مدخلاته إلى الخادم عبر زر أو رابط، والذي سيقوم بمعالجتها،

يمكن تحديد أي علامة JSF بواسطة سمة id. في معظم الأحيان، لا يكون ذلك ضروريًا، وهذا هو الحال بالنسبة لمعظم علامات JSF المستخدمة هنا. ومع ذلك، فإن هذه السمة مفيدة في حالات معينة. في السطر 17، يتم تحديد النموذج بواسطة المعرف "form". في هذا المثال، لن يتم استخدام معرف النموذج وكان من الممكن حذفه.

  • الأسطر 18-21: تحدد علامة `<h:panelGrid>` هنا جدول HTML مكون من عمودين. وهي تولد علامة HTML `<table>`.
  • يحتوي النموذج على ثلاثة روابط تؤدي إلى تشغيله، في الأسطر 19 و20 و23. تحتوي علامة <h:commandLink> على سمتين على الأقل:
    • القيمة: نص الرابط؛
    • action: إما سلسلة C أو مرجع إلى طريقة تعيد سلسلة C عند تنفيذها. يمكن أن تكون سلسلة C هذه:
      • إما اسم صفحة JSF في المشروع،
      • أو اسم محدد في قواعد التنقل في ملف [faces-config.xml] ومرتبط بصفحة JSF في المشروع؛

في كلتا الحالتين، يتم عرض صفحة JSF بمجرد تنفيذ الإجراء المحدد بواسطة السمة action.

دعونا ندرس آليات معالجة النماذج باستخدام الرابط الموجود في السطر 13 كمثال:


         <h:commandLink value="#{msg['welcome.langue1']}" action="#{changeLocale.setFrenchLocale}"/>}"/>

أولاً، يتم استخدام ملف الرسائل لاستبدال التعبير #{msg['welcome.langue1']} بقيمته. بعد التقييم، تصبح العلامة:


<h:commandLink value="Français" action="#{changeLocale.setFrenchLocale}"/>}"/>

سيكون الترجمة HTML لهذه العلامة JSF كما يلي:


<a href="#" onclick="mojarra.jsfcljs(document.getElementById('formulaire'),{'formulaire:j_idt8':'formulaire:j_idt8'},'');return false">Français</a>

مما سيؤدي إلى المظهر البصري التالي:

لاحظ سمة onclick لعلامة HTML <a>. عندما ينقر المستخدم على رابط [French]، سيتم تنفيذ كود JavaScript. يتم تضمين هذا الكود في الصفحة التي يتلقاها المتصفح، ويقوم المتصفح بتنفيذه. يستخدم JavaScript على نطاق واسع في JSF و AJAX (Asynchronous JavaScript and XML). والغرض العام منه هو تحسين قابلية استخدام تطبيقات الويب واستجابتها. وغالبًا ما يتم إنشاؤه تلقائيًا بواسطة أدوات برمجية، لذا لا داعي لفهمه. ومع ذلك، قد يحتاج المطور أحيانًا إلى إضافة كود JavaScript إلى صفحات JSF الخاصة به. وفي مثل هذه الحالات، تكون معرفة JavaScript ضرورية.

ليس من الضروري هنا فهم كود JavaScript الذي تم إنشاؤه لعلامة JSF <h:commandLink>. ومع ذلك، هناك نقطتان جديرتان بالملاحظة:

  • يستخدم كود JavaScript معرف النموذج الذي قمنا بتعيينه لعلامة JSF <h:form
  • يقوم JSF بإنشاء معرفات تلقائية لجميع العلامات التي لم يتم تعريف سمة id فيها. وإليك مثال على ذلك: j_idt8. إن إعطاء العلامات معرفات واضحة يجعل من السهل فهم كود JavaScript الذي تم إنشاؤه إذا لزم الأمر. وينطبق هذا بشكل خاص عندما يتعين على المطور إضافة كود JavaScript بنفسه لمعالجة مكونات الصفحة. وفي هذه الحالة، يحتاج إلى معرفة معرفات مكوناته.

ماذا سيحدث عندما ينقر المستخدم على رابط [الفرنسية] في الصفحة أعلاه؟ دعونا ننظر إلى بنية تطبيق JSF:

ستتلقى وحدة التحكم [Faces Servlet] الطلب من متصفح العميل بالتنسيق HTTP التالي:

1
2
3
4
5
6
POST /mv-jsf2-02/faces/index.xhtml HTTP/1.1
Host: localhost:8080
Content-Type: application/x-www-form-URLencoded
Content-Length: 126

formulaire=formulaire&javax.faces.ViewState=-9139703055324497810%3A8197824608762605653&formulaire%3Aj_idt8=formulaire%3Aj_idt8 
  • السطران 1-2: يطلب المتصفح عنوان URL [http://localhost:8080/mv-jsf2-02/faces/index.xhtml]. وهذا هو الحال دائمًا: يتم إرسال الإدخالات التي تم إجراؤها في نموذج JSF تم الحصول عليه مبدئيًا عبر عنوان URLFormulaire إلى نفس عنوان URL. لدى المتصفح طريقتان لإرسال القيم المدخلة: GET و POST. باستخدام طريقة GET، يتم إرسال القيم المدخلة بواسطة المتصفح في عنوان URL المطلوب. في المثال أعلاه، كان بإمكان المتصفح إرسال السطر الأول التالي:

GET /mv-jsf2-02/faces/index.xhtml?formulaire=formulaire&javax.faces.ViewState=-9139703055324497810%3A8197824608762605653&formulaire%3Aj_idt8=formulaire%3Aj_idt8 HTTP/1.1

باستخدام طريقة POST هنا، يرسل المتصفح القيم التي تم إدخالها إلى الخادم عبر السطر 6.

  • السطر 3: يحدد تنسيق ترميز قيم النموذج،
  • السطر 4: يحدد الحجم بالبايت للسطر 6،
  • السطر 5: سطر فارغ يشير إلى نهاية رؤوس HTTP وبداية 126 بايت من قيم النموذج،
  • السطر 6: قيم النموذج بتنسيق element1=value1&element2=value2& ...، وهو تنسيق الترميز المحدد في السطر 3. في تنسيق الترميز هذا، يتم استبدال أحرف معينة بقيمها السداسية العشرية. وهذا هو الحال في العنصر الأخير:

formulaire=formulaire&javax.faces.ViewState=...&formulaire%3Aj_idt8=formulaire%3Aj_idt8

حيث يمثل %3A حرف النقطتين. وبالتالي، يتم إرسال السلسلة form:j_idt8=form:j_idt8 إلى الخادم. قد تتذكر أننا صادفنا بالفعل المعرف j_idt8 عندما قمنا بفحص كود HTML الذي تم إنشاؤه للعلامة


          <h:commandLink value="#{msg['welcome.langue1']}" action="#{changeLocale.setFrenchLocale}"/>

وقد تم إنشاؤه تلقائيًا بواسطة JSF. المهم هنا هو أن وجود هذا المعرف في سلسلة القيم المرسلة من متصفح العميل يسمح لـ JSF بمعرفة أنه تم النقر على الرابط [French]. ثم سيستخدم سمة action أعلاه لتحديد كيفية معالجة السلسلة المستلمة. تخبر السمة action="#{changeLocale.setFrenchLocale}" JSF أن طلب العميل يجب معالجته بواسطة طريقة [setFrenchLocale] لكائن يسمى changeLocale. تذكر أن هذا الكائن تم تعريفه بواسطة التعليقات التوضيحية في فئة Java [ChangeLocale]:


@ManagedBean
@SessionScoped
public class ChangeLocale implements Serializable{

يتم تعريف اسم الحبة بواسطة سمة الاسم في تعليق @ManagedBean. إذا كانت هذه السمة مفقودة، يتم استخدام اسم الفئة كاسم للحبة مع تحويل الحرف الأول إلى حرف صغير.

لنعد إلى طلب المتصفح:

وإلى علامة <h:commandLink> التي أنشأت الرابط [الفرنسي] الذي نقرنا عليه:


          <h:commandLink value="#{msg['welcome.langue1']}" action="#{changeLocale.setFrenchLocale}"/>

سيقوم وحدة التحكم بإعادة توجيه طلب المتصفح إلى معالج الأحداث المحدد بواسطة سمة action لعلامة <h:commandLink>. يجب أن يكون لمعالج الأحداث M المشار إليه بواسطة سمة action لأمر <h:commandLink> التوقيع التالي:

public String M();
  • لا يتلقى أي معلمات. سنرى أنه يمكنه مع ذلك الوصول إلى طلب العميل؛
  • ويجب أن ترجع نتيجة C من نوع String. يمكن أن تكون هذه السلسلة C:
    • إما اسم صفحة JSF في المشروع؛
    • إما اسم مُعرَّف في قواعد التنقل بملف [faces-config.xml] ومرتبط بصفحة JSF في المشروع؛
    • أو مؤشر فارغ، إذا لم يكن من المفترض أن يغير متصفح العميل الصفحات،

في بنية JSF أعلاه، سيستخدم وحدة التحكم [Faces Servlet] السلسلة C التي يعيدها معالج الأحداث، وإذا لزم الأمر، ملف التكوين [faces-config.xml] الخاص بها لتحديد صفحة JSF التي يجب إرسالها استجابةً للعميل [4].

في العلامة


          <h:commandLink value="#{msg['welcome.langue1']}" action="#{changeLocale.setFrenchLocale}"/>

، فإن معالج الحدث للنقر على الرابط [French] هو الطريقة [changeLocale.setFrenchLocale]، حيث changeLocale هي مثيل للفئة [ utils.ChangeLocale] التي تمت مناقشتها سابقًا:


package utils;
 
import java.io.Serializable;
import javax.enterprise.context.SessionScoped;
import javax.faces.bean.ManagedBean;
 
@ManagedBean
@SessionScoped
public class ChangeLocale implements Serializable{
  // page locale
  private String locale="fr";
  
  public ChangeLocale() {
  }
  
  public String setFrenchLocale(){
    locale="fr";
    return null;
  }
  
  public String setEnglishLocale(){
    locale="en";
    return null;
  }
 
  public String getLocale() {
    return locale;
  }
}

تحتوي طريقة setFrenchLocale بالفعل على توقيع معالجات الأحداث. تذكر أن معالج الأحداث يجب أن يعالج طلب العميل. وبما أنه لا يتلقى أي معلمات، فكيف يمكنه الوصول إلى الطلب؟ هناك عدة طرق للقيام بذلك:

  • غالبًا ما يكون bean B الذي يحتوي على معالج الأحداث لصفحة JSF P هو نفسه الذي يحتوي على النموذج M لتلك الصفحة. وهذا يعني أن bean B يحتوي على حقول سيتم تهيئتها بالقيم التي تم إدخالها في الصفحة P. ويتم ذلك بواسطة وحدة التحكم [Faces Servlet] قبل استدعاء معالج الأحداث الخاص بـ bean B. وبالتالي، سيتمكن هذا المعالج، عبر حقول bean B التي ينتمي إليها، من الوصول إلى القيم التي أدخلها العميل في النموذج وسيكون قادرًا على معالجتها.
  • تتيح الطريقة الثابتة [FacesContext.getCurrentInstance()] من النوع [FacesContext] الوصول إلى سياق تنفيذ طلب JSF الحالي، وهو كائن من النوع [FacesContext]. ويتيح سياق تنفيذ الطلب الذي يتم الحصول عليه بهذه الطريقة الوصول إلى المعلمات التي أرسلها متصفح العميل إلى الخادم باستخدام الطريقة التالية:
Map FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap()

إذا كانت المعلمات التي أرسلها (POST) متصفح العميل هي كما يلي:

formulaire=formulaire&javax.faces.ViewState=...&formulaire%3Aj_id_id21=formulaire%3Aj_id_id21

فستُرجع طريقة getRequestParameterMap() القاموس التالي:

المفتاح
القيمة
نموذج
نموذج
javax.faces.ViewState
...
نموذج:j_id_id21
نموذج:j_id_id21

في العلامة


          <h:commandLink value="#{msg['welcome.langue1']}" action="#{changeLocale.setFrenchLocale}"/>

ما هو المطلوب من معالج الأحداث locale.setFrenchLocale؟ نريده أن يحدد اللغة المستخدمة في التطبيق. في مصطلحات Java، يُسمى هذا "توطين" التطبيق. يتم استخدام هذا التوطين بواسطة العلامة <f:view> في صفحة JSF [index.xhtml]:


  <f:view locale="#{changeLocale.locale}">
    ...
</f:view>

لتغيير لغة الصفحة إلى الفرنسية، ما عليك سوى تعيين السمة locale إلى "fr". ولتغييرها إلى الإنجليزية، قم بتعيينها إلى "en". يتم الحصول على قيمة السمة locale باستخدام التعبير [ChangeLocale].getLocale(). يعرض هذا التعبير قيمة حقل locale في فئة [ChangeLocale]. من هذا، نستنتج كود طريقة [ChangeLocale].setFrenchLocale()، التي من المفترض أن تحول الصفحات إلى اللغة الفرنسية:


  public String setFrenchLocale(){
    locale="fr";
    return null;
}

لقد أوضحنا أن معالج الأحداث يجب أن يُرجع سلسلة نصية بنمط C سيستخدمها [Faces Servlet] للعثور على صفحة JSF لإرسالها استجابةً لمتصفح العميل. إذا كانت الصفحة المراد إرجاعها هي نفس الصفحة التي تتم معالجتها حاليًا، فيمكن لمعالج الأحداث ببساطة إرجاع القيمة null. وهذا ما يتم هنا في السطر 3: نريد إرجاع نفس الصفحة [index.xhtml] ولكن بلغة مختلفة.

لنعد إلى بنية معالجة الطلبات:

تم تنفيذ معالج الأحداث changeLocale.setFrenchLocale وأعاد القيمة null إلى وحدة التحكم [Faces Servlet]. وبالتالي، ستقوم وحدة التحكم بإعادة عرض الصفحة [index.xhtml]. دعونا نلقي نظرة أخرى عليها:


<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core">
  <f:view locale="#{changeLocale.locale}">
    <head>
      <title><h:outputText value="#{msg['welcome.titre']}" /></title>
    </head>
    <body>
      <h:form id="formulaire">
        <h:panelGrid columns="2">
          <h:commandLink value="#{msg['welcome.langue1']}" action="#{changeLocale.setFrenchLocale}"/>
          <h:commandLink value="#{msg['welcome.langue2']}" action="#{changeLocale.setEnglishLocale}"/>
        </h:panelGrid>
        <h1><h:outputText value="#{msg['welcome.titre']}" /></h1>
        <h:commandLink value="#{msg['welcome.page1']}" action="page1"/>
      </h:form>
    </body>
  </f:view>
</html>

في كل مرة يتم فيها تقييم قيمة من النوع #{msg['...']}، يتم استخدام أحد ملفات الرسائل [messages.properties]. الملف المستخدم هو الملف الذي يتوافق مع "ترجمة" الصفحة (السطر 6). نظرًا لأن معالج الأحداث changeLocale.setFrenchLocale يضبط هذه الإعدادات المحلية على fr، فسيتم استخدام ملف [messages_fr.properties]. سيؤدي النقر على رابط [English] (السطر 14) إلى تغيير الإعدادات المحلية إلى en (انظر طريقة changeLocale.setEnglishLocale). سيتم بعد ذلك استخدام ملف [messages_en.properties]، وستظهر الصفحة باللغة الإنجليزية:

في كل مرة يتم فيها عرض صفحة [index.xhtml]، يتم تنفيذ العلامة <f:view>:


  <f:view locale="#{changeLocale.locale}">

وبالتالي يتم إعادة تنفيذ طريقة [ChangeLocale].getLocale(). وبما أننا قد أعطينا bean نطاق Session:


@ManagedBean
@SessionScoped
public class ChangeLocale implements Serializable{

يتم الاحتفاظ بالترجمة التي تم إجراؤها أثناء الطلب للطلبات اللاحقة.

هناك عنصر أخير في صفحة [index.xhtml] يجب فحصه:


<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core">
  <f:view locale="#{changeLocale.locale}">
    <head>
      <title><h:outputText value="#{msg['welcome.titre']}" /></title>
    </head>
    <body>
      <h:form id="formulaire">
        <h:panelGrid columns="2">
          <h:commandLink value="#{msg['welcome.langue1']}" action="#{changeLocale.setFrenchLocale}"/>
          <h:commandLink value="#{msg['welcome.langue2']}" action="#{changeLocale.setEnglishLocale}"/>
        </h:panelGrid>
        <h1><h:outputText value="#{msg['welcome.titre']}" /></h1>
        <h:commandLink value="#{msg['welcome.page1']}" action="page1"/>
      </h:form>
    </body>
  </f:view>
</html>

تحتوي علامة <h:commandLink> في السطر 17 على سمة action محددة بقيمة سلسلة. في هذه الحالة، لا يتم استدعاء أي معالج أحداث لمعالجة الصفحة. يتم إعادة توجيه المستخدم على الفور إلى الصفحة [page1.xhtml]. دعونا ندرس كيفية عمل التطبيق في حالة الاستخدام هذه:

ينقر المستخدم على رابط [Page 1]. يتم إرسال النموذج إلى وحدة التحكم [Faces Servlet]. تتعرف وحدة التحكم في الطلب الذي تتلقاه على أنه تم النقر على رابط [Page 1]. وتفحص العلامة المقابلة:


        <h:commandLink value="#{msg['welcome.page1']}" action="page1"/>

لا يوجد معالج أحداث مرتبط بالرابط. تنتقل وحدة التحكم [Faces Servlet] على الفور إلى الخطوة [3] أعلاه وتعرض الصفحة [page1.xhtml]:

2.4.7. صفحة JSF [page1.xhtml]

ترسل صفحة [page1.xhtml] الدفق التالي إلى متصفح العميل:

 

الرمز الذي يولد هذه الصفحة هو كما يلي:


<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core">
  <f:view locale="#{changeLocale.locale}">
    <head>
      <title><h:outputText value="#{msg['page1.titre']}"/></title>
    </head>
    <body>
      <h1><h:outputText value="#{msg['page1.entete']}"/></h1>
      <h:form>
        <h:commandLink value="#{msg['page1.welcome']}" action="index"/>
      </h:form>
    </body>
  </f:view>
</html>

لا يوجد في هذه الصفحة ما لم يتم شرحه من قبل. يجب على القارئ ربط كود JSF بالصفحة المرسلة إلى متصفح العميل. الرابط للعودة إلى الصفحة الرئيسية:


        <h:commandLink value="#{msg['page1.welcome']}" action="index"/>

سيعرض صفحة [index.xhtml].

2.4.8. تشغيل المشروع

لقد اكتمل مشروعنا الآن. يمكننا تجميعه (تنظيف وتجميع):

  • يؤدي بناء المشروع إلى إنشاء مجلد [target] في علامة التبويب [Files]. داخل هذا المجلد، ستجد أرشيف المشروع [mv-jsf2-02-1.0-SNAPSHOT.war]. هذا هو الأرشيف الذي يتم نشره على الخادم؛
  • في [WEB-INF/classes] [2]، ستجد الفئات المجمعة من مجلد [Source Packages] الخاص بالمشروع بالإضافة إلى الملفات التي كانت موجودة في فرع [Other Sources]، وهنا ملفات الرسائل،
  • في [WEB-INF/lib] [3]، ستجد مكتبات المشروع،
  • في جذر [WEB-INF] [4]، ستجد ملفات تكوين المشروع،
  • في جذر الأرشيف [5]، ستجد صفحات JSF التي كانت موجودة في فرع [Web Pages] للمشروع،
  • بمجرد إنشاء المشروع، يمكن تشغيله [6]. سيتم تنفيذه وفقًا لتكوين وقت التشغيل الخاص به [7]،
  • سيتم تشغيل خادم Tomcat إذا لم يكن قيد التشغيل بالفعل [8]،
  • سيتم تحميل الأرشيف [mv-jsf2-02-1.0-SNAPSHOT.war] على الخادم. وهذا ما يُسمى بنشر المشروع على خادم التطبيق،
  • في [9]، سيُطلب منك تشغيل متصفح عند التنفيذ. سيطلب المتصفح سياق التطبيق [10]، أي عنوان URL [http://localhost:8080/mv-jsf2-02]. وفقًا للقواعد الموجودة في ملف [web.xml] (انظر الصفحة 44)، سيتم تقديم ملف [faces/index.xhtml] إلى متصفح العميل. نظرًا لأن عنوان URL يكون بالصيغة [/faces/*]، فسيتم التعامل معه بواسطة وحدة التحكم [Faces Servlet] (انظر [web.xml] في الصفحة 44). ستقوم وحدة التحكم هذه بمعالجة الصفحة وإرسال إخراج HTML التالي:
 
  • ستقوم وحدة التحكم [Faces Servlet] بعد ذلك بمعالجة الأحداث التي تحدث على هذه الصفحة.

2.4.9. ملف التكوين [faces-config.xml]

استخدمنا ملف [faces-config.xml] التالي:


<?xml version='1.0' encoding='UTF-8'?>
 
<!-- =========== FULL CONFIGURATION FILE ================================== -->
 
<faces-config version="2.0"
              xmlns="http://java.sun.com/xml/ns/javaee" 
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
              xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd">
 
 
  <application>
    <resource-bundle>
      <base-name>
        messages
      </base-name>
      <var>msg</var>
    </resource-bundle>
  </application>
</faces-config>

هذه هي بنية الملفات الدنيا لتطبيق JSF 2 المُعَرَّب. هنا، استخدمنا ميزات جديدة في JSF 2 مقارنةً بـ JSF 1:

  • تحديد الفئات (Beans) ونطاقها باستخدام علامات @ManagedBean و@RequestScoped و@SessionScoped و@ApplicationScoped،
  • التنقل بين الصفحات باستخدام أسماء صفحات XHTML (بدون اللاحقة .xhtml) كمفاتيح تنقل.

يمكنك اختيار عدم استخدام هذه الميزات وبدلاً من ذلك إعلان عناصر مشروع JSF هذه في [faces-config.xml] كما هو الحال في JSF 1. في هذه الحالة، قد يبدو ملف [faces-config.xml] كما يلي:


<?xml version='1.0' encoding='UTF-8'?>
 
<!-- =========== FULL CONFIGURATION FILE ================================== -->
 
<faces-config version="2.0"
              xmlns="http://java.sun.com/xml/ns/javaee" 
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
              xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd">
<!-- application -->
  <application>
    <resource-bundle>
      <base-name>
        messages
      </base-name>
      <var>msg</var>
    </resource-bundle>
  </application>
  
  <!-- managed beans -->
  <managed-bean>
    <managed-bean-name>changeLocale</managed-bean-name>
    <managed-bean-class>utils.ChangeLocale</managed-bean-class>
    <managed-bean-scope>session</managed-bean-scope>
  </managed-bean>
 
   <!-- navigation -->
  <navigation-rule>
    <description/>
    <from-view-id>/index.xhtml</from-view-id>
    <navigation-case>
      <from-outcome>p1</from-outcome>
      <to-view-id>/page1.xhtml</to-view-id>
    </navigation-case>
  </navigation-rule>
 
  <navigation-rule>
    <description/>
    <from-view-id>/page1.xhtml</from-view-id>
    <navigation-case>
      <from-outcome>welcome</from-outcome>
      <to-view-id>/index.xhtml</to-view-id>
    </navigation-case>
  </navigation-rule>
 
 
</faces-config>
  • الأسطر 20–24: إعلان عن bean changeLocale:
    • السطر 21: اسم bean؛
    • السطر 22: الاسم الكامل للفئة المرتبطة بـ bean؛
    • السطر 23: نطاق bean. القيم المحتملة هي request، session، application،
  • الأسطر 27–34: إعلان قاعدة التنقل:
    • السطر 28: يمكن وصف القاعدة. هنا، لم نقم بذلك؛
    • السطر 29: الصفحة التي يبدأ منها التنقل (نقطة البداية)؛
    • الأسطر 30-33: حالة تنقل. قد يكون هناك عدة حالات؛
    • السطر 31: مفتاح التنقل؛
    • السطر 32: الصفحة التي تتنقل إليها.

يمكن عرض قواعد التنقل بطريقة أكثر وضوحًا. عند تحرير ملف [faces-config.xml]، يمكنك استخدام علامة التبويب [PageFlow]:

 

لنفترض أننا نستخدم ملف [faces-config.xml] السابق. كيف سيتغير تطبيقنا؟

  • في فئة [ChangeLocale]، ستختفي علامتا @ManagedBean و@SessionScoped لأن الفول قد تم إعلانه الآن في [faces-config
  • وستصبح عملية التنقل من [index.xhtml] إلى [page1.xhtml] عبر رابط كما يلي:

        <h:commandLink value="#{msg['welcome.page1']}" action="p1"/>

يتم تعيين السمة action لمفتاح التنقل p1 المحدد في [faces-config

  • سيصبح التنقل من [page1.xhtml] إلى [index.xhtml] عبر رابط كما يلي:

        <h:commandLink value="#{msg['page1.welcome']}" action="welcome"/>

نقوم بتعيين مفتاح التنقل </span>**<span style="color: #000000">welcome</span>**<span style="color: #000000">، المحدد في [faces-config]، إلى السمة action؛

  • ولا تحتاج طريقتا setFrenchLocale و setEnglishLocale، اللتان يجب أن تعيدا مفتاح تنقل، إلى التعديل لأنهما كانتا تعيدان في السابق القيمة null للإشارة إلى أن المستخدم بقي على نفس الصفحة.

2.4.10. الخلاصة

لنعد إلى مشروع NetBeans الذي كتبناه:

يتبع هذا المشروع البنية التالية:

في كل مشروع JSF، سنجد العناصر التالية:

  • صفحات JSF [A] التي يتم إرسالها [4] إلى متصفحات العملاء بواسطة وحدة التحكم [Faces Servlet] [3]،
  • ملفات الرسائل [C] التي تسمح لك بتغيير لغة صفحات JSF،
  • فئات Java [B] التي تتعامل مع الأحداث التي تحدث على متصفح العميل [2a، 2b] و/أو تعمل كنماذج لصفحات JSF [3]. في أغلب الأحيان، يتم تطوير واختبار طبقات [business] و [DAO] بشكل منفصل. ثم يتم اختبار طبقة [web] باستخدام طبقة [business] وهمية. إذا كانت طبقات [business] و [DAO] متاحة، فإننا عادة ما نعمل مع ملفات .jar الخاصة بها.
  • ملفات التكوين [D] لربط هذه العناصر المختلفة معًا. تم وصف ملف [web.xml] في الصفحة 44 ونادرًا ما يتم تعديله. وينطبق الأمر نفسه على [faces-config]، حيث سنستخدم دائمًا النسخة المبسطة.

2.5. مثال mv-jsf2-03: نموذج إدخال - مكونات JSF

من الآن فصاعدًا، لن نعرض كيفية بناء المشروع. سنقدم مشاريع جاهزة ونشرح كيفية عملها. يمكن للقارئ تنزيل جميع الأمثلة من موقع الويب الخاص بهذا المستند (انظر القسم 1.2).

2.5.1. التطبيق

يحتوي التطبيق على عرض واحد:

يعرض التطبيق مكونات JSF الرئيسية التي يمكن استخدامها في نموذج الإدخال:

  • يُشير العمود [1] إلى اسم علامة JSF/HTML المستخدمة،
  • العمود [2] يعرض مثالاً لإدخال البيانات لكل علامة من العلامات الموجودة،
  • العمود [3] يعرض قيم الفول التي تعمل كنموذج للصفحة،
  • يتم التحقق من صحة الإدخالات التي تمت في [2] بواسطة الزر [4]. يعمل هذا التحقق من الصحة ببساطة على تحديث bean نموذج الصفحة. ثم يتم إرجاع نفس الصفحة. وبالتالي، بعد التحقق من الصحة، يعرض العمود [3] القيم الجديدة لـ bean النموذج، مما يسمح للمستخدم بالتحقق من تأثير إدخالاته على نموذج الصفحة.

2.5.2. مشروع NetBeans

مشروع NetBeans للتطبيق هو كما يلي:

  • في [1]، ملفات تكوين مشروع JSF،
  • في [2]، الصفحة الوحيدة للمشروع: index.xhtml،
  • في [3]، ورقة أنماط [styles.css] لتكوين مظهر صفحة [index.xhtml]
  • في [4]، فئات Java الخاصة بالمشروع،
  • في [5]، ملف رسائل التطبيق بلغتين: الفرنسية والإنجليزية.

2.5.3. ملف [pom.xml]

نحن نعرض التبعيات فقط:


    <dependencies>
        <dependency>
            <groupId>com.sun.faces</groupId>
            <artifactId>jsf-api</artifactId>
            <version>2.1.7</version>
        </dependency>
        <dependency>
            <groupId>com.sun.faces</groupId>
            <artifactId>jsf-impl</artifactId>
            <version>2.1.7</version>
        </dependency>
        <dependency>
            <groupId>javax</groupId>
            <artifactId>javaee-web-api</artifactId>
            <version>6.0</version>
            <scope>provided</scope>
        </dependency>
</dependencies>

هذه هي التبعيات المطلوبة لمشروع JSF. في الأمثلة التالية، لن يظهر هذا الملف إلا عند حدوث تغيير فيه.

2.5.4. ملف [ web.xml]

تم تكوين ملف [web.xml] بحيث تكون الصفحة [index.xhtml] هي الصفحة الرئيسية للمشروع:


<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
  <context-param>
    <param-name>javax.faces.STATE_SAVING_METHOD</param-name>
    <param-value>client</param-value>
  </context-param>  
  <context-param>
    <param-name>javax.faces.PROJECT_STAGE</param-name>
    <param-value>Development</param-value>
  </context-param>
  <context-param>
    <param-name>javax.faces.FACELETS_SKIP_COMMENTS</param-name>
    <param-value>true</param-value>
  </context-param> 
  <servlet>
    <servlet-name>Faces Servlet</servlet-name>
    <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>Faces Servlet</servlet-name>
    <url-pattern>/faces/*</url-pattern>
  </servlet-mapping>
  <session-config>
    <session-timeout>
      30
    </session-timeout>
  </session-config>
  <welcome-file-list>
    <welcome-file>faces/index.xhtml</welcome-file>
  </welcome-file-list>
</web-app>
  • السطر 30: الصفحة [index.xhtml] هي الصفحة الرئيسية،
  • الأسطر 11–14: معلمة لـ [Faces Servlet]. تطلب أن تكون التعليقات في facelet مثل:

        <!-- langues -->

يتم تجاهلها. بدون هذا المعامل، تتسبب التعليقات في مشكلات يصعب فهمها،

  • الأسطر 3–6: معلمة لـ [Faces Servlet] سيتم شرحها لاحقًا.

2.5.5. ملف [faces-config.xml]

ملف [faces-config.xml] الخاص بالتطبيق هو كما يلي:


<?xml version='1.0' encoding='UTF-8'?>
 
<!-- =========== FULL CONFIGURATION FILE ================================== -->
 
<faces-config version="2.0"
              xmlns="http://java.sun.com/xml/ns/javaee" 
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
              xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd">
 
  <application>
    <resource-bundle>
      <base-name>
        messages
      </base-name>
      <var>msg</var>
    </resource-bundle>
  </application>
</faces-config>
  • الأسطر 11–16: تكوين ملف رسائل التطبيق.

2.5.6. ملف الرسائل [messages.properties]

ملفات الرسائل (انظر [5] في لقطة شاشة المشروع) هي كما يلي:

[messages_fr.properties]


form.langue1=Fran\u00e7ais
form.langue2=Anglais
form.titre=Java Server Faces - les tags
form.headerCol1=Type
form.headerCol2=Champs de saisie
form.headerCol3=Valeurs du modèle de la page
form.loginPrompt=login : 
form.passwdPrompt=mot de passe : 
form.descPrompt=description : 
form.selectOneListBox1Prompt=choix unique : 
form.selectOneListBox2Prompt=choix unique : 
form.selectManyListBoxPrompt=choix multiple : 
form.selectOneMenuPrompt=choix unique : 
form.selectManyMenuPrompt=choix multiple : 
form.selectBooleanCheckboxPrompt=marié(e) : 
form.selectManyCheckboxPrompt=couleurs préférées : 
form.selectOneRadioPrompt=moyen de transport préféré : 
form.submitText=Valider
form.buttonRazText=Raz

يتم عرض هذه الرسائل في المواقع التالية على الصفحة:

فيما يلي النص الإنجليزي للرسائل:

[messages_en.properties]


form.langue1=French
form.langue2=English
form.titre=Java Server Faces - the tags
form.headerCol1=Input Type
form.headerCol2=Input Fields
form.headerCol3=Page Model Values
form.loginPrompt=login : 
form.passwdPrompt=password : 
form.descPrompt=description : 
form.selectOneListBox1Prompt=unique choice : 
form.selectOneListBox2Prompt=unique choice : 
form.selectManyListBoxPrompt=multiple choice : 
form.selectOneMenuPrompt=unique choice : 
form.selectManyMenuPrompt=multiple choice : 
form.selectBooleanCheckboxPrompt=married : 
form.selectManyCheckboxPrompt=preferred colors : 
form.selectOneRadioPrompt=preferred transport means : 
form.submitText=Submit
form.buttonRazText=Reset

2.5.7. نموذج [Form.java] لصفحة [index.xhtml]

في المشروع أعلاه، ستعمل فئة [Form.java] كنموذج أو حبة دعم لصفحة JSF [index.xhtml]. دعونا نوضح مفهوم النموذج هذا بمثال مأخوذ من صفحة [index.xhtml]:


<!-- line 1 -->
          <h:outputText value="inputText"  styleClass="info"/>
          <h:panelGroup>
            <h:outputText value="#{msg['form.loginPrompt']}"/>
            <h:inputText id="inputText" value="#{form.inputText}"/>
          </h:panelGroup>
          <h:outputText value="#{form.inputText}"/>

عندما يتم طلب صفحة [index.xhtml] لأول مرة، يقوم الكود أعلاه بإنشاء الصف الثاني من جدول الإدخال:

يعرض السطر 2 الحقل [1]، وتعرض الأسطر 3-6 الحقل [2]، ويعرض السطر 7 الحقل [3].

يستخدم السطران 5 و7 تعبير EL يشير إلى حبة النموذج المحددة في فئة [Form.java] على النحو التالي:


package forms;
 
import javax.enterprise.context.RequestScoped;
import javax.faces.bean.ManagedBean;
 
 
@ManagedBean
@RequestScoped
public class Form {
  • يحدد السطر 7 حبة غير مسماة. وبالتالي سيكون هذا هو اسم الفئة الذي يبدأ بحرف صغير: form،
  • يتمتع bean بنطاق الطلب. وهذا يعني أنه في دورة طلب العميل/استجابة الخادم، يتم إنشاء مثيل له عندما يحتاج الطلب إليه ويتم إتلافه عند إرجاع الاستجابة إلى العميل.

في الكود أدناه من صفحة [index.xhtml]:


<!-- line 1 -->
          <h:outputText value="inputText"  styleClass="info"/>
          <h:panelGroup>
            <h:outputText value="#{msg['form.loginPrompt']}"/>
            <h:inputText id="inputText" value="#{form.inputText}"/>
          </h:panelGroup>
<h:outputText value="#{form.inputText}"/>

تستخدم السطران 5 و7 قيمة inputText من حبة النموذج. لفهم الروابط بين الصفحة P ونموذجها M، يجب أن نعود إلى دورة طلب العميل/استجابة الخادم ( ) التي تميز تطبيق الويب:

يجب أن نميز بين الحالة التي يتم فيها إرسال الصفحة P كاستجابة للمتصفح (الخطوة 4) — على سبيل المثال، أثناء الطلب الأولي للصفحة — والحالة التي يقوم فيها المستخدم بتشغيل حدث على الصفحة P، والذي يتم معالجته بعد ذلك بواسطة وحدة التحكم [Faces Servlet] (الخطوة 1).

يمكننا التمييز بين هاتين الحالتين من خلال النظر إليهما من منظور المتصفح:

  1. أثناء الطلب الأولي للصفحة، يقوم المتصفح بإجراء عملية GET على عنوان URL للصفحة،
  2. عند إرسال القيم التي تم إدخالها على الصفحة، يقوم المتصفح بإجراء عملية POST على عنوان URL للصفحة.

في كلتا الحالتين، يتم طلب عنوان URL نفسه. اعتمادًا على ما إذا كان طلب المتصفح هو GET أو POST، ستتم معالجة الطلب بشكل مختلف.

[الحالة 1 – الطلب الأولي للصفحة P]

يطلب المتصفح عنوان URL للصفحة باستخدام طلب GET. ستنتقل وحدة التحكم [Faces Servlet] مباشرةً إلى الخطوة [4] من عرض الاستجابة، وسيتم إرسال الصفحة [index.xhtml] إلى العميل. ستقوم وحدة التحكم JSF بإصدار تعليمات لكل علامة على الصفحة لعرضها. لنأخذ مثالاً من السطر 5 من كود [index.xhtml]:


            <h:inputText id="inputText" value="#{form.inputText}"/>

تقوم علامة JSF <h:inputText value="value"/> بإنشاء علامة HTML <input type="text" value="value"/>. تصادف الفئة المسؤولة عن معالجة هذه العلامة التعبير #{form.inputText}، الذي يجب عليها تقييمه:

  • إذا لم يكن bean النموذج موجودًا بعد، يتم إنشاؤه عن طريق إنشاء مثيل لفئة forms.Form،
  • يتم تقييم التعبير #{form.inputText} عن طريق استدعاء طريقة form.getInputText()،
  • يتم إدراج النص <input id="form:inputText" type="text" name="form:inputText" value="text" /> في دفق HTML الذي سيتم إرساله إلى العميل، بافتراض أن طريقة form.getInputText() قد أعادت السلسلة "text". كما سيقوم JSF بتعيين اسم (name) لمكون HTML الموجود في الدفق. يتم تكوين هذا الاسم من معرفات id لمكون JSF الذي تم تحليله ومكونات الأصل الخاصة به، وفي هذه الحالة العلامة <h:form id="form"/>.

لاحظ أنه إذا استخدمت، في الصفحة P، التعبير #{M.field} حيث M هو حبة النموذج للصفحة P، فيجب أن تحتوي هذه الحبة على طريقة getField() عامة. يجب أن يكون النوع الذي ترجع هذه الطريقة قابلاً للتحويل إلى نوع String. فيما يلي نموذج M شائع:

1
2
3
4
private T champ;
public T getChamp(){
    return champ;
} 

حيث T هو نوع يمكن تحويله إلى String، ربما باستخدام طريقة toString.

وبالنسبة لعرض الصفحة P، فإن معالجة السطر:


<h:outputText value="#{form.inputText}"/>

ستكون مشابهة، وسيتم إنشاء الناتج HTML التالي:

texte

داخليًا على الخادم، يتم تمثيل الصفحة P كشجرة مكونات، تعكس شجرة العلامات للصفحة المرسلة إلى العميل. سنشير إلى هذه الشجرة باسم عرض الصفحة أو . يتم تخزين هذه الحالة. ويمكن تخزينها بطريقتين اعتمادًا على التكوين في ملف [web.xml] الخاص بالتطبيق:


<web-app ...>
...
  <context-param>
    <param-name>javax.faces.STATE_SAVING_METHOD</param-name>
    <param-value>client</param-value>
  </context-param>
  <servlet>
    <servlet-name>Faces Servlet</servlet-name>
    <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>
...
</web-app>

تحدد الأسطر 7–11 وحدة التحكم [Faces Servlet]. يمكن تكوينها باستخدام علامات <context-param> متنوعة، بما في ذلك العلامة الموجودة في الأسطر 3–6، والتي تحدد أنه يجب حفظ حالة الصفحة على العميل (المتصفح). القيمة الأخرى الممكنة، في السطر 5، هي **server** للإشارة إلى الحفظ على الخادم. هذه هي القيمة الافتراضية.

عندما يتم حفظ حالة الصفحة على العميل، تضيف وحدة التحكم JSF حقلًا مخفيًا إلى كل صفحة HTML ترسلها، تكون قيمته هي الحالة الحالية للصفحة. يتخذ هذا الحقل المخفي الشكل التالي:

<input type="hidden" name="javax.faces.ViewState" id="javax.faces.ViewState" value="H4sIAAAAAAAAANV...Bnoz8dqAAA=" />

تمثل قيمته، في شكل مشفر، حالة الصفحة المرسلة إلى العميل. من المهم أن نفهم أن هذا الحقل المخفي هو جزء من نموذج الصفحة، وبالتالي سيتم تضمينه في القيم التي ينشرها المتصفح عند إرسال النموذج. باستخدام هذا الحقل المخفي، يمكن لوحدة التحكم JSF استعادة العرض كما تم إرساله إلى العميل.

عندما يتم حفظ حالة الصفحة على الخادم، يتم حفظ حالة الصفحة المرسلة إلى العميل في جلسة عمل العميل. وعندما يرسل متصفح العميل القيم التي تم إدخالها في النموذج، فإنه سيرسل أيضًا رمز جلسة العمل الخاص به. وباستخدام هذا الرمز، سيسترد وحدة التحكم JSF حالة الصفحة المرسلة إلى العميل ويعيدها.

قد يتطلب ترميز حالة صفحة JSF عدة مئات من البايتات. ونظرًا لأن هذه الحالة يتم الاحتفاظ بها لكل مستخدم للتطبيق، فقد تنشأ مشكلات في الذاكرة إذا كان هناك عدد كبير من المستخدمين. ولهذا السبب، اخترنا هنا حفظ حالة الصفحة على العميل (انظر [web.xmlالقسم 2.5.4، الصفحة 66).

[الحالة 2 – معالجة الصفحة P]

نحن الآن في الخطوة [1] أعلاه، حيث سيتلقى وحدة التحكم [Faces Servlet] طلب POST من متصفح العميل الذي أرسلت إليه سابقًا صفحة [index.xhtml]. نحن نتعامل مع معالجة حدث صفحة. ستحدث عدة خطوات قبل أن يتم معالجة الحدث في [2a]. دورة معالجة طلب POST بواسطة وحدة التحكم JSF هي كما يلي:

Image

  • في [A]، بفضل الحقل المخفي `javax.faces.ViewState`، يتم إعادة بناء العرض الذي تم إرساله في البداية إلى متصفح العميل. هنا، تستعيد مكونات الصفحة القيم التي كانت لها في الصفحة المرسلة. يستعيد مكون `inputText` قيمته "text
  • في [B]، تُستخدم القيم التي أرسلها متصفح العميل لتحديث مكونات العرض. وبالتالي، إذا في حقل الإدخال HTML المسمى inputText، كتب المستخدم "jean"، فإن القيمة "jean" تحل محل القيمة "text". يعكس العرض الآن الصفحة كما عدّلها المستخدم ولم يعد كما تم إرسالها إلى المتصفح،
  • في [C]، يتم التحقق من القيم المرسلة. لنفترض أن مكون inputText السابق هو حقل إدخال العمر. يجب أن تكون القيمة المدخلة عددًا صحيحًا. القيم التي يرسلها المتصفح تكون دائمًا من النوع String. قد يكون نوعها النهائي في نموذج M المرتبط بالصفحة P مختلفًا تمامًا. عندئذٍ يحدث تحويل من النوع String إلى نوع آخر T. قد يفشل هذا التحويل. في هذه الحالة، يتم إنهاء دورة الطلب/الاستجابة، ويتم إرسال الصفحة P التي تم إنشاؤها في [B] مرة أخرى إلى متصفح العميل مع رسائل خطأ إذا كان مؤلف الصفحة P قد قدمها. لاحظ أن المستخدم يرى الصفحة تمامًا كما أدخلها، دون أي جهد من جانب المطور. في تقنية أخرى، مثل JSP، يجب على المطور إعادة بناء الصفحة P بنفسه باستخدام القيم التي أدخلها المستخدم. قد تخضع قيمة المكون أيضًا لعملية التحقق من الصحة. وبالاستمرار في مثال مكون inputText، وهو حقل إدخال العمر، يجب ألا تكون القيمة المدخلة عددًا صحيحًا فحسب، بل عددًا صحيحًا ضمن النطاق [1،N]. إذا اجتازت القيمة المدخلة خطوة التحويل، فقد تفشل في خطوة التحقق من الصحة. في هذه الحالة، تكتمل دورة الطلب/الاستجابة أيضًا، ويتم إرسال الصفحة P التي تم إنشاؤها في [B] إلى متصفح العميل،
  • في [D]، إذا اجتازت جميع مكونات الصفحة P خطوات التحويل والتحقق من الصحة، فسيتم تعيين قيمها إلى نموذج M للصفحة P. إذا كانت قيمة حقل الإدخال الناتجة عن العلامة التالية:

        <h:inputText value="#{form.inputText}"/>

هي "jean"، فسيتم تعيين هذه القيمة إلى نموذج النموذج الخاص بالصفحة عن طريق تنفيذ الكود form.setInputText("jean"). لاحظ أنه في النموذج M للصفحة P، يجب أن تحتوي الحقول الخاصة في M التي تخزن قيمة حقل الإدخال في P على طريقة تعيين،

  • بمجرد تحديث النموذج M للصفحة P بالقيم المرسلة، يمكن معالجة الحدث الذي أدى إلى إرسال POST للصفحة P. هذه هي الخطوة [E]. لاحظ أنه إذا كان معالج هذا الحدث ينتمي إلى bean M، فإنه يتمتع بإمكانية الوصول إلى القيم من النموذج P التي تم تخزينها في حقول ذلك bean نفسه.
  • تُرجع الخطوة [E] مفتاح تنقل إلى وحدة التحكم JSF. في أمثلةنا، سيكون هذا دائمًا اسم صفحة XHTML المراد عرضها، بدون اللاحقة .xhtml. هذه هي الخطوة [F]. هناك طريقة أخرى تتمثل في إرجاع مفتاح تنقل سيتم البحث عنه في ملف [faces-config.xml]. وقد وصفنا هذه الحالة.

من ما سبق، يمكننا استنتاج ما يلي:

  • تعرض الصفحة P الحقول C لنموذجها M باستخدام الطرق [M].getC()،
  • يتم تهيئة الحقول C للنموذج M على الصفحة P بالقيم التي تم إدخالها على الصفحة P باستخدام الطرق [M].setC(input). في هذه الخطوة، قد تحدث عمليات تحويل وتحقق من الصحة قد تفشل. في هذه الحالة، لا تتم معالجة الحدث الذي أدى إلى إرسال POST للصفحة P، ويتم إرسال الصفحة مرة أخرى إلى العميل تمامًا كما أدخلها العميل.

سيكون نموذج [Form.java] لصفحة [index.xhtml] كما يلي:


package forms;

import javax.enterprise.context.RequestScoped;
import javax.faces.bean.ManagedBean;
 
 
@ManagedBean
@RequestScoped
public class Form {
  
  /** Creates a new instance of Form */
  public Form() {
  }
  
  // form fields
  private String inputText="texte";
  private String inputSecret="secret";
  private String inputTextArea="ligne1\nligne2\n";
  private String selectOneListBox1="2";
  private String selectOneListBox2="3";
  private String[] selectManyListBox=new String[]{"1","3"};
  private String selectOneMenu="1";
  private String[] selectManyMenu=new String[]{"1","2"};
  private String inputHidden="initial";
  private boolean selectBooleanCheckbox=true;
  private String[] selectManyCheckbox=new String[]{"1","3"};
  private String selectOneRadio="2";
  
  // events
  public String submit(){
    return null;
  }
  
  // getters and setters
  ...
}

تُستخدم الحقول الموجودة في الأسطر 16–27 في الأجزاء التالية من النموذج:

2.5.8. الصفحة [ index.xhtml]

الصفحة [index.xhtml] التي تولد العرض السابق هي كما يلي:


<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core">
 
  <f:view locale="#{changeLocale.locale}">
    <h:head>
      <title>JSF</title>
      <h:outputStylesheet library="css" name="styles.css"/>
    </h:head>
    <h:body style="background-image: url('${request.contextPath}/resources/images/standard.jpg');">
      <h:form id="formulaire">
        <!-- languages -->
        <h:panelGrid columns="2">
          <h:commandLink value="#{msg['form.langue1']}" action="#{changeLocale.setFrenchLocale}"/>
          <h:commandLink value="#{msg['form.langue2']}" action="#{changeLocale.setEnglishLocale}"/>
        </h:panelGrid>
        <h1><h:outputText value="#{msg['form.titre']}"/></h1>
        <h:panelGrid columnClasses="col1,col2,col3" columns="3" border="1">
          <!-- headers -->
          <h:outputText value="#{msg['form.headerCol1']}" styleClass="entete"/>
          <h:outputText value="#{msg['form.headerCol2']}" styleClass="entete"/>
          <h:outputText value="#{msg['form.headerCol3']}" styleClass="entete"/>
          <!-- line 1 -->
          ...
          <!-- line 2 -->
          ...
          <!-- line 3 -->
          ...
          <!-- line 4 -->
          ...
          <!-- line 5 -->
          ...
          <!-- line 6 -->
          ...
          <!-- line 7 -->
          ...
          <!-- line 8 -->
          ...
          <!-- line 9 -->
          ...
          <!-- line 10 -->
          ...
          <!-- line 11 -->
          ...
          <!-- line 12 -->
          ...
        </h:panelGrid>
        <p>
          <h:commandButton type="submit" id="submit" value="#{msg['form.submitText']}"/>
        </p>
      </h:form>
    </h:body>
  </f:view>
</html>

سنقوم بفحص المكونات الرئيسية لهذه الصفحة واحدة تلو الأخرى. لاحظ البنية العامة لنموذج JSF:


<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core">
 
  <f:view ...>
    <h:head>
      ...
    </h:head>
    <h:body ...>
      <h:form id="formulaire">
        ...
        <h:commandButton type="submit" id="submit" value="#{msg['form.submitText']}"/>
        ...
      </h:form>
    </h:body>
  </f:view>
</html>

يجب أن تكون مكونات النموذج داخل علامة <h:form> (الأسطر 12–16). علامة <f:view> (الأسطر 7–18) مطلوبة إذا كان التطبيق متعدد اللغات. بالإضافة إلى ذلك، يجب أن يكون للنموذج طريقة لإرساله (POST)، وغالبًا ما تكون رابطًا أو زرًا كما في السطر 14. يمكن أيضًا إرساله من خلال أحداث مختلفة (تغيير اختيار في قائمة، تغيير الحقل النشط، كتابة حرف في حقل إدخال، إلخ).

2.5.9. نمط النموذج

لجعل أعمدة جدول النموذج أكثر قابلية للقراءة، يتضمن النموذج ورقة أنماط:


  <f:view locale="#{changeLocale.locale}">
    <h:head>
      <title>JSF</title>
      <h:outputStylesheet library="css" name="styles.css"/>
</h:head>
  • السطر 4: يتم تعريف ورقة أنماط الصفحة داخل علامة head في HTML باستخدام العلامة التالية:

<h:outputStylesheet library="css" name="styles.css"/>

ستكون ورقة الأنماط موجودة في المجلد [resources]:

في العلامة:


<h:outputStylesheet library="css" name="styles.css"/>
  • library هو اسم المجلد الذي يحتوي على ورقة الأنماط،
  • name هو اسم ورقة الأنماط.

لنلقِ نظرة على مثال يوضح كيفية استخدام ورقة الأنماط هذه:


        <h:panelGrid columnClasses="col1,col2,col3" columns="3" border="1">

تحدد العلامة <h:panelGrid columns="3"/> شبكة مكونة من ثلاثة أعمدة. تُستخدم السمة columnClasses لتصميم هذه الأعمدة. تحدد القيم col1 و col2 و col3 في السمة columnClasses الأنماط الخاصة بالأعمدة 1 و 2 و 3 من الشبكة. يتم البحث عن هذه الأنماط في ورقة أنماط الصفحة:


.info{
   font-family: Arial,Helvetica,sans-serif;
   font-size: 14px;
   font-weight: bold
}
 
.col1{
   background-color: #ccccff
}
 
.col2{
   background-color: #ffcccc
}
 
.col3{
   background-color: #ffcc66
}
 
.entete{
   font-family: 'Times New Roman',Times,serif;
   font-size: 14px;
   font-weight: bold
}
  • الأسطر 7–9: النمط المسمى col1،
  • الأسطر 11–13: النمط المسمى col2،
  • الأسطر 15–17: النمط المسمى col3،

تحدد هذه الأنماط الثلاثة لون خلفية كل عمود.

  • الأسطر 19–23: يُستخدم النمط `entete` لتحديد نمط النص في الصف الأول من الجدول:

          <!-- entêtes -->
          <h:outputText value="#{msg['form.headerCol1']}" styleClass="entete"/>
          <h:outputText value="#{msg['form.headerCol2']}" styleClass="entete"/>
          <h:outputText value="#{msg['form.headerCol3']}" styleClass="entete"/>
  • الأسطر 1–5: يُستخدم نمط "info" لتحديد نمط النص في العمود الأول من الجدول:

          <!-- ligne 1 -->
          <h:outputText value="inputText"  styleClass="info"/>

لن نتطرق إلى استخدام أوراق الأنماط، لأنها تستحق كتابًا بحد ذاتها، علاوة على أن تطويرها غالبًا ما يُعهد به إلى متخصصين. ومع ذلك، اخترنا استخدام ورقة أنماط بسيطة لتذكير القراء بأن استخدامها ضروري.

الآن دعونا نلقي نظرة على كيفية تعريف صورة خلفية الصفحة:


<h:body style="background-image: url('${request.contextPath}/resources/images/standard.jpg');">

يتم تعيين صورة الخلفية بواسطة سمة style الخاصة بعلامة <h:body>. تتيح لك هذه السمة تعيين عناصر الأنماط. توجد صورة الخلفية في المجلد [resources/images/standard.jpg]:

يمكن الوصول إلى هذه الصورة عبر عنوان URL [/mv-jsf2-03/resources/images/standard.jpg]. وبالتالي، يمكننا كتابة:


<h:body style="background-image: url('mv-jsf2-03/resources/images/standard.jpg');">

/mv-jsf2-03 هو سياق التطبيق. يتم تعيين هذا السياق بواسطة مسؤول خادم الويب، وبالتالي قد يتغير. يمكن الحصول على هذا السياق باستخدام تعبير EL ${request.contextPath}. لذلك، نفضل سمة النمط التالية:


style="background-image: url('${request.contextPath}/resources/images/standard.jpg');"

والتي ستكون صالحة بغض النظر عن السياق.

2.5.10. دورتا طلب العميل/استجابة الخادم للنموذج

دعونا نراجع ما تم شرحه بالفعل في القسم 2.5.7 في حالة عامة ونطبقه على النموذج قيد الدراسة. سيتم اختبار هذا النموذج في بيئة JSF القياسية:

هنا، لن تكون هناك معالجات أحداث أو طبقة [الأعمال]. وبالتالي، لن تكون الخطوات [2x] موجودة. سنميز بين الحالة التي يطلب فيها المتصفح النموذج F في البداية والحالة التي يقوم فيها المستخدم بتشغيل حدث في النموذج F، والذي تتم معالجته بعد ذلك بواسطة وحدة التحكم [Faces Servlet]. هناك دورتان متميزتان لطلب العميل/استجابة الخادم.

  • الأولى، التي تتوافق مع طلب الصفحة الأولي، يتم تشغيلها بواسطة عملية GET من المتصفح على عنوان URL للنموذج،
  • والثانية، التي تتوافق مع إرسال القيم التي تم إدخالها على الصفحة، يتم تشغيلها بواسطة عملية POST على نفس عنوان URL.

اعتمادًا على ما إذا كان طلب المتصفح هو GET أو POST، تعالج وحدة التحكم [Faces Servlet] الطلب بشكل مختلف.

[الحالة 1 – الطلب الأولي للنموذج F]

يطلب المتصفح عنوان URL للصفحة باستخدام GET. ستنتقل وحدة التحكم [Faces Servlet] مباشرةً إلى الخطوة [4] لعرض الاستجابة. سيتم تهيئة النموذج [index.xhtml] بواسطة نموذجه [Form.java] وإرساله إلى العميل، الذي يتلقى العرض التالي:

Image

في هذه الحالة، تكون تبادلات HTTP بين العميل والخادم كما يلي:

طلب HTTP من العميل:

1
2
3
4
5
6
7
8
GET /mv-jsf2-03/ HTTP/1.1
Host: localhost:8080
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:12.0) Gecko/20100101 Firefox/12.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: fr,fr-fr;q=0.8,en;q=0.6,en-us;q=0.4,es;q=0.2
Accept-Encoding: gzip, deflate
DNT: 1
Connection: keep-alive

يُظهر السطر 1 طلب GET للمتصفح.

استجابة HTTP من الخادم:

1
2
3
4
5
6
7
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
X-Powered-By: JSF/2.0
Set-Cookie: JSESSIONID=F6E66136BF00EEE026ADAB1BBEBFD587; Path=/mv-jsf2-03/; HTTPOnly
Content-Type: text/html;charset=UTF-8
Content-Length: 7371
Date: Tue, 15 May 2012 09:04:57 GMT

غير معروض هنا، يتبع السطر 7 سطر فارغ ورمز HTML للنموذج. هذا هو الرمز الذي يفسره المتصفح ويعرضه.

[الحالة 2 – معالجة القيم المدخلة في النموذج F]

يقوم المستخدم بملء النموذج وإرساله باستخدام زر [إرسال]. ثم يرسل المتصفح طلب POST إلى عنوان URL الخاص بالنموذج. يقوم وحدة التحكم [Faces Servlet] بمعالجة هذا الطلب، وتحديث نموذج [Form.java] الخاص بالنموذج [index.xhtml]، وإرجاع النموذج [index.xhtml] المحدث بناءً على هذا النموذج الجديد. دعونا ندرس هذه الدورة باستخدام مثال:

Image

في الأعلى، أدخل المستخدم بياناته وأرسلها. ورداً على ذلك، يتلقى العرض التالي:

Image

في هذه الحالة، تتم عمليات تبادل HTTP بين العميل والخادم على النحو التالي:

طلب HTTP من العميل:

POST /mv-jsf2-03/faces/index.xhtml HTTP/1.1
Host: localhost:8080
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:12.0) Gecko/20100101 Firefox/12.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: fr,fr-fr;q=0.8,en;q=0.6,en-us;q=0.4,es;q=0.2
Accept-Encoding: gzip, deflate
DNT: 1
Connection: keep-alive
Referer: http://localhost:8080/mv-jsf2-03/faces/index.xhtml
Cookie: JSESSIONID=374CC5F1D2ACAC182A5747A443651E36
Content-Type: application/x-www-form-URLencoded
Content-Length: 1543

formulaire=formulaire&formulaire%3AinputText=nouveau+texte&formulaire%3AinputSecret=mdp&formulaire%3AinputTextArea=Tutoriel+JSF%0D%0A&formulaire%3AselectOneListBox1=3&formulaire%3AselectOneListBox2=5&formulaire%3AselectManyListBox=3&formulaire%3AselectManyListBox=4&formulaire%3AselectManyListBox=5&formulaire%3AselectOneMenu=4&formulaire%3AselectManyMenu=5&formulaire%3AinputHidden=initial&formulaire%3AselectManyCheckbox=2&formulaire%3AselectManyCheckbox=3&formulaire%3AselectManyCheckbox=4&formulaire%3AselectOneRadio=4&formulaire%3Asubmit=Valider&javax.faces.ViewState=H4sIAAAAAAAAAJVUT0g...P4BKm1E4F0FAAA  

في السطر 1، طلب POST الذي أرسله المتصفح. في السطر 14، القيم التي أدخلها المستخدم. على سبيل المثال، يمكنك رؤية النص الذي تم إدخاله في حقل الإدخال:

formulaire%3AinputText=nouveau+texte

في السطر 14، تم إرسال الحقل المخفي javax.faces.ViewState. يمثل هذا الحقل، في شكل مشفر، حالة النموذج كما تم إرسالها في البداية إلى المتصفح أثناء طلب GET الأولي.

استجابة HTTP من الخادم:

1
2
3
4
5
6
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
X-Powered-By: JSF/2.0
Content-Type: text/html;charset=UTF-8
Content-Length: 7299
Date: Tue, 15 May 2012 09:37:17 GMT

غير معروض هنا، يتبع السطر 6 سطر فارغ ورمز HTML للنموذج، الذي تم تحديثه بواسطة القالب الجديد المستمد من طلب POST.

سنقوم الآن بفحص المكونات المختلفة لهذا النموذج.

2.5.11. علامة <h:inputText>

تقوم علامة <h:inputText> بإنشاء علامة HTML <input type="text" ...>.

انظر إلى الكود التالي:


          <!-- line 1 -->
          <h:outputText value="inputText"  styleClass="info"/>
          <h:panelGroup>
            <h:outputText value="#{msg['form.loginPrompt']}"/>
            <h:inputText id="inputText" value="#{form.inputText}"/>
          </h:panelGroup>
<h:outputText value="#{form.inputText}"/>

وقالبها [Form.java]:


  private String inputText="texte";
 
  public String getInputText() {
    return inputText;
  }
  
  public void setInputText(String inputText) {
    this.inputText = inputText;
}

عندما يتم طلب صفحة [index.html] لأول مرة، تكون الصفحة الناتجة كما يلي:

  • السطر 2 من كود XHTML يُنشئ [1]،
  • وتسمح العلامة <h:panelGroup> (الأسطر 3–6) بتجميع عناصر متعددة داخل خلية واحدة من الجدول الذي تم إنشاؤه بواسطة العلامة <h:panelGrid> في السطر 20 من كود الصفحة الكامل (انظر القسم 2.5.8). يتم إنشاء النص [2] بواسطة السطر 4. يتم إنشاء حقل الإدخال [3] بواسطة السطر [5]. هنا، تم استخدام طريقة getInputText من [Form.java] (الأسطر 3-5 من كود Java) لإنشاء نص حقل الإدخال،
  • السطر 7 من كود XHTML يُنشئ [4]. مرة أخرى، تُستخدم طريقة getInputText من [Form.java] لإنشاء النص [4].

إخراج HTML الذي تم إنشاؤه بواسطة صفحة XHTML هو كما يلي:


<tr>
<td class="col1"><span class="info">inputText</span></td>
<td class="col2">login : <input id="formulaire:inputText" type="text" name="formulaire:inputText" value="texte" /></td>
<td class="col3">texte</td>
</tr>

يتم إنشاء علامات HTML <tr> و <td> بواسطة علامة <h:panelGrid> المستخدمة لإنشاء جدول النموذج.

الآن، أدناه، دعونا ندخل قيمة في حقل الإدخال [1] ونرسل النموذج باستخدام زر [Submit] [2]. نتلقى الصفحة التالية كرد [3، 4]:

يتم نشر قيمة الحقل [1] على النحو التالي:

formulaire%3AinputText=nouveau+texte

في [2]، يتم إرسال النموذج باستخدام الزر التالي:


          <h:commandButton id="submit" type="submit" value="#{msg['form.submitText']}"/>

لا تحتوي علامة <h:commandButton> على سمة action. في هذه الحالة، لا يتم استدعاء أي معالج أحداث ولا يتم تطبيق أي قاعدة تنقل. بعد المعالجة، يتم إرجاع نفس الصفحة. دعونا نستعرض دورة معالجتها:

Image

  • في [A]، يتم استعادة الصفحة P تمامًا كما تم إرسالها. وهذا يعني أن المكون الذي يحمل المعرف inputText يتم استعادته بقيمته الأولية "text
  • في [B]، يتم تعيين القيم التي أرسلها المتصفح (التي أدخلها المستخدم) إلى مكونات الصفحة P. هنا، يتلقى المكون ذو المعرف inputText القيمة "new text
  • في [C]، تتم عمليات التحويل والتحقق من الصحة. هنا، لا توجد أي منها. في النموذج M، يكون الحقل المرتبط بالمكون الذي يحمل المعرف inputText كما يلي:

private String inputText="texte";

نظرًا لأن القيم المدخلة من النوع String، فلا حاجة إلى أي تحويل. علاوة على ذلك، لم يتم إنشاء أي قواعد للتحقق من الصحة. سنقوم بإنشائها لاحقًا.

  • في [D]، يتم تعيين القيم المدخلة إلى النموذج. يتلقى حقل inputText في [Form.java] القيمة "new text
  • في [E]، لا يحدث شيء لأنه لم يتم ربط أي معالج أحداث بالزر [Validate].
  • في [F]، يتم إرسال الصفحة P مرة أخرى إلى العميل لأن زر [Validate] لا يحتوي على سمة action. ثم يتم تنفيذ الأسطر التالية من [index.xhtml]:

          <!-- line 1 -->
          <h:outputText value="inputText"  styleClass="info"/>
          <h:panelGroup>
            <h:outputText value="#{msg['form.loginPrompt']}"/>
            <h:inputText id="inputText" value="#{form.inputText}"/>
          </h:panelGroup>
<h:outputText value="#{form.inputText}"/>

تستخدم السطران 5 و7 قيمة حقل inputText في النموذج، والتي أصبحت الآن "نص جديد". وينتج عن ذلك العرض التالي:

2.5.12. علامة <h:inputSecret>

تقوم علامة <h:inputSecret> بإنشاء علامة HTML <input type="password" ...>. وهي حقل إدخال مشابه لعلامة JSF <h:inputText>، باستثناء أن كل حرف يكتبه المستخدم يتم استبداله بصريًا بعلامة نجمية (*).

انظر إلى الكود التالي:


          <!-- line 2 -->
          <h:outputText value="inputSecret"  styleClass="info"/>
          <h:panelGroup>
            <h:outputText value="#{msg['form.passwdPrompt']}"/>
            <h:inputSecret id="inputSecret" value="#{form.inputSecret}"/>
          </h:panelGroup>
<h:outputText value="#{form.inputSecret}"/>

وقوالبها في [Form.java]:


private String inputSecret="secret";

عندما يتم طلب صفحة [index.xhtml] لأول مرة، تكون الصفحة الناتجة كما يلي:

  • السطر 2 من كود XHTML يُنشئ [1]
  • يتم إنشاء النص [2] بواسطة السطر 4. يتم إنشاء حقل الإدخال [3] بواسطة السطر [5]. عادةً، كان ينبغي استخدام طريقة getInputSecret في [Form.java] لإنشاء النص لحقل الإدخال. هناك استثناء عندما يكون حقل الإدخال من النوع "password". تُستخدم علامة <h:inputSecret> فقط لقراءة الإدخال، وليس لعرضه.
  • السطر 7 من كود XHTML يولد [4]. هنا، تم استخدام طريقة getInputSecret من [Form.java] لتوليد النص [4] (انظر السطر 1 من كود Java).

إخراج HTML الذي تم إنشاؤه بواسطة صفحة XHTML هو كما يلي:


<tr>
<td class="col1"><span class="info">inputSecret</span></td>
<td class="col2">mot de passe : <input id="formulaire:inputSecret" type="password" name="formulaire:inputSecret" value="" /></td>
<td class="col3">secret</td>
</tr>
  • السطر 3: علامة HTML <input type="password" .../> التي تم إنشاؤها بواسطة علامة JSF <h:inputSecret>

الآن، أدناه، دعونا ندخل قيمة في حقل الإدخال [1] ونرسل النموذج باستخدام زر [Submit] [2]. نحصل على الصفحة التالية كرد [3]:

يتم إرسال قيمة الحقل [1] على النحو التالي:

formulaire%3AinputSecret=mdp

أدى إرسال النموذج عبر [2] إلى تحديث نموذج [Form.java] بالمدخلات الواردة من [1]. ثم تلقى حقل inputSecret في [Form.java] القيمة "mdp". ونظرًا لأن نموذج [index.xhtml] لم يحدد أي قواعد تنقل أو معالجات أحداث، يتم إعادة عرضه بعد تحديث نموذجه. ثم نعود إلى العرض الذي ظهر عند طلب صفحة [index.xhtml] في البداية، حيث تغيرت فقط قيمة حقل inputSecret في النموذج [3].

2.5.13. علامة <h:inputTextArea>

تقوم علامة <h:inputTextArea> بإنشاء علامة HTML <textarea ...>text</textarea>. وهي حقل إدخال مشابه لعلامة JSF <h:inputText>، باستثناء أنه يمكنك هنا كتابة عدة أسطر من النص.

انظر إلى الكود التالي:


          <!-- line 3 -->
          <h:outputText value="inputTextArea" styleClass="info"/>          
          <h:panelGroup>
            <h:outputText value="#{msg['form.descPrompt']}"/>
            <h:inputTextarea id="inputTextArea" value="#{form.inputTextArea}" rows="4"/>
          </h:panelGroup>         
<h:outputText value="#{form.inputTextArea}"/>

وقوالبها في [Form.java]:


private String inputTextArea="ligne1\nligne2\n";

عندما يتم طلب صفحة [index.xhtml] لأول مرة، تكون الصفحة الناتجة كما يلي:

  • السطر 2 من كود XHTML يولد [1]،
  • يتم إنشاء النص [2] بواسطة السطر 4. يتم إنشاء حقل الإدخال [3] بواسطة السطر [5]. تم إنشاء محتواه عن طريق استدعاء طريقة getInputTextArea الخاصة بالقالب، والتي أعادت القيمة المحددة في السطر 1 من كود Java أعلاه،
  • السطر 7 من كود XHTML يولد [4]. هنا، تم استخدام طريقة getInputTextArea من [Form.java] مرة أخرى. احتوت السلسلة "line1\nline2" على فواصل أسطر \n. وهي لا تزال موجودة. ولكن عند إدراجها في دفق HTML، يتم عرضها كمسافات بواسطة المتصفحات. علامة HTML <textarea>، التي تعرض [3]، تفسر فواصل الأسطر بشكل صحيح.

إخراج HTML الذي تم إنشاؤه بواسطة صفحة XHTML هو كما يلي:


<tr>
<td class="col1"><span class="info">inputTextArea</span></td>
<td class="col2">description : <textarea id="formulaire:inputTextArea" name="formulaire:inputTextArea" rows="4">ligne1
ligne2
</textarea></td>
<td class="col3">ligne1
ligne2
</td>
</tr>
  • الأسطر 3-5: علامة HTML <textarea>...</textarea> التي تم إنشاؤها بواسطة علامة JSF <h:inputTextArea>

الآن، أدناه، دعونا ندخل قيمة في حقل الإدخال [1] ونرسل النموذج باستخدام زر [إرسال] [2]. ونحصل على الصفحة التالية كرد [3]:

قيمة الحقل [1] الذي تم إرساله هي كما يلي:

formulaire%3AinputTextArea=Tutoriel+JSF%0D%0Apartie+1%0D%0A

أدى إرسال النموذج عبر [2] إلى تحديث نموذج [Form.java] بالمدخلات من [1]. ثم تلقى حقل textArea في [Form.java] القيمة "JSF Tutorial\npart1". تظهر إعادة تحميل [index.xhtml] أن حقل textArea في النموذج قد تم تحديثه بالفعل [3].

2.5.14. علامة <h:selectOneListBox>

تقوم علامة <h:selectOneListBox> بإنشاء علامة HTML <select>...</select>. بصريًا، تقوم بإنشاء قائمة منسدلة أو قائمة مع شريط تمرير.

انظر إلى الكود التالي:


<!-- line 4 -->
          <h:outputText value="selectOneListBox (size=1)" styleClass="info"/>
          <h:panelGroup>
            <h:outputText value="#{msg['form.selectOneListBox1Prompt']}"/>
            <h:selectOneListbox id="selectOneListBox1" value="#{form.selectOneListBox1}" size="1">
              <f:selectItem itemValue="1" itemLabel="un"/>
              <f:selectItem itemValue="2" itemLabel="deux"/>
              <f:selectItem itemValue="3" itemLabel="trois"/>
            </h:selectOneListbox>
          </h:panelGroup>
          <h:outputText value="#{form.selectOneListBox1}"/>

وقوالبها في [Form.java]:


private String selectOneListBox1="2";

عندما يتم طلب الصفحة [index.xhtml] لأول مرة، تكون الصفحة الناتجة كما يلي:

  • السطر 2 من كود XHTML يُنشئ [1]
  • يتم إنشاء النص [2] بواسطة السطر 4. يتم إنشاء القائمة المنسدلة [3] بواسطة الأسطر [5-9]. إن قيمة السمة size="1" هي التي تجعل القائمة تعرض عنصرًا واحدًا فقط. إذا كانت هذه السمة مفقودة، فإن القيمة الافتراضية لسمة size هي 1. تم إنشاء عناصر القائمة بواسطة علامات <f:selectItem> في الأسطر 6–8. هذه العلامات لها الصيغة التالية:

<f:selectItem itemValue="valeur" itemLabel="texte"/>

قيمة السمة itemLabel هي ما يتم عرضه في القائمة. قيمة السمة itemValue هي قيمة العنصر. هذه هي القيمة التي سيتم إرسالها إلى وحدة التحكم [Faces Servlet] إذا تم تحديد العنصر من القائمة المنسدلة.

تم تحديد العنصر المعروض في [3] عن طريق استدعاء الأسلوب getSelectOneListBox1() (السطر 5). أدت النتيجة "2" التي تم الحصول عليها (السطر 1 من كود Java) إلى عرض العنصر الموجود في السطر 7 من القائمة المنسدلة، لأن سمة itemValue الخاصة به هي "2"،

  • السطر 11 من كود XHTML يولد [4]. هنا، تم استخدام طريقة getSelectOneListBox1 من [Form.java] مرة أخرى.

إخراج HTML الذي تم إنشاؤه بواسطة صفحة XHTML هو كما يلي:


<tr>
<td class="col1"><span class="info">selectOneListBox (size=1)</span></td>
<td class="col2">choix unique : <select id="formulaire:selectOneListBox1" name="formulaire:selectOneListBox1" size="1">
    <option value="1">un</option>
    <option value="2" selected="selected">deux</option>
    <option value="3">trois</option>
</select></td>
<td class="col3">2</td>
</tr>
  • السطران 3 و7: علامة HTML <select ...>...</select> التي تم إنشاؤها بواسطة علامة JSF <h:selectOneListBox
  • الأسطر 4–6: علامات HTML <option ...> ... </option> التي تم إنشاؤها بواسطة علامات JSF <f:selectItem
  • السطر 5: إن حقيقة أن العنصر الذي له القيمة value="2" قد تم تحديده في القائمة تنعكس من خلال وجود السمة selected="selected".

الآن، أدناه، دعونا نختار [1] قيمة جديدة من القائمة ونرسل النموذج باستخدام زر [Submit] [2]. نتلقى الصفحة التالية كرد [3]:

قيمة الحقل [1] الذي تم إرساله هي كما يلي:

formulaire%3AselectOneListBox1=3

أدى إرسال النموذج عبر [2] إلى تحديث نموذج [Form.java] بالمدخلة [1]. عنصر HTML


    <option value="3">trois</option>

تم تحديده. أرسل المتصفح السلسلة "3" كقيمة لمكون JSF الذي أنشأ القائمة المنسدلة:


            <h:selectOneListbox id="selectOneListBox1" value="#{form.selectOneListBox1}" size="1">

سيستخدم وحدة التحكم JSF الطريقة setSelectOneListBox1("3") لتحديث نموذج القائمة المنسدلة. أيضًا، بعد هذا التحديث، حقل النموذج [Form.java]


        private String selectOneListBox1;

القيمة "3".

عندما يتم إعادة عرض صفحة [index.xhtml] بعد المعالجة، تؤدي هذه القيمة إلى عرض [3,4] الموضح أعلاه:

  • فهي تحدد عنصر القائمة المنسدلة الذي يجب عرضه [3]،
  • ويتم عرض قيمة حقل selectOneListBox1 في [4].

لننظر إلى أحد أشكال علامة <h:selectOneListBox>:


<!-- line 5 -->
          <h:outputText value="selectOneListBox (size=3)" styleClass="info"/>
          <h:panelGroup>
            <h:outputText value="#{msg['form.selectOneListBox2Prompt']}"/>
            <h:selectOneListbox id="selectOneListBox2" value="#{form.selectOneListBox2}" size="3">
              <f:selectItem itemValue="1" itemLabel="un"/>
              <f:selectItem itemValue="2" itemLabel="deux"/>
              <f:selectItem itemValue="3" itemLabel="trois"/>
              <f:selectItem itemValue="4" itemLabel="quatre"/>
              <f:selectItem itemValue="5" itemLabel="cinq"/>
            </h:selectOneListbox>
          </h:panelGroup>
          <h:outputText value="#{form.selectOneListBox2}"/>

القالب في [Form.java] لعلامة <h:selectOneListBox> في السطر 5 هو كما يلي:


  private String selectOneListBox2="3";

عندما يتم طلب صفحة [index.xhtml] لأول مرة، تكون الصفحة الناتجة كما يلي:

  • السطر 2 من كود XHTML يُنشئ [1]،
  • ويتم إنشاء النص [2] بواسطة السطر 4. يتم إنشاء القائمة ذات شريط التمرير [3] بواسطة الأسطر [5-11]. إن قيمة السمة size="3" هي التي تؤدي إلى إنشاء قائمة ذات شريط تمرير بدلاً من قائمة منسدلة. تم إنشاء عناصر القائمة بواسطة علامات <f:selectItem> في الأسطر 6–8،

تم تحديد العنصر المحدد في [3] من خلال استدعاء الدالة getSelectOneListBox2() (السطر 5). وأدى الحصول على النتيجة "3" (السطر 1 من كود Java) إلى عرض العنصر الموجود في السطر 8 من القائمة، لأن سمة itemValue الخاصة به هي "3"،

  • السطر 13 من كود XHTML يولد [4]. هنا، تم استخدام طريقة getSelectOneListBox2 من [Form.java] مرة أخرى.

إخراج HTML الذي تم إنشاؤه بواسطة صفحة XHTML هو كما يلي:


<tr>
<td class="col1"><span class="info">selectOneListBox (size=3)</span></td>
<td class="col2">choix unique : <select id="formulaire:selectOneListBox2" name="formulaire:selectOneListBox2" size="3">
    <option value="1">un</option>
    <option value="2">deux</option>
    <option value="3" selected="selected">trois</option>
    <option value="4">quatre</option>
    <option value="5">cinq</option>
</select></td>
<td class="col3">3</td>
</tr>
  • السطر 6: يؤدي تحديد العنصر الذي له القيمة "3" في القائمة إلى وجود السمة selected="selected".

الآن، أدناه، دعونا نختار [1] قيمة جديدة من القائمة ونرسل النموذج باستخدام زر [Submit] [2]. نحصل على الصفحة التالية كرد [3]:

القيمة المسجلة للحقل [1] هي كما يلي:

formulaire%3AselectOneListBox2=5

أدى إرسال النموذج عبر [2] إلى تحديث القالب [Form.java] بالبيانات التي تم إدخالها في [1]. عنصر HTML


    <option value="5">cinq</option>

تم تحديده. أرسل المتصفح السلسلة "5" كقيمة لمكون JSF الذي أنشأ القائمة المنسدلة:


            <h:selectOneListbox id="selectOneListBox2" value="#{form.selectOneListBox2}" size="3">

سيستخدم وحدة التحكم JSF الطريقة setSelectOneListBox2("5") لتحديث نموذج القائمة. أيضًا، بعد هذا التحديث، الحقل


        private String selectOneListBox2;

القيمة "5".

عندما يتم إعادة عرض صفحة [index.xhtml] بعد المعالجة، تؤدي هذه القيمة إلى العرض الموضح في [3،4] أعلاه:

  • فهي تحدد عنصر القائمة الذي يجب تحديده [3]،
  • ويتم عرض قيمة حقل selectOneListBox2 في [4].

2.5.15. علامة <h:selectManyListBox>

تقوم علامة <h:selectManyListBox> بإنشاء علامة HTML <select multiple="multiple">...</select> التي تسمح للمستخدم بتحديد عناصر متعددة من قائمة.

انظر إلى الكود التالي:


<!-- line 6 -->
          <h:outputText value="selectManyListBox (size=3)"  styleClass="info"/>
          <h:panelGroup>
            <h:outputText value="#{msg['form.selectManyListBoxPrompt']}"/>
            <h:selectManyListbox id="selectManyListBox" value="#{form.selectManyListBox}" size="3">
              <f:selectItem itemValue="1" itemLabel="un"/>
              <f:selectItem itemValue="2" itemLabel="deux"/>
              <f:selectItem itemValue="3" itemLabel="trois"/>
              <f:selectItem itemValue="4" itemLabel="quatre"/>
              <f:selectItem itemValue="5" itemLabel="cinq"/>
            </h:selectManyListbox>
            <p><input type="button" value="#{msg['form.buttonRazText']}" onclick="this.form['formulaire:selectManyListBox'].selectedIndex=-1;" /></p>
          </h:panelGroup>
          <h:outputText value="#{form.selectManyListBoxValue}"/>

وقوالبها في [Form.java]:


private String[] selectManyListBox=new String[]{"1","3"};

عندما يتم طلب صفحة [index.xhtml] لأول مرة، تكون الصفحة الناتجة كما يلي:

  • السطر 2 من كود XHTML يُنشئ [1]
  • يتم إنشاء النص [2] بواسطة السطر 4. يتم إنشاء القائمة [3] بواسطة الأسطر [5-11]. تؤدي السمة size="3" إلى عرض ثلاثة من هذه العناصر في أي وقت. تم تحديد العناصر المحددة في القائمة عن طريق استدعاء الأسلوب getSelectManyListBox() (السطر 5) في نموذج Java. النتيجة {"1","3"} (السطر 1 من كود Java) هي مصفوفة من عناصر String. يتم استخدام كل عنصر من هذه العناصر لـ اختيار أحد عناصر القائمة. هنا، سيتم اختيار العناصر الموجودة في السطرين 6 و 10 التي تكون سمة itemValue الخاصة بها موجودة في المصفوفة {"1","3"}. يظهر هذا في [3].
  • يُنتج السطر 14 من كود XHTML [4]. وهنا، لا يتم استدعاء طريقة getSelectManyListBox الخاصة بنموذج قائمة Java، بل يتم استدعاء طريقة getSelectManyListBoxValue التالية:

private String[] selectManyListBox=new String[]{"1","3"};
  ...
  // getters et setters
  
  public String getSelectManyListBoxValue(){
    return getValue(selectManyListBox);
  }
  
  private String getValue(String[] chaines){
    String value="[";
    for(String chaine : chaines){
      value+=" "+chaine;
    }
    return value+"]";
  }

لو كنا قد استدعينا طريقة getSelectManyListBox، لكنا حصلنا على مصفوفة من سلاسل الأحرف. ولإدراج هذا العنصر في إخراج HTML، كان سيتعين على وحدة التحكم استدعاء طريقة toString الخاصة بها. ومع ذلك، بالنسبة للمصفوفة، لا تُرجع هذه الطريقة سوى "رمز التجزئة" الخاص بالمصفوفة وليس قائمة عناصرها كما نرغب. ولذلك، نستخدم طريقة getSelectManyListBoxValue المذكورة أعلاه للحصول على سلسلة تمثل محتويات المصفوفة؛

  • السطر 12 من كود XHTML يولد الزر [5]. عند النقر على هذا الزر، يتم تنفيذ كود JavaScript الموجود في سمة onclick. سيتم تضمينه داخل صفحة HTML التي سيتم إنشاؤها بواسطة كود JSF. لفهم ذلك، نحتاج إلى معرفة الطبيعة الدقيقة لتلك الصفحة.

إخراج HTML الذي تم إنشاؤه بواسطة صفحة XHTML هو كما يلي:


<tr>
<td class="col1"><span class="info">selectManyListBox (size=3)</span></td>
<td class="col2">choix multiple : <select id="formulaire:selectManyListBox" name="formulaire:selectManyListBox" multiple="multiple" size="3">
    <option value="1" selected="selected">un</option>
    <option value="2">deux</option>
    <option value="3" selected="selected">trois</option>
    <option value="4">quatre</option>
    <option value="5">cinq</option>
</select>
            <p><input type="button" value="Raz" onclick="this.form['formulaire:selectManyListBox'].selectedIndex=-1;" /></p>
          </td>
<td class="col3">[ 1 3]</td>
</tr>
  • السطران 3 و 9: علامة HTML <select multiple="multiple"...>...</select> التي تم إنشاؤها بواسطة علامة JSF <h:selectManyListBox>. يشير وجود السمة multiple إلى أن هذه قائمة متعددة التحديد،
  • وحقيقة أن نموذج القائمة هو مصفوفة String {"1","3"} تعني أن عناصر القائمة في السطرين 4 (value="1") و 6 (value="3") لها السمة selected="selected
  • السطر 10: عند النقر على زر [Clear]، يتم تنفيذ كود JavaScript الموجود في السمة onclick. يتم تمثيل الصفحة في المتصفح بواسطة شجرة من الكائنات تُسمى غالبًا DOM (نموذج كائنات المستند). يمكن الوصول إلى كل كائن في الشجرة بواسطة كود JavaScript عبر سمة name الخاصة به. تسمى القائمة الموجودة في السطر 3 من كود HTML أعلاه formulaire:selectManyListBox. يمكن الإشارة إلى النموذج نفسه بطرق مختلفة. هنا، يُشار إليه باستخدام الترميز this.form، حيث يشير this إلى زر [Reset] ويشير this.form إلى النموذج الذي يوجد فيه هذا الزر. يقع النموذج القائمة form:selectManyListBox داخل هذا النموذج نفسه. وبالتالي، يشير الترميز this.form['form:selectManyListBox'] إلى موقع القائمة في شجرة مكونات النموذج. يحتوي الكائن الذي يمثل القائمة على سمة selectedIndex التي تمثل قيمة مؤشر العنصر المحدد في القائمة. يبدأ هذا المؤشر من 0 للإشارة إلى العنصر الأول في القائمة. تشير القيمة -1 إلى عدم تحديد أي عنصر في القائمة. يقوم كود JavaScript الذي يضبط سمة selectedIndex على -1 بإلغاء تحديد جميع العناصر في القائمة، إذا كان أي منها محددًا.

الآن، أدناه، دعونا نختار [1] قيم جديدة من القائمة (لتحديد عناصر متعددة في القائمة، اضغط باستمرار على مفتاح Ctrl أثناء النقر) ونرسل النموذج باستخدام زر [Submit] [2]. نتلقى الصفحة التالية كرد [3,4]:

قيمة الحقل [1] الذي تم إرساله هي كما يلي:

formulaire%3AselectManyListBox=3&formulaire%3AselectManyListBox=4&formulaire%3AselectManyListBox=5

أدى إرسال النموذج عبر [2] إلى تحديث نموذج [Form.java] بالمدخلة [1]. عناصر HTML


    <option value="3">trois</option>
    <option value="4">quatre</option>
    <option value="5">cinq</option>

تم تحديدها. أرسل المتصفح السلاسل الثلاثة "3" و"4" و"5" كقيم لمكون JSF الذي أنشأ القائمة المنسدلة:


            <h:selectManyListbox id="selectManyListBox" value="#{form.selectManyListBox}" size="3">

سيتم استخدام طريقة setSelectManyListBox الخاصة بالنموذج لتحديث هذا النموذج بالقيم التي أرسلها المتصفح:


  private String[] selectManyListBox;
....
  public void setSelectManyListBox(String[] selectManyListBox) {
    this.selectManyListBox = selectManyListBox;
}

في السطر 3، نرى أن معلمة الأسلوب عبارة عن مصفوفة من سلاسل الأحرف. هنا، ستكون المصفوفة {"3", "4", "5"}. بعد هذا التحديث، الحقل


        private String[] selectManyListBox;

المصفوفة {"3","4","5"}.

عندما يتم إعادة عرض صفحة [index.xhtml] بعد المعالجة، تؤدي هذه القيمة إلى عرض [3,4] أعلاه:

  • فهي تحدد العناصر التي يجب تحديدها في القائمة [3]،
  • ويتم عرض قيمة حقل selectManyListBox في [4].

2.5.16. علامة <h:selectOneMenu>

علامة <h:selectOneMenu> مطابقة لعلامة <h:selectOneListBox size="1">. في المثال، يتم تنفيذ كود JSF كما يلي:


<!-- line 7 -->
          <h:outputText value="selectOneMenu" styleClass="info"/>
          <h:panelGroup>
            <h:outputText value="#{msg['form.selectOneMenuPrompt']}"/>
            <h:selectOneMenu id="selectOneMenu" value="#{form.selectOneMenu}">
              <f:selectItem itemValue="1" itemLabel="un"/>
              <f:selectItem itemValue="2" itemLabel="deux"/>
              <f:selectItem itemValue="3" itemLabel="trois"/>
              <f:selectItem itemValue="4" itemLabel="quatre"/>
              <f:selectItem itemValue="5" itemLabel="cinq"/>
            </h:selectOneMenu>
          </h:panelGroup>
          <h:outputText value="#{form.selectOneMenu}"/>

القالب الخاص بعلامة <h:selectOneMenu> في [Form.java] هو كما يلي:


  private String selectOneMenu="1";

عندما يتم طلب صفحة [index.xhtml] لأول مرة، يقوم الكود أعلاه بإنشاء العرض:

قد يكون مثال التنفيذ كما يلي:

القيمة المسجلة للحقل [1] هي كما يلي:

formulaire%3AselectOneMenu=4

2.5.17. علامة <h:selectManyMenu>

علامة <h:selectManyMenu> مطابقة لعلامة <h:selectManyListBox size="1">. فيما يلي كود JSF الذي تم تنفيذه في المثال:


<!-- line 8 -->
          <h:outputText value="selectManyMenu" styleClass="info"/>
          <h:panelGroup>
            <h:outputText value="#{msg['form.selectManyMenuPrompt']}" styleClass="prompt" />
            <h:selectManyMenu id="selectManyMenu" value="#{form.selectManyMenu}" >
              <f:selectItem itemValue="1" itemLabel="un"/>
              <f:selectItem itemValue="2" itemLabel="deux"/>
              <f:selectItem itemValue="3" itemLabel="trois"/>
              <f:selectItem itemValue="4" itemLabel="quatre"/>
              <f:selectItem itemValue="5" itemLabel="cinq"/>
            </h:selectManyMenu>
            <p><input type="button" value="#{msg['form.buttonRazText']}" onclick="this.form['formulaire:selectManyMenu'].selectedIndex=-1;" /></p>
          </h:panelGroup>
          <h:outputText value="#{form.selectManyMenuValue}" styleClass="prompt"/>

القالب الخاص بعلامة <h:selectManyMenu> في [Form.java] هو كما يلي:


    private String[] selectManyMenu=new String[]{"1","2"};

عندما يتم طلب صفحة [index.xhtml] لأول مرة، يقوم الكود أعلاه بإنشاء الصفحة:

تحتوي القائمة [1] على النصوص "واحد"، ...، "خمسة" مع تحديد العنصرين "واحد" و"اثنان". فيما يلي كود HTML الذي تم إنشاؤه:


<tr>
<td class="col1"><span class="info">selectManyMenu</span></td>
<td class="col2"><span class="prompt">choix multiple : </span><select id="formulaire:selectManyMenu" name="formulaire:selectManyMenu" multiple="multiple" size="1">
    <option value="1" selected="selected">un</option>
    <option value="2" selected="selected">deux</option>
    <option value="3">trois</option>
    <option value="4">quatre</option>
    <option value="5">cinq</option>
</select>
            
            
            <p><input type="button" value="Raz" onclick="this.form['formulaire:selectManyMenu'].selectedIndex=-1;" /></p>
          </td>
<td class="col3"><span class="prompt">[ 1 2]</span></td>
</tr>

كما هو موضح أعلاه في السطرين 4 و 5، تم تحديد العنصرين "one" و "two" (وجود السمة selected).

من الصعب تقديم لقطة شاشة لمثال عملي على ذلك لأننا لا نستطيع عرض العناصر المحددة في القائمة. ننصح القراء بتجربة ذلك بأنفسهم (لتحديد عناصر متعددة في القائمة، اضغط باستمرار على مفتاح Ctrl أثناء النقر).

2.5.18. علامة <h:inputHidden>

لا يوجد تمثيل مرئي لعلامة <h:inputHidden>. تُستخدم فقط لإدراج علامة HTML <input type="hidden" value="..."/> في تدفق HTML للصفحة. عند تضمينها داخل علامة <h:form>، تصبح قيمها جزءًا من البيانات المرسلة إلى الخادم عند إرسال النموذج. نظرًا لأن هذه حقول نموذج لا يمكن للمستخدم رؤيتها، فإنها تسمى الحقول المخفية. الغرض من هذه الحقول هو الحفاظ على البيانات بين دورات الطلب/الاستجابة المختلفة لنفس العميل:

  • يطلب العميل نموذج F. يرسله الخادم ويضع المعلومات I في حقل مخفي C، في النموذج <h:inputHidden id="C" value="I"/>،
  • عندما يملأ العميل النموذج F ويرسله إلى الخادم، يتم إرسال القيمة I للحقل C مرة أخرى إلى الخادم. يمكن للخادم بعد ذلك استرداد المعلومات I التي كان قد خزنها على الصفحة. وهذا يخلق ذاكرة بين دورتي الطلب/الاستجابة،
  • يستخدم JSF نفسه هذه التقنية. المعلومات I التي يخزنها في النموذج F هي قيمة جميع مكوناته. ويستخدم الحقل المخفي التالي لهذا الغرض:

<input type="hidden" name="javax.faces.ViewState" id="javax.faces.ViewState" value="H4sIAAAAAAAAANV...8PswawAA" />

يُسمى الحقل المخفي javax.faces.ViewState، وقيمته عبارة عن سلسلة تمثل، في شكل مشفر، قيم جميع المكونات الموجودة على الصفحة المرسلة إلى العميل. عندما يرسل العميل الصفحة بعد إدخال البيانات في النموذج، يتم إرسال الحقل المخفي javax.faces.ViewState مع القيم التي تم إدخالها. وهذا يسمح لوحدة التحكم JSF بإعادة بناء الصفحة كما تم إرسالها في الأصل. تم شرح هذه الآلية في الصفحة 72.

فيما يلي كود JSF الخاص بالمثال:


<!-- ligne 9 -->
          <h:outputText value="inputHidden"  styleClass="info"/>
          <h:inputHidden id="inputHidden" value="#{form.inputHidden}"/>
          <h:outputText value="#{form.inputHidden}"/>

القالب الخاص بعلامة <h:inputHidden> في [Form.java] هو كما يلي:


  private String inputHidden="initial";

ينتج عن ذلك العرض التالي عند طلب صفحة [index.xhtml] لأول مرة:

  • السطر 2 يولد [1]، والسطر 4 يولد [2]. أما السطر 3 فلا يولد أي عنصر مرئي.

فيما يلي كود HTML الذي تم إنشاؤه:


<tr>
<td class="col1"><span class="info">inputHidden</span></td>
<td class="col2"><input id="formulaire:inputHidden" type="hidden" name="formulaire:inputHidden" value="initial" /></td>
<td class="col3">initial</td>
</tr>

عند إرسال النموذج، سيتم إرسال القيمة "initial" للحقل المسمى form:inputHidden في السطر 3 مع قيم النموذج الأخرى. الحقل


  private String inputHidden;

بهذه القيمة، وهي القيمة التي كان يحتوي عليها بالفعل في البداية. سيتم تضمين هذه القيمة في الصفحة الجديدة التي يتم إرسالها إلى العميل. ولذلك نحصل دائمًا على لقطة الشاشة أعلاه.

القيمة التي تم إرسالها للحقل المخفي هي كما يلي:

formulaire%3AinputHidden=initial

2.5.19. علامة <h:selectBooleanCheckBox>

تقوم علامة <h:selectBooleanCheckBox> بإنشاء علامة HTML <input type="checkbox" ...>.

انظر إلى كود JSF التالي:


<!-- line 10 -->
  <h:outputText value="selectBooleanCheckbox" styleClass="info"/>
  <h:panelGroup>
    <h:outputText value="#{msg['form.selectBooleanCheckboxPrompt']}" styleClass="prompt" />
    <h:selectBooleanCheckbox id="selectBooleanCheckbox" value="#{form.selectBooleanCheckbox}"/>
  </h:panelGroup>
  <h:outputText value="#{form.selectBooleanCheckbox}"/>

القالب الخاص بعلامة <h:selectBooleanCheckbox> في السطر 5 أعلاه في [Form.java] هو كما يلي:


  private boolean selectBooleanCheckbox=true;

عندما يتم طلب صفحة [index.xhtml] لأول مرة، تكون الصفحة الناتجة كما يلي:

  • السطر 2 من كود XHTML يولد [1]،
  • يتم إنشاء النص [2] بواسطة السطر 4. ويتم إنشاء مربع الاختيار [3] بواسطة السطر [5]. هنا، تم استخدام طريقة getSelectBooleanCheckbox من ملف [Form.java] لتفعيل المربع أو إلغاء تفعيله. ونظرًا لأن الطريقة تُرجع قيمة منطقية (boolean) تساوي true (انظر كود Java)، فقد تم تفعيل المربع،
  • السطر 7 من كود XHTML يُنشئ [4]. مرة أخرى، تُستخدم طريقة getSelectBooleanCheckbox من [Form.java] لإنشاء النص [4].

إخراج HTML الذي تم إنشاؤه بواسطة كود JSF السابق هو كما يلي:


<tr>
<td class="col1"><span class="info">selectBooleanCheckbox</span></td>
<td class="col2"><span class="prompt">mari&eacute;(e) : </span>
<input id="formulaire:selectBooleanCheckbox" type="checkbox" name="formulaire:selectBooleanCheckbox" checked="checked" /></td>
<td class="col3">true</td>
</tr>

في [4]، نرى علامة HTML <input type="checkbox"> التي تم إنشاؤها. تسببت القيمة true للنموذج المرتبط في إضافة السمة checked="checked" إلى العلامة. وهذا يؤدي إلى تحديد مربع الاختيار.

الآن، أدناه، دعونا نلغي تحديد مربع الاختيار [1]، ونرسل النموذج [2]، ونلقي نظرة على النتيجة [3، 4]:

نظرًا لأن خانة الاختيار غير محددة، لم يتم إرسال أي قيمة للحقل [1].

أدى إرسال النموذج عبر [2] إلى تحديث النموذج [Form.java] بواسطة الإدخال [1]. ثم تلقى حقل selectBooleanCheckbox في [Form.java] القيمة false. تُظهر إعادة تحميل [index.xhtml] أن حقل selectBooleanCheckbox في النموذج قد تم تحديثه بالفعل [3] و[4]. تجدر الإشارة هنا إلى أن JSF تمكن من تحديد أن مربع الاختيار الذي كان محددًا في البداية قد تم إلغاء تحديده من قبل المستخدم بفضل الحقل المخفي javax.faces.ViewState. في الواقع، لا يتم تضمين قيمة مربع الاختيار غير المحدد في القيم التي يتم إرسالها بواسطة المتصفح. بفضل شجرة المكونات المخزنة في الحقل المخفي javax.faces.ViewState، يحدد JSF أن هناك مربع اختيار باسم "selectBooleanCheckbox" في النموذج وأن قيمته غير مدرجة في القيم التي أرسلها متصفح العميل. وبالتالي يمكنه استنتاج أنه تم إلغاء تحديده في النموذج المرسل، مما يسمح له بتعيين القيمة المنطقية false للنموذج Java المرتبط:


  private boolean selectBooleanCheckbox;

2.5.20. علامة <h:selectManyCheckBox>

تقوم علامة <h:selectManyCheckBox> بإنشاء مجموعة من مربعات الاختيار، وبالتالي، عدة علامات HTML <input type="checkbox" ...>. هذه العلامة هي نظيرة لعلامة <h:selectManyListBox>، باستثناء أن العناصر القابلة للتحديد تُعرض كمربعات اختيار متجاورة بدلاً من قائمة. ما قيل عن علامة <h:selectManyListBox> ينطبق هنا أيضًا.

انظر إلى كود JSF التالي:


          <!-- line 11 -->
          <h:outputText value="selectManyCheckbox" styleClass="info"/>
          <h:panelGroup>
            <h:outputText value="#{msg['form.selectManyCheckboxPrompt']}" styleClass="prompt" />
            <h:selectManyCheckbox id="selectManyCheckbox" value="#{form.selectManyCheckbox}">
              <f:selectItem itemValue="1" itemLabel="rouge"/>
              <f:selectItem itemValue="2" itemLabel="bleu"/>
              <f:selectItem itemValue="3" itemLabel="blanc"/>
              <f:selectItem itemValue="4" itemLabel="noir"/>
            </h:selectManyCheckbox>
          </h:panelGroup>
<h:outputText value="#{form.selectManyCheckboxValue}"/>

القالب الخاص بعلامة <h:selectManyCheckbox> في السطر 5 أعلاه في [Form.java] هو كما يلي:


private String[] selectManyCheckbox=new String[]{"1","3"};

عندما يتم طلب صفحة [index.xhtml] لأول مرة، تكون الصفحة الناتجة كما يلي:

  • السطر 2 من كود XHTML يولد [1]،
  • يتم إنشاء النص [2] بواسطة السطر 4. يتم إنشاء مربعات الاختيار [3] بواسطة الأسطر 5-10. بالنسبة لكل منها:
  • تحدد السمة itemLabel النص المعروض بجوار مربع الاختيار؛
  • تحدد السمة itemvalue القيمة التي سيتم إرسالها إلى الخادم إذا تم تحديد مربع الاختيار،

نموذج مربعات الاختيار الأربعة هو حقل Java التالي:


private String[] selectManyCheckbox=new String[]{"1","3"};

يحدد هذا المصفوف:

  • عند عرض الصفحة، أي مربعات الاختيار يجب تحديدها. ويتم ذلك من خلال قيمتها، أي حقل itemValue الخاص بها. في المثال أعلاه، سيتم تحديد مربعات الاختيار التي تحتوي على قيم في المصفوفة {"1","3"}. وهذا ما يظهر في لقطة الشاشة أعلاه؛
  • عند إرسال الصفحة، يتلقى نموذج selectManyCheckbox مصفوفة القيم الخاصة بمربعات الاختيار التي حددها المستخدم. وهذا ما سنراه بعد قليل؛
  • السطر 12 من كود XHTML يولد [4]. إنها طريقة getSelectManyCheckboxValue التالية التي ولدت [4]:

  public String getSelectManyCheckboxValue(){
    return getValue(getSelectManyCheckbox());
  }
  
  private String getValue(String[] chaines){
    String value="[";
    for(String chaine : chaines){
      value+=" "+chaine;
    }
    return value+"]";
}

إخراج HTML الناتج عن كود JSF السابق هو كما يلي:


    <tr>
<td>
<input name="formulaire:selectManyCheckbox" id="formulaire:selectManyCheckbox:0" value="1" type="checkbox" checked="checked" /><label for="formulaire:selectManyCheckbox:0"> rouge</label></td>
<td>
<input name="formulaire:selectManyCheckbox" id="formulaire:selectManyCheckbox:1" value="2" type="checkbox" /><label for="formulaire:selectManyCheckbox:1"> bleu</label></td>
<td>
<input name="formulaire:selectManyCheckbox" id="formulaire:selectManyCheckbox:2" value="3" type="checkbox" checked="checked" /><label for="formulaire:selectManyCheckbox:2"> blanc</label></td>
<td>
<input name="formulaire:selectManyCheckbox" id="formulaire:selectManyCheckbox:3" value="4" type="checkbox" /><label for="formulaire:selectManyCheckbox:3"> noir</label></td>
    </tr>
</table></td>
<td class="col3">[ 1 3]</td>
</tr>

تم إنشاء أربع علامات HTML <input type="checkbox" ...>. تحتوي العلامات في السطرين 3 و7 على السمة checked="checked"، مما يجعلها تظهر محددة. لاحظ أن جميعها تحتوي على نفس السمة name="formulaire:selectManyCheckbox"؛ بمعنى آخر، الحقول الأربعة في HTML لها نفس الاسم. إذا قام المستخدم بتحديد مربعات الاختيار في السطرين 5 و9، فسيرسل المتصفح قيم مربعات الاختيار الأربعة بالتنسيق التالي:

formulaire:selectManyCheckbox=2&formulaire:selectManyCheckbox=4

ونموذج مربعات الاختيار الأربعة


private String[] selectManyCheckbox=new String[]{"1","3"};

ستحصل على المصفوفة {"2","4"}.

دعونا نتحقق من ذلك أدناه. في [1]، نقوم بإجراء التغيير؛ وفي [2]، نرسل النموذج. وفي [3]، النتيجة التي تم الحصول عليها:

القيم التي تم إرسالها للحقول [1] هي كما يلي:

formulaire%3AselectManyCheckbox=2&formulaire%3AselectManyCheckbox=4

2.5.21. علامة <h:selectOneRadio>

تقوم علامة <h:selectOneRadio> بإنشاء مجموعة من أزرار الاختيار المتنافية.

انظر إلى كود JSF التالي:


<!-- line 12 -->
          <h:outputText value="selectOneRadio" styleClass="info"/>
          <h:panelGroup>
            <h:outputText value="#{msg['form.selectOneRadioPrompt']}" />
            <h:selectOneRadio id="selectOneRadio" value="#{form.selectOneRadio}">
              <f:selectItem itemValue="1" itemLabel="voiture"/>
              <f:selectItem itemValue="2" itemLabel="vélo"/>
              <f:selectItem itemValue="3" itemLabel="scooter"/>
              <f:selectItem itemValue="4" itemLabel="marche"/>
            </h:selectOneRadio>
          </h:panelGroup>
          <h:outputText value="#{form.selectOneRadio}"/>

القالب الخاص بعلامة <h:selectOneRadio> في السطر 5 أعلاه هو كما يلي في [Form.java]:


  private String selectOneRadio="2";

عندما يتم طلب صفحة [index.xhtml] لأول مرة، يكون العرض الناتج كما يلي:

  • السطر 2 من كود XHTML يُنشئ [1]،
  • يتم إنشاء النص [2] بواسطة السطر 4. يتم إنشاء أزرار الاختيار [3] بواسطة الأسطر 5–10. بالنسبة لكل منها:
  • تحدد السمة itemLabel النص المعروض بجوار زر الاختيار؛
  • تحدد السمة itemvalue القيمة التي سيتم إرسالها إلى الخادم في حالة تحديد الزر،

نموذج أزرار الاختيار الأربعة هو حقل Java التالي:


  private String selectOneRadio="2";

يحدد هذا النموذج:

  • عند عرض الصفحة، زر الاختيار الوحيد الذي يجب تحديده. ويتم ذلك من خلال قيمته، أي حقل itemValue الخاص به. في المثال أعلاه، سيتم تحديد زر الاختيار الذي يحمل القيمة "2". وهذا ما يظهر في لقطة الشاشة أعلاه؛
  • عند إرسال الصفحة، يتلقى قالب selectOneRadio قيمة زر الاختيار الذي تم تحديده. سنرى ذلك بعد قليل؛
  • السطر 12 من كود XHTML يولد [4].

إخراج HTML الذي تم إنشاؤه بواسطة كود JSF السابق هو كما يلي:


<tr>
<td class="col1"><span class="info">selectOneRadio</span></td>
<td class="col2">moyen de transport pr&eacute;f&eacute;r&eacute; : <table id="formulaire:selectOneRadio">
    <tr>
<td>
<input type="radio" name="formulaire:selectOneRadio" id="formulaire:selectOneRadio:0" value="1" /><label for="formulaire:selectOneRadio:0"> voiture</label></td>
<td>
<input type="radio" checked="checked" name="formulaire:selectOneRadio" id="formulaire:selectOneRadio:1" value="2" /><label for="formulaire:selectOneRadio:1"> v&eacute;lo</label></td>
<td>
<input type="radio" name="formulaire:selectOneRadio" id="formulaire:selectOneRadio:2" value="3" /><label for="formulaire:selectOneRadio:2"> scooter</label></td>
<td>
<input type="radio" name="formulaire:selectOneRadio" id="formulaire:selectOneRadio:3" value="4" /><label for="formulaire:selectOneRadio:3"> marche</label></td>
</tr>

تم إنشاء أربع علامات HTML <input type="radio" ...>. تحتوي العلامة الموجودة في السطر 8 على السمة checked="checked"، مما يؤدي إلى ظهور زر الاختيار المقابل محددًا. لاحظ أن جميع العلامات لها نفس السمة name="form:selectOneRadio"، مما يعني أن الحقول الأربعة في HTML تشترك في نفس الاسم. هذا هو الشرط المطلوب لمجموعة من أزرار الاختيار المتنافية: عندما يتم تحديد أحدها، لا يتم تحديد البقية.

فيما يلي، في [1]، نقوم بتحديد أحد أزرار الاختيار؛ وفي [2]، نرسل النموذج؛ وفي [3]، النتيجة التي تم الحصول عليها:

القيمة التي تم إرسالها للحقل [1] هي كما يلي:

formulaire%3AselectOneRadio=4

2.6. مثال mv-jsf2-04: قوائم ديناميكية

2.6.1. التطبيق

التطبيق هو نفسه كما كان من قبل:

التغييرات الوحيدة هي في كيفية إنشاء عناصر القائمة للحقول [1] و[2]. هنا، يتم إنشاؤها ديناميكيًا بواسطة كود Java، بينما في الإصدار السابق كانت مدمجة بشكل ثابت في صفحة JSF.

2.6.2. مشروع NetBeans

مشروع NetBeans الخاص بالتطبيق هو كما يلي:

مشروع [mv-jsf2-04] مطابق لمشروع [mv-jsf2-03]، مع الاختلافات التالية:

  • في [1]، على صفحة JSF، لم تعد عناصر القائمة مبرمجة بشكل ثابت في الكود،
  • في [2]، سيتم تعديل القالب الخاص بصفحة JSF [1]،
  • في [3]، سيتم تعديل إحدى الرسائل.

2.6.3. صفحة [index.xhtml] ونموذجها [Form.java]

تصبح صفحة JSF [index.xhtml] كما يلي:


<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core">
 
  <f:view locale="#{changeLocale.locale}">
    <h:head>
      <title>JSF</title>
      <h:outputStylesheet library="css" name="styles.css"/>
    </h:head>
    <h:body style="background-image: url('${request.contextPath}/resources/images/standard.jpg');">
      <h:form id="formulaire">
        <!-- languages -->
        <h:panelGrid columns="2">
          <h:commandLink value="#{msg['form.langue1']}" action="#{changeLocale.setFrenchLocale}"/>
          <h:commandLink value="#{msg['form.langue2']}" action="#{changeLocale.setEnglishLocale}"/>
        </h:panelGrid>
        <h1><h:outputText value="#{msg['form.titre']}"/></h1>
        <h:panelGrid columnClasses="col1,col2,col3" columns="3" border="1">
...
          <!-- line 4 -->
          <h:outputText value="selectOneListBox (size=1)" styleClass="info"/>
          <h:panelGroup>
            <h:outputText value="#{msg['form.selectOneListBox1Prompt']}"/>
            <h:selectOneListbox id="selectOneListBox1" value="#{form.selectOneListBox1}" size="1">
              <f:selectItems value="#{form.selectOneListbox1Items}"/>
            </h:selectOneListbox>
          </h:panelGroup>
          <h:outputText value="#{form.selectOneListBox1}"/>
          <!-- line 5 -->
          <h:outputText value="selectOneListBox (size=3)" styleClass="info"/>
          <h:panelGroup>
            <h:outputText value="#{msg['form.selectOneListBox2Prompt']}"/>
            <h:selectOneListbox id="selectOneListBox2" value="#{form.selectOneListBox2}" size="3">
              <f:selectItems value="#{form.selectOneListbox2Items}"/>
            </h:selectOneListbox>
          </h:panelGroup>
          <h:outputText value="#{form.selectOneListBox2}"/>
          <!-- line 6 -->
          <h:outputText value="selectManyListBox (size=3)"  styleClass="info"/>
          <h:panelGroup>
            <h:outputText value="#{msg['form.selectManyListBoxPrompt']}"/>
            <h:selectManyListbox id="selectManyListBox" value="#{form.selectManyListBox}" size="3">
              <f:selectItems value="#{form.selectManyListBoxItems}"/>
            </h:selectManyListbox>
            <p><input type="button" value="#{msg['form.buttonRazText']}" onclick="this.form['formulaire:selectManyListBox'].selectedIndex=-1;" /></p>
          </h:panelGroup>
          <h:outputText value="#{form.selectManyListBoxValue}"/>
          <!-- line 7 -->
          <h:outputText value="selectOneMenu" styleClass="info"/>
          <h:panelGroup>
            <h:outputText value="#{msg['form.selectOneMenuPrompt']}"/>
            <h:selectOneMenu id="selectOneMenu" value="#{form.selectOneMenu}">
              <f:selectItems value="#{form.selectOneMenuItems}"/>
            </h:selectOneMenu>
          </h:panelGroup>
          <h:outputText value="#{form.selectOneMenu}"/>
          <!-- line 8 -->
          <h:outputText value="selectManyMenu" styleClass="info"/>
          <h:panelGroup>
            <h:outputText value="#{msg['form.selectManyMenuPrompt']}" styleClass="prompt" />
            <h:selectManyMenu id="selectManyMenu" value="#{form.selectManyMenu}" >
              <f:selectItems value="#{form.selectManyMenuItems}"/>
            </h:selectManyMenu>
            <p><input type="button" value="#{msg['form.buttonRazText']}" onclick="this.form['formulaire:selectManyMenu'].selectedIndex=-1;" /></p>
          </h:panelGroup>
          <h:outputText value="#{form.selectManyMenuValue}" styleClass="prompt"/>
...
          <!-- line 11 -->
          <h:outputText value="selectManyCheckbox" styleClass="info"/>
          <h:panelGroup>
            <h:outputText value="#{msg['form.selectManyCheckboxPrompt']}" styleClass="prompt" />
            <h:selectManyCheckbox id="selectManyCheckbox" value="#{form.selectManyCheckbox}">
              <f:selectItems value="#{form.selectManyCheckboxItems}"/>
            </h:selectManyCheckbox>
          </h:panelGroup>
          <h:outputText value="#{form.selectManyCheckboxValue}"/>
          <!-- line 12 -->
          <h:outputText value="selectOneRadio" styleClass="info"/>
          <h:panelGroup>
            <h:outputText value="#{msg['form.selectOneRadioPrompt']}" />
            <h:selectOneRadio id="selectOneRadio" value="#{form.selectOneRadio}">
              <f:selectItems value="#{form.selectOneRadioItems}"/>
            </h:selectOneRadio>
          </h:panelGroup>
          <h:outputText value="#{form.selectOneRadio}"/>
        </h:panelGrid>
        <p>
          <h:commandButton type="submit" id="submit" value="#{msg['form.submitText']}"/>
        </p>
      </h:form>
    </h:body>
  </f:view>
</html>

تظهر التغييرات التي تم إجراؤها في الأسطر 26–28. حيث كان لدينا سابقًا الكود:


<h:selectOneListbox id="selectOneListBox1" value="#{form.selectOneListBox1}" size="1">
              <f:selectItem itemValue="1" itemLabel="un"/>
              <f:selectItem itemValue="2" itemLabel="deux"/>
              <f:selectItem itemValue="3" itemLabel="trois"/>
</h:selectOneListbox>

والآن لدينا ما يلي:


<h:selectOneListbox id="selectOneListBox1" value="#{form.selectOneListBox1}" size="1">
              <f:selectItems value="#{form.selectOneListbox1Items}"/>
</h:selectOneListbox>

تم استبدال العلامات الثلاث <f:selectItem> في الأسطر 2-4 بعلامة واحدة <f:selectItems> في السطر ب. تحتوي هذه العلامة على سمة value التي تمثل مجموعة من العناصر من النوع javax.faces.model.SelectItem. أعلاه، يتم الحصول على قيمة سمة value عن طريق استدعاء الطريقة التالية [form].getSelectOneListbox1Items:


  public SelectItem[] getSelectOneListbox1Items() {
    return getItems("A",3);
  }
 
  private SelectItem[] getItems(String label, int qte) {
    SelectItem[] items=new SelectItem[qte];
    for(int i=0;i<qte;i++){
      items[i]=new SelectItem(i,label+i);
    }
    return items;
}
  • في السطر 1، تُرجع الطريقة getSelectOneListbox1Items مصفوفة من العناصر من النوع javax.faces.model.SelectItem التي تم إنشاؤها بواسطة الطريقة الخاصة getItems في السطر 5. لاحظ أن الطريقة getSelectOneListbox1Items ليست طريقة الحصول على حقل خاص باسم selectOneListBox1Items؛
  • تحتوي فئة javax.faces.model.SelectItem على العديد من المنشئات.

Image

في السطر 8 من طريقة getItems، نستخدم منشئ SelectItem(Object value, String label)، والذي يتوافق مع علامة JSF


    <f:selectItem itemValue="value" labelValue="label"/>
  • الأسطر 5–10: تقوم طريقة getItems(String label, int qte) بإنشاء مصفوفة من qte عنصر من نوع SelectItem، حيث يتم الحصول على العنصر i عبر منشئ SelectItem(i, label+i).

كود JSF


<h:selectOneListbox id="selectOneListBox1" value="#{form.selectOneListBox1}" size="1">
              <f:selectItems value="#{form.selectOneListbox1Items}"/>
</h:selectOneListbox>

يكون عندئذٍ مكافئًا وظيفيًا لرمز JSF التالي:


<h:selectOneListbox id="selectOneListBox1" value="#{form.selectOneListBox1}" size="1">
              <f:selectItem itemValue="0" itemLabel="A0"/>
              <f:selectItem itemValue="1" itemLabel="A1"/>
              <f:selectItem itemValue="2" itemLabel="A2"/>
</h:selectOneListbox>

وينطبق الأمر نفسه على جميع القوائم الأخرى في صفحة JSF. يتضمن القالب [Form.java] الآن الطرق الجديدة التالية:


  public SelectItem[] getSelectOneListbox1Items() {
    return getItems("A",3);
  }
  
  public SelectItem[] getSelectOneListbox2Items() {
    return getItems("B",4);
  }
  
  public SelectItem[] getSelectManyListBoxItems() {
    return getItems("C",5);
  }
  
  public SelectItem[] getSelectOneMenuItems() {
    return getItems("D",3);
  }
  
  public SelectItem[] getSelectManyMenuItems() {
   return getItems("E",4);
   }
  
  public SelectItem[] getSelectManyCheckboxItems() {
   return getItems("F",3);
   }
  
  public SelectItem[] getSelectOneRadioItems() {
   return getItems("G",4);
   }
  
  private SelectItem[] getItems(String label, int qte) {
    SelectItem[] items=new SelectItem[qte];
    for(int i=0;i<qte;i++){
      items[i]=new SelectItem(i,label+i);
    }
    return items;
}

2.6.4. ملف الرسالة

تم تعديل ملف رسالة واحد فقط:

[messages_fr.properties]


form.titre=Java Server Faces - remplissage dynamique des listes

[messages_en.properties]


form.titre=Java Server Faces - dynamic filling of lists of elements

2.6.5. الاختبارات

ندعو القراء إلى اختبار هذا الإصدار الجديد.

في أغلب الأحيان، تكون العناصر الديناميكية في النموذج ناتجة عن معالجة منطق الأعمال أو مستمدة من قاعدة بيانات:

دعونا ندرس الطلب الأولي لصفحة JSF [index.xhtml] عبر طلب GET للمتصفح:

  • يتم طلب صفحة JSF [1]،
  • يطلب وحدة التحكم [Faces Servlet] عرضها في [3]. يقوم محرك JSF الذي يعالج الصفحة باستدعاء نموذجه [Form.java]، على سبيل المثال طريقة getSelectOneListBox1Items. يمكن لهذه الطريقة أن تُرجع مصفوفة من العناصر من نوع SelectItem، بناءً على المعلومات المخزنة في قاعدة البيانات. للقيام بذلك، ستستدعي الطبقة [الأعمال] [2b].

2.7. مثال mv-jsf2-05: التنقل – الجلسة – معالجة الاستثناءات

2.7.1. التطبيق

التطبيق هو نفسه كما كان من قبل، باستثناء أن النموذج يأخذ الآن شكل معالج متعدد الصفحات:

  • في [1]، الصفحة الأولى من النموذج — يمكن الوصول إليها أيضًا عبر الرابط 1 في [2]
  • في [2]، مجموعة من 5 روابط.
  • في [3]، الصفحة 2 من النموذج، يمكن الوصول إليها عبر الرابط 2 في [2]
  • في [4]، الصفحة 3 من النموذج، يمكن الوصول إليها عبر الرابط 3 في [2]
  • في [5]، الصفحة التي تم الوصول إليها عبر رابط "رفع استثناء" في [2]
  • في [6]، الصفحة التي تم الوصول إليها عبر الرابط 4 في [2]. وهي تلخص الإدخالات التي تمت في الصفحات من 1 إلى 3.

2.7.2. مشروع NetBeans

مشروع NetBeans للتطبيق هو كما يلي:

يقدم مشروع [mv-jsf2-05] ميزتين جديدتين:

  1. في [1]، تم تقسيم صفحة JSF [index.xhtml] إلى ثلاث صفحات [form1.xhtml، form2.xhtml، form3.xhtml] تم توزيع الإدخالات عليها. الصفحة [form4.xhtml] هي نسخة من صفحة [index.xhtml] من المشروع السابق. في [2]، تظل فئة [Form.java] دون تغيير. وستعمل كقالب للصفحات الأربع لـ JSF المذكورة أعلاه،
  2. في [3]، تمت إضافة صفحة [exception.xhtml]: سيتم استخدامها عند حدوث استثناء في التطبيق.

2.7.3. صفحات [form.xhtml] ونموذجها [Form.java]

2.7.3.1. كود صفحة XHTML

صفحة JSF [form1.xhtml] هي كما يلي:


<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core">
  <f:view locale="#{changeLocale.locale}">
    <h:head>
      <title>JSF</title>
      <h:outputStylesheet library="css" name="styles.css"/>
    </h:head>
    <h:body style="background-image: url('${request.contextPath}/resources/images/standard.jpg');">
    <h:form id="formulaire">
        <!-- links -->
        <h:panelGrid columns="2">
          <h:commandLink value="#{msg['form.langue1']}" action="#{changeLocale.setFrenchLocale}"/>
          <h:commandLink value="#{msg['form.langue2']}" action="#{changeLocale.setEnglishLocale}"/>
        </h:panelGrid>
        <h1><h:outputText value="#{msg['form1.titre']}"/></h1>
        <h:panelGrid columnClasses="col1,col2" columns="2" border="1">
          <h:outputText value="#{msg['form.headerCol1']}" styleClass="entete"/>
          <h:outputText value="#{msg['form.headerCol2']}" styleClass="entete"/>
          <!-- line 1 -->
          <h:outputText value="inputText"  styleClass="info"/>
          <h:panelGroup>
            <h:outputText value="#{msg['form.loginPrompt']}"/>
            <h:inputText id="inputText" value="#{form.inputText}"/>
          </h:panelGroup>
          <!-- line 2 -->
          <h:outputText value="inputSecret"  styleClass="info"/>
          <h:panelGroup>
            <h:outputText value="#{msg['form.passwdPrompt']}"/>
            <h:inputSecret id="inputSecret" value="#{form.inputSecret}"/>
          </h:panelGroup>
          <!-- line 3 -->
          <h:outputText value="inputTextArea" styleClass="info"/>          
          <h:panelGroup>
            <h:outputText value="#{msg['form.descPrompt']}"/>
            <h:inputTextarea id="inputTextArea" value="#{form.inputTextArea}" rows="4"/>
          </h:panelGroup>         
        </h:panelGrid>
        <!-- links -->
        <h:panelGrid columns="6">
          <h:commandLink value="1" action="form1"/>
          <h:commandLink value="2" action="#{form.doAction2}"/>
          <h:commandLink value="3" action="form3"/>
          <h:commandLink value="4" action="#{form.doAction4}"/>
          <h:commandLink value="#{msg['form.pagealeatoireLink']}" action="#{form.doAlea}"/>
          <h:commandLink value="#{msg['form.exceptionLink']}" action="#{form.throwException}"/>
        </h:panelGrid>
      </h:form>
      </h:body>
  </f:view>
</html>

ويطابق العرض التالي:

لاحظ النقاط التالية:

  • السطر 16: الجدول، الذي كان يحتوي سابقًا على ثلاثة أعمدة، أصبح يحتوي الآن على عمودين فقط. تمت إزالة العمود 3، الذي كان يعرض قيم النموذج. سيتم عرض هذه القيم بواسطة [form4.xhtml
  • الأسطر 40–46: جدول مكون من ستة روابط. الروابط الموجودة في السطرين 44 و46 لها تنقل ثابت: سمة action الخاصة بها مكتوبة بشكل ثابت. الروابط الأخرى لها تنقل ديناميكي: سمة action الخاصة بها تشير إلى طريقة في حبة النموذج المسؤولة عن إرجاع مفتاح التنقل. الطرق المشار إليها في [Form.java] هي كما يلي:

// événements
  public String doAction2(){
    return "form2";
  }
  
  public String doAction4(){
    return "form4";
  }
  
  public String doAlea(){
    // un nombre aléatoire entre 1 et 3
    int i=1+(int)(3*Math.random());
    // on rend la clé de navigation
    return "form"+i;
  }
  
  public String throwException() throws java.lang.Exception{
    throw new Exception("Exception test");
}

سنتجاهل طريقة throwException في السطر 17 في الوقت الحالي. سنعود إليها لاحقًا. تعيد طريقتا doAction2 و doAction4 ببساطة مفتاح التنقل دون إجراء أي معالجة. كان بإمكاننا بسهولة كتابة:


<h:commandLink value="1" action="form1"/>
          <h:commandLink value="2" action="form2"/>
          <h:commandLink value="3" action="form3"/>
          <h:commandLink value="4" action="form4"/>
          <h:commandLink value="#{msg['form.pagealeatoireLink']}" action="#{form.doAlea}"/>
          <h:commandLink value="#{msg['form.exceptionLink']}" action="#{form.throwException}"/>

تقوم طريقة doAlea بإنشاء مفتاح تنقل عشوائي يتم اختيار قيمته من المجموعة {"form1", "form2", "form3"}.

يشبه كود الصفحات [form2.xhtml، form3.xhtml، form3.xhtml] كود الصفحة [form1.xhtml].

2.7.3.2. عمر نموذج [Form.java] لصفحات [form*.xhtml]

لننظر إلى تسلسل الإجراءات التالي:

  • في [1]، نملأ الصفحة 1 وننتقل إلى الصفحة 3،
  • في [2]، نملأ الصفحة 3 ونعود إلى الصفحة 1،
  • في [3]، يتم استرداد الصفحة 1 كما تم إدخالها. ثم نعود إلى الصفحة 3،
  • في [4]، يتم العثور على الصفحة 3 تمامًا كما تم إدخالها.

آلية الحقل المخفي [javax.faces.ViewState] لا تكفي لتفسير هذه الظاهرة.

عند الانتقال من [1] إلى [2]، تحدث عدة خطوات:

  • يتم تحديث النموذج [Form.java] باستخدام POST من [form1.jsp]. على وجه الخصوص، يتلقى حقل inputText القيمة "نص آخر
  • ويؤدي مفتاح التنقل "form3" إلى عرض [form3.xhtml]. يحتوي ViewState المضمن في [form3.xhtml] على حالة المكونات الموجودة في [form3.xhtml] فقط، وليس تلك الموجودة في [form1.xhtml].

عند الانتقال من [2] إلى [3]:

  • يتم تحديث نموذج [Form.java] باستخدام POST من [form3.xhtml]. إذا تم تعيين دورة حياة نموذج [Form.java] على "request"، يتم إنشاء كائن [Form.java] جديد تمامًا قبل تحديثه بواسطة POST من [form3.xhtml]. في هذه الحالة، يعود حقل inputText في النموذج إلى قيمته الافتراضية:

  private String inputText="texte";

ويحتفظ بها: في الواقع، في طلب POST من [form3.xhtml]، لا يوجد ما يقوم بتحديث حقل inputText، الذي يعد جزءًا من نموذج [form1.xhtml] وليس من [form3.xhtml

  • مفتاح التنقل "form1" يؤدي إلى عرض [form1.xhtml]. تعرض الصفحة القالب الخاص بها. في حالتنا، سيعرض حقل إدخال تسجيل الدخول المرتبط بقالب inputText "text" وليس القيمة "another text" التي تم إدخالها في [1]. لكي يحتفظ حقل inputText بالقيمة التي تم إدخالها في [1]، يجب أن يكون نطاق قالب [Form.java] هو session وليس request. في هذه الحالة،
    • بعد طلب POST من [form1.xhtml]، سيتم وضع النموذج في جلسة عمل العميل. سيحتوي حقل inputText على القيمة "some other text
    • عند إرسال [form3.xhtml] عبر POST، سيتم استرداد النموذج من هذه الجلسة وتحديثه بواسطة POST من [form3.xhtml]. لن يتم تحديث حقل inputText بواسطة هذا الطلب POST ولكنه سيحتفظ بالقيمة "some other text" التي تم الحصول عليها بعد الطلب POST من [form1.xhtml] [1].

وبالتالي، يكون إعلان حبة [Form.java] كما يلي:


package forms;
 
import javax.enterprise.context.SessionScoped;
import javax.faces.bean.ManagedBean;
import javax.faces.model.SelectItem;
 
@ManagedBean
@SessionScoped
public class Form {

السطر 8 يمنح الفول نطاق الجلسة.

2.7.4. معالجة الاستثناءات

دعونا نراجع البنية العامة لتطبيق JSF:

ماذا يحدث عندما يلتقط معالج الأحداث أو النموذج استثناءً ناشئًا عن طبقة الأعمال — مثل انقطاع غير متوقع للاتصال بقاعدة البيانات؟

  • يمكن لمعالجات الأحداث [2a] اعتراض أي استثناء قادم من طبقة [الأعمال] وإرجاع مفتاح تنقل إلى وحدة التحكم [Faces Servlet] يشير إلى صفحة خطأ خاصة بالاستثناء؛
  • بالنسبة للنماذج، هذا الحل غير قابل للتطبيق لأنه عند استدعائها [3,4]، يكون النظام في مرحلة عرض صفحة XHTML محددة ولم يعد في مرحلة الاختيار. كيف يمكن تغيير الصفحات أثناء مرحلة عرض إحدى هذه الصفحات؟ هناك حل بسيط، وإن لم يكن مناسبًا دائمًا، وهو عدم معالجة الاستثناء، والذي سينتقل بعد ذلك إلى حاوية السيرفلت التي تشغل التطبيق. يمكن تكوين الحاوية لعرض صفحة محددة عند وصول استثناء إليها. هذا الحل قابل للتطبيق دائمًا، وسنقوم بفحصه الآن.

2.7.4.1. تكوين تطبيق الويب لمعالجة الاستثناءات

يتم تكوين تطبيق الويب لمعالجة الاستثناءات في ملف [web.xml] الخاص به:


<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
  <context-param>
    <param-name>javax.faces.STATE_SAVING_METHOD</param-name>
    <param-value>client</param-value>
  </context-param>  
  <context-param>
    <param-name>javax.faces.PROJECT_STAGE</param-name>
    <param-value>Development</param-value>
  </context-param>
  <context-param>
    <param-name>javax.faces.FACELETS_SKIP_COMMENTS</param-name>
    <param-value>true</param-value>
  </context-param> 
  <servlet>
    <servlet-name>Faces Servlet</servlet-name>
    <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>Faces Servlet</servlet-name>
    <url-pattern>/faces/*</url-pattern>
  </servlet-mapping>
  <session-config>
    <session-timeout>
      30
    </session-timeout>
  </session-config>
  <welcome-file-list>
    <welcome-file>faces/form1.xhtml</welcome-file>
  </welcome-file-list>
  <error-page>
    <error-code>500</error-code>
    <location>/faces/exception.xhtml</location>
  </error-page>
  <error-page>
    <exception-type>java.lang.Exception</exception-type>
    <location>/faces/exception.xhtml</location>
  </error-page>
</web-app>

تحتوي الأسطر 32–39 على تعريف صفحتي خطأ. يمكنك استخدام أي عدد تريده من علامات <error-page>. تحدد علامة <location> الصفحة التي سيتم عرضها في حالة حدوث خطأ. يمكن تعريف نوع الخطأ المرتبط بالصفحة بطريقتين:

  • باستخدام علامة <exception-type>، التي تحدد نوع Java للاستثناء الذي تمت معالجته. وبالتالي، تحدد علامة <error-page> في الأسطر 36–39 أنه إذا اكتشف حاوية السيرفلت استثناءً من النوع [java.lang.Exception] أو نوع مشتق (السطر 37) أثناء تنفيذ التطبيق، فيجب عليها عرض الصفحة [/faces/exception.xhtml] (السطر 38). باستخدام نوع الاستثناء الأكثر عمومية [java.lang.Exception] هنا، نضمن معالجة جميع الاستثناءات،
  • عبر العلامة <error-code> (السطر 33)، التي تحدد رمز خطأ HTTP. على سبيل المثال، إذا طلب متصفح عنوان URL [http://machine:port/contexte/P] ولم تكن الصفحة P موجودة في سياق التطبيق، فإن التطبيق لا يتدخل في الرد. بل يقوم حاوية السيرفلت (servlet container) بإنشاء هذا الرد عن طريق إرسال صفحة خطأ افتراضية. يحتوي السطر الأول من استجابة HTTP على رمز خطأ 404 يشير إلى أن الصفحة P المطلوبة غير موجودة. قد ترغب في إنشاء استجابة تتبع، على سبيل المثال، دليل الأسلوب المرئي للتطبيق أو توفر روابط لحل المشكلة. في هذه الحالة، ستستخدم علامة <error-page> مع علامة <error-code>404</error-code>.

أعلاه، رمز خطأ HTTP 500 هو الرمز الذي يتم إرجاعه في حالة "تعطل" التطبيق. هذا هو الرمز الذي سيتم إرجاعه إذا تم نشر استثناء إلى حاوية السيرفلت. وبالتالي، من المحتمل أن تكون علامتا <error-page> في الأسطر 28-35 زائدتين عن الحاجة. قمنا بتضمين كليهما لتوضيح طريقتين للتعامل مع الخطأ.

2.7.4.2. محاكاة الاستثناء

يتم تشغيل الاستثناء بشكل مصطنع بواسطة الرابط [Throw an exception]:

يؤدي النقر على رابط [إلقاء استثناء] [1] إلى عرض الصفحة [2].

في كود صفحات [formx.xhtml]، يتم إنشاء رابط [إلقاء استثناء] على النحو التالي:


<!-- liens -->
        <h:panelGrid columns="6">
          <h:commandLink value="1" action="form1"/>
...
          <h:commandLink value="#{msg['form.exceptionLink']}" action="#{form.throwException}"/>
        </h:panelGrid>

في السطر 5، نرى أنه عند النقر على الرابط، سيتم تنفيذ طريقة [form].throwException. وهي كما يلي:


  public String throwException() throws java.lang.Exception{
    throw new Exception("Exception test");
}

هنا، يتم إلقاء استثناء من النوع [java.lang.Exception]. سينتقل هذا الاستثناء إلى حاوية السيرفلت، والتي ستعرض بعد ذلك الصفحة [/faces/exception.xhtml].

2.7.4.3. المعلومات المتعلقة بالاستثناء

عندما ينتقل الاستثناء إلى حاوية السيرفلت، تعرض الحاوية صفحة الخطأ المقابلة عن طريق تمرير المعلومات المتعلقة بالاستثناء إليها. تضاف هذه المعلومات كسمات جديدة إلى الطلب الذي يجري معالجته حاليًا. يتم تغليف طلب المتصفح والاستجابة التي سيتلقاها في كائنات Java من النوع [HttpServletRequest request] و [HttpServletResponse response]. تتوفر هذه الكائنات في جميع مراحل معالجة طلب المتصفح.

عند استلام طلب HTTP من المتصفح، تقوم حاوية السيرفلت بتغليفه في كائن Java [HttpServletRequest request] وإنشاء الكائن [HttpServletResponse response]، الذي سيُستخدم لتوليد الاستجابة. يحتوي هذا الكائن، على وجه الخصوص، على قناة TCP/IP التي سيتم استخدامها لتدفق استجابة HTTP. جميع الطبقات t1، t2، ...، tn المشاركة في معالجة كائن الطلب لديها حق الوصول إلى هذين الكائنين. يمكن لكل منها الوصول إلى عناصر الطلب الأولي وإعداد الاستجابة من خلال إثراء كائن الاستجابة. على سبيل المثال، يمكن لطبقة الترجمة تعيين لغة الاستجابة باستخدام الطريقة response.setLocale(Locale l).

يمكن للطبقات المختلفة تبادل المعلومات فيما بينها عبر كائن الطلب. يحتوي هذا الكائن على قاموس للسمات، يكون فارغًا عند إنشائه، ويمكن أن تملأه طبقات المعالجة المتتالية. يمكن لهذه الطبقات وضع المعلومات الضرورية لطبقة المعالجة التالية في سمات كائن الطلب. هناك طريقتان لإدارة سمات كائن الطلب:

  • void setAttribute(String s, Object o)، التي تضيف كائن o المحدد بالسلسلة s إلى السمات،
  • Object getAttribute(String s)، التي تسترد السمة o المحددة بالسلسلة s.

عندما تنتقل استثناء إلى حاوية السيرفلت، تقوم الحاوية بتعيين السمات التالية في الطلب قيد المعالجة:

key
القيمة
javax.servlet.error.status_code
رمز خطأ HTTP الذي سيتم إرجاعه إلى العميل
javax.servlet.error.exception
نوع الاستثناء في Java مع رسالة الخطأ.
javax.servlet.error.request_uri
عنوان URL الذي تم طلبه عند حدوث الاستثناء
javax.servlet.error.servlet_name
البرنامج الخادم الذي كان يعالج الطلب عند حدوث الاستثناء

سنستخدم سمات الطلب هذه في صفحة [exception.xhtml] لعرضها.

2.7.4.4. صفحة الخطأ [ exception.xhtml]

محتواها كما يلي:


<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core">
  <f:view locale="#{changeLocale.locale}">
    <h:head>
      <title>JSF</title>
      <h:outputStylesheet library="css" name="styles.css"/>
    </h:head>
    <h:body style="background-image: url('${request.contextPath}/resources/images/standard.jpg');">
      <h:form id="formulaire">
        <h3><h:outputText value="#{msg['exception.header']}"/></h3>
        <h:panelGrid columnClasses="col1,col2" columns="2" border="1">
          <h:outputText value="#{msg['exception.httpCode']}"/>
          <h:outputText value="#{requestScope['javax.servlet.error.status_code']}"/>
          <h:outputText value="#{msg['exception.message']}"/>
          <h:outputText value="#{requestScope['javax.servlet.error.exception']}"/>
          <h:outputText value="#{msg['exception.requestUri']}"/>
          <h:outputText value="#{requestScope['javax.servlet.error.request_uri']}"/>
          <h:outputText value="#{msg['exception.servletName']}"/>
          <h:outputText value="#{requestScope['javax.servlet.error.servlet_name']}"/>
        </h:panelGrid>
        <!-- links -->
        <h:panelGrid columns="6">
          <h:commandLink value="1" action="form1"/>
          <h:commandLink value="2" action="#{form.doAction2}"/>
          <h:commandLink value="3" action="form3"/>
          <h:commandLink value="4" action="#{form.doAction4}"/>
          <h:commandLink value="#{msg['form.pagealeatoireLink']}" action="#{form.doAlea}"/>
        </h:panelGrid>
      </h:form>
    </h:body>
  </f:view>
</html>

2.7.4.4.1. التعبيرات في صفحة الاستثناء

في سلسلة معالجة طلبات العميل، عادةً ما تكون صفحة XHTML هي الحلقة الأخيرة في السلسلة:

جميع العناصر في السلسلة هي فئات Java، بما في ذلك صفحة XHTML. يتم تحويل هذه الصفحة إلى سيرفلت بواسطة حاوية السيرفلت، أي إلى فئة Java قياسية. وبشكل أكثر تحديدًا، يتم تحويل صفحة XHTML إلى كود Java يتم تشغيله ضمن الطريقة التالية:


public void _jspService(HttpServletRequest request, HttpServletResponse response)
throws java.io.IOException, ServletException {
 
JspFactory _jspxFactory = null;
PageContext pageContext = null;
HTTPSession session = null;
ServletContext application = null;
ServletConfig config = null;
JspWriter out = null;
Object page = this;
JspWriter _jspx_out = null;
PageContext _jspx_page_context = null;
... 
...code de la page XHTML
 

ابتداءً من السطر 14، ستجد كود Java المطابق لصفحة XHTML. سيحتوي هذا الكود على عدد من الكائنات التي تم تهيئتها بواسطة طريقة _jspService في السطر 1 أعلاه:

  • السطر 1: HttpServletRequest request: الطلب الذي يجري معالجته حاليًا،
  • السطر 1: HttpServletResponse response: الاستجابة التي سيتم إرسالها إلى العميل،
  • السطر 7: ServletContext application: كائن يمثل تطبيق الويب نفسه. مثل كائن request، يمكن أن يحتوي كائن application على سمات. يتم مشاركة هذه السمات بين جميع الطلبات من جميع العملاء. وهي عادةً سمات للقراءة فقط،
  • السطر 6: HTTPSession session: يمثل جلسة عمل العميل. مثل كائني request و application، يمكن أن يحتوي كائن session على سمات. يتم مشاركة هذه السمات بين جميع الطلبات الواردة من نفس العميل،
  • السطر 9: JspWriter out: دفق كتابة إلى متصفح العميل. هذا الكائن مفيد لتصحيح أخطاء صفحة XHTML. سيتم عرض أي شيء مكتوب عبر out.println(text) في متصفح العميل.

عند كتابة #{expression} في صفحة JSF، يمكن أن يكون expression مفتاح سمة لكائنات الطلب أو الجلسة أو التطبيق المذكورة أعلاه. يتم البحث عن السمة المقابلة بالتتابع في هذه الكائنات الثلاثة. وبالتالي، يتم تقييم #{key} على النحو التالي:

  1. request.getAttribute(key)
  2. session.getAttribute(key)
  3. application.getAttribute(key)

بمجرد الحصول على قيمة غير فارغة، يتوقف تقييم #{key}. قد ترغب في أن تكون أكثر تحديدًا من خلال الإشارة إلى السياق الذي يجب البحث فيه عن السمة:

  • #{requestScope['key']} للبحث عن السمة في كائن الطلب،
  • #{sessionScope['key']} للبحث عن السمة في كائن الجلسة،
  • #{applicationScope['key']} للبحث عن السمة في كائن التطبيق.

هذا ما تم تنفيذه في صفحة [exception.xhtml] في الصفحة 116. والسمات المستخدمة هي كما يلي:

key
domain
القيمة
javax.servlet.error.status_code
الطلب
javax.servlet.error.exception
نفس
نفس
javax.servlet.error.request_uri
نفس
نفس
javax.servlet.error.servlet_name
نفس
نفس

تمت إضافة الرسائل المختلفة المطلوبة لصفحة JSF [exception.xhtml] إلى ملفات الرسائل الموجودة:

[messages_fr.properties]


exception.header=L'exception suivante s'est produite
exception.httpCode=Code HTTP de l'erreur
exception.message=Message de l'exception
exception.requestUri=URL demandée lors de l'erreur
exception.servletName=Nom de la servlet demandée lorsque l'erreur s'est produite

[messages_en.properties]


exception.header=The following error occurred
exception.httpCode=HTTP error code
exception.message=Exception message
exception.requestUri=URL requested when error occurred
exception.servletName=Servlet requested when error occurred

2.8. مثال mv-jsf2-06: التحقق من صحة مدخلات المستخدم وتحويلها

2.8.1. التطبيق

يعرض التطبيق نموذج إدخال. عند التحقق من صحة البيانات، يتم إرجاع النموذج نفسه كاستجابة، مصحوبًا بأي رسائل خطأ إذا تبين أن الإدخال غير صحيح.

2.8.2. مشروع NetBeans

مشروع NetBeans للتطبيق هو كما يلي:

يعتمد مشروع [mv-jsf2-06] مرة أخرى على صفحة واحدة [index.html] [1] ونموذجها [Form.java] [2]. ويستمر في استخدام الرسائل من [messages.properties] ولكن باللغة الفرنسية فقط [3]. خيار تغيير اللغة غير متاح.

2.8.3. بيئة التطبيق

نقدم هنا محتويات الملفات التي تهيئ التطبيق دون تقديم تفسيرات محددة. تساعد هذه الملفات على فهم ما يلي بشكل أفضل.

[ faces-config.xml]


<?xml version='1.0' encoding='UTF-8'?>
 
<!-- =========== FULL CONFIGURATION FILE ================================== -->
 
<faces-config version="2.0"
              xmlns="http://java.sun.com/xml/ns/javaee" 
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
              xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd">
 
  <application>
    <resource-bundle>
      <base-name>
        messages
      </base-name>
      <var>msg</var>
    </resource-bundle>
    <message-bundle>messages</message-bundle>
  </application>
</faces-config>

السطر 17 جديد. سيتم شرحه لاحقًا.

ملف الرسائل [messages_fr.properties]


form.titre=Jsf - validations et conversions
saisie1.prompt=1-Nombre entier de type int
saisie2.prompt=2-Nombre entier de type int
saisie3.prompt=3-Nombre entier de type int
data.required=Vous devez entrer une donn\u00e9e
integer.required=Vous devez entrer un nombre entier
saisie4.prompt=4-Nombre entier de type int dans l'intervalle [1,10]
saisie4.error=4-Vous devez entrer un nombre entier dans l'intervalle [1,10]
saisie5.prompt=5-Nombre r\u00e9el de type double
double.required=Vous devez entrer un nombre
saisie6.prompt=6-Nombre r\u00e9el>=0  de type double
saisie6.error=6-Vous devez entrer un nombre >=0
saisie7.prompt=7-Bool\u00e9en
saisie7.error=7-Vous devez entrer un bool\u00e9en
saisie8.prompt=8-Date au format jj/mm/aaaa
saisie8.error=8-Vous devez entrer une date valide au format jj/mm/aaaa
date.required=Vous devez entrer une date
saisie9.prompt=9-Cha\u00eene de 4 caract\u00e8res
saisie9.error=9-Vous devez entrer une cha\u00eene de 4 caract\u00e8res exactement
saisie9B.prompt=9B-Heure au format hh:mm
saisie9B.error=La cha\u00eene saisie ne respecte pas le format hh:mm
submit=Valider
cancel=Annuler
saisie.type=Type de la saisie
saisie.champ=Champ de saisie
saisie.erreur=Erreur de saisie
bean.valeur=Valeurs du mod\u00e8le du formulaire
saisie10.prompt=10-Nombre entier de type int <1 ou >7
saisie10.incorrecte=10-Saisie n\u00b0 10 incorrecte
saisie10.incorrecte_detail=10-Vous devez entrer un nombre entier <1 ou >7
saisies11et12.incorrectes=La propri\u00e9t\u00e9 saisie11+saisie12=10 n'est pas v\u00e9rifi\u00e9e
saisies11et12.incorrectes_detail=La propri\u00e9t\u00e9 saisie11+saisie12=10 n'est pas v\u00e9rifi\u00e9e
saisie11.prompt=11-Nombre entier de type int
saisie12.prompt=12-Nombre entier de type int
error.sign="!"
error.sign_detail="!"

ورقة الأنماط [styles.css] هي كما يلي:


.info{
   font-family: Arial,Helvetica,sans-serif;
   font-size: 14px;
   font-weight: bold
}
 
.col1{
   background-color: #ccccff
}
 
.col2{
   background-color: #ffcccc
}
 
.col3{
   background-color: #ffcc66
}
 
.col4{
   background-color: #ccffcc
}

.error{
   color: #ff0000
}
 
.saisie{
   background-color: #ffcccc;
   border-color: #000000;
   border-width: 5px;
   color: #cc0033;
   font-family: cursive;
   font-size: 16px
}
 
.entete{
   font-family: 'Times New Roman',Times,serif;
   font-size: 14px;
   font-weight: bold
}

2.8.4. صفحة [index.xhtml] وقالبها [Form.java]

صفحة [index.xhtml] هي كما يلي:


<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core">
  <h:head>
    <title>JSF</title>
    <h:outputStylesheet library="css" name="styles.css"/>
  </h:head>
  <h:body style="background-image: url('${request.contextPath}/resources/images/standard.jpg');">
    <h2><h:outputText value="#{msg['form.titre']}"/></h2>
    <h:form id="formulaire">
      <h:messages globalOnly="true" />
      <h:panelGrid columns="4" columnClasses="col1,col2,col3,col4" border="1">
        <!-- line 1 -->
        <h:outputText value="#{msg['saisie.type']}" styleClass="entete"/>
        <h:outputText value="#{msg['saisie.champ']}" styleClass="entete"/>
        <h:outputText value="#{msg['saisie.erreur']}" styleClass="entete"/>
        <h:outputText value="#{msg['bean.valeur']}" styleClass="entete"/>
        <!-- line 2 -->
        <h:outputText value="#{msg['saisie1.prompt']}"/>
        <h:inputText id="saisie1" value="#{form.saisie1}" styleClass="saisie"/>
        <h:message for="saisie1" styleClass="error"/>
        <h:outputText value="#{form.saisie1}"/>
        <!-- line 3 -->
        <h:outputText value="#{msg['saisie2.prompt']}" />
        <h:inputText id="saisie2" value="#{form.saisie2}"  styleClass="saisie"/>
        <h:message for="saisie2" showSummary="true" showDetail="false" styleClass="error"/>
        <h:outputText value="#{form.saisie2}"/>
        <!-- line 4 -->
        <h:outputText value="#{msg['saisie3.prompt']}" />
        <h:inputText id="saisie3" value="#{form.saisie3}"  styleClass="saisie" required="true" requiredMessage="#{msg['data.required']}" converterMessage="#{msg['integer.required']}"/>
        <h:message for="saisie3" styleClass="error"/>
        <h:outputText value="#{form.saisie3}"/>
        <!-- line 5 -->
        <h:outputText value="#{msg['saisie4.prompt']}" />
        <h:inputText id="saisie4" value="#{form.saisie4}"  styleClass="saisie" required="true" requiredMessage="#{msg['data.required']}" converterMessage="#{msg['integer.required']}" validatorMessage="#{msg['saisie4.error']}">
          <f:validateLongRange minimum="1" maximum="10" />
        </h:inputText>
        <h:message for="saisie4" styleClass="error"/>
        <h:outputText value="#{form.saisie4}"/>
        <!-- line 6 -->
        ...
        <!-- line 7 -->
        ...
        <!-- line 8 -->
        ...
        <!-- line 9 -->
        ...
        <!-- line 10 -->
        ...
        <!-- line 11 -->
        ...
        <!-- line 12 -->
        ...
        <!-- line 13 -->
        ...
      </h:panelGrid>
      <!-- control buttons -->
      <h:panelGrid columns="2">
        <h:commandButton value="#{msg['submit']}" action="#{form.submit}"/>
        <h:commandButton value="#{msg['cancel']}" immediate="true" action="#{form.cancel}"/>
      </h:panelGrid>
    </h:form>
  </h:body>
</html>

الميزة الجديدة الرئيسية هي استخدام العلامات:

  • لعرض رسائل الخطأ <h:messages> (السطر 14)، <h:message> (الأسطر 24، 29، 34)،
  • التي تفرض قيودًا على صحة المدخلات <f:validateLongRange> (السطر 39)، <f:validateDoubleRange>، <f:validateLength<f:validateRegex>،
  • التي تحدد محولًا بين المدخلات ونموذجها، مثل <f:convertDateTime>.

القالب لهذه الصفحة هو فئة [Form.java] التالية:


package forms;
 
import com.corejsf.util.Messages;
import java.util.Date;
import javax.enterprise.context.RequestScoped;
import javax.faces.application.FacesMessage;
import javax.faces.bean.ManagedBean;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.validator.ValidatorException;
 
@ManagedBean
@RequestScoped
public class Form {
 
public Form() {
}
// foreclosures
private Integer saisie1 = 0;
private Integer saisie2 = 0;
private Integer saisie3 = 0;
private Integer saisie4 = 0;
private Double saisie5 = 0.0;
private Double saisie6 = 0.0;
private Boolean saisie7 = true;
private Date saisie8 = new Date();
private String saisie9 = "";
private Integer saisie10 = 0;
private Integer saisie11 = 0;
private Integer saisie12 = 0;
private String errorSaisie11 = "";
private String errorSaisie12 = "";
 
// actions
public String submit() {
...
}
 
public String cancel() {
...
}
 
// validators
public void validateSaisie10(FacesContext context, UIComponent component, Object value) {
...
}
// getters and setters
...
}

الميزة الجديدة هنا هي أن حقول النموذج لم تعد من النوع String فحسب، بل من أنواع مختلفة.

2.8.5. مدخلات النماذج المختلفة

سنقوم الآن بفحص مدخلات النموذج المختلفة واحدة تلو الأخرى.

2.8.5.1. المدخلات من 1 إلى 4: إدخال عدد صحيح

تقدم صفحة [index.xhtml] الحقل 1 بالشكل التالي:


<!-- ligne 2 -->
        <h:outputText value="#{msg['saisie1.prompt']}"/>
        <h:inputText id="saisie1" value="#{form.saisie1}" styleClass="saisie"/>
        <h:message for="saisie1" styleClass="error"/>
        <h:outputText value="#{form.saisie1}"/>

يتم تعريف نموذج form.saisie1 على النحو التالي في [Form.java]:


private Integer saisie1 = 0;

عندما يرسل المتصفح طلب GET، تعرض صفحة [index.xhtml] المرتبطة بقالب [Form.java] ما يلي:

  • السطر 2 ينتج [1]،
  • السطر 3 ينتج [2]،
  • السطر 4 يعرض [3]،
  • السطر 5 يعرض [4].

لنفترض إدخال الإدخال التالي وإرساله:

ثم نحصل على النتيجة التالية في النموذج الذي يعرضه التطبيق:

  • في [1]، الإدخال غير الصحيح،
  • في [2]، رسالة الخطأ التي تشير إلى ذلك،
  • في [3]، نلاحظ أن قيمة حقل `saisie1` من نوع `Integer` في النموذج لم تتغير.

دعونا نوضح ما حدث. للقيام بذلك، دعونا نعود إلى دورة معالجة صفحة JSF:

نقوم بفحص هذه الدورة للمكون:


<h:inputText id="saisie1" value="#{form.saisie1}" styleClass="saisie"/>

وقوالبه:


private Integer saisie1 = 0;
  • في [A]، يتم استعادة صفحة [index.xhtml] التي تم إرسالها أثناء طلب GET للمتصفح. في [A]، تكون الصفحة تمامًا كما استلمها المستخدم. يعود المكون id="saisie1" إلى قيمته الأولية "0"،
  • في [B]، تتلقى مكونات الصفحة القيم التي أرسلها المتصفح. في [B]، تكون الصفحة كما أدخلها المستخدم وصدق عليها. يتلقى المكون id="saisie1" القيمة المرسلة "x"،
  • في [C]، إذا كانت الصفحة تحتوي على أدوات التحقق من الصحة والمحولات الصريحة، يتم تنفيذها. يتم أيضًا تنفيذ المحولات الضمنية إذا كان نوع الحقل المرتبط بالمكون ليس من نوع String. هذا هو الحال هنا، حيث يكون حقل form.saisie1 من نوع Integer. سيحاول JSF تحويل القيمة "x" للمكون id="saisie1" إلى نوع Integer. سيؤدي هذا إلى حدوث خطأ سيوقف دورة المعالجة [A-F]. سيتم ربط هذا الخطأ بالمكون id="saisie1". عبر [D2]، ننتقل بعد ذلك مباشرة إلى مرحلة عرض الاستجابة. يتم إرجاع نفس الصفحة [index.xhtml
  • ولا تحدث المرحلة [D] إلا إذا اجتازت جميع المكونات الموجودة على الصفحة مرحلة التحويل/التحقق من الصحة. وفي هذه المرحلة، سيتم تعيين قيمة المكون id="saisie1" إلى نموذج form.saisie1 الخاص به.

إذا فشلت المرحلة [C]، يتم إعادة عرض الصفحة ويتم تنفيذ الكود التالي مرة أخرى:


<!-- ligne 2 -->
        <h:outputText value="#{msg['saisie1.prompt']}"/>
        <h:inputText id="saisie1" value="#{form.saisie1}" styleClass="saisie"/>
        <h:message for="saisie1" styleClass="error"/>
<h:outputText value="#{form.saisie1}"/>

الرسالة المعروضة في [2] مأخوذة من السطر 4 في ملف [index.xhtml]. تعرض العلامة <h:message for="idComposant"/> رسالة الخطأ المرتبطة بالمكون المحدد بواسطة السمة for، في حالة حدوث خطأ. الرسالة المعروضة في [2] هي رسالة قياسية وتوجد في ملف [javax/faces/Messages.properties] داخل أرشيف [jsf-api.jar]:

في [2]، يمكننا أن نرى أن ملف الرسائل موجود في عدة صيغ. دعونا نفحص محتويات [Messages_fr.properties]:

...
# ==============================================================================
# Component Errors
# ==============================================================================
javax.faces.component.UIInput.CONVERSION={0} : une erreur de conversion est survenue.
javax.faces.component.UIInput.REQUIRED={0} : erreur de validation. Vous devez indiquer une valeur.
javax.faces.component.UIInput.UPDATE={0} : une erreur est survenue lors du traitement des informations que vous avez soumises. 
javax.faces.component.UISelectOne.INVALID={0} : erreur de validation. La valeur est incorrecte.
javax.faces.component.UISelectMany.INVALID={0} : erreur de validation. La valeur est incorrecte.

# ==============================================================================
# Converter Errors
# ==============================================================================
...
javax.faces.converter.FloatConverter.FLOAT={2} : «{0 doit être un nombre composé dun ou de plusieurs chiffres.
javax.faces.converter.FloatConverter.FLOAT_detail={2} : «{0 doit être un nombre compris entre 1.4E-45 et 3.4028235E38. Exemple : {1}
javax.faces.converter.IntegerConverter.INTEGER={2} : «{0 doit être un nombre composé dun ou de plusieurs chiffres.
javax.faces.converter.IntegerConverter.INTEGER_detail={2} : «{0 doit être un nombre compris entre -2147483648 et 2147483647. Exemple : {1}
...


# ==============================================================================
# Validator Errors
# ==============================================================================
javax.faces.validator.DoubleRangeValidator.MAXIMUM={1} : erreur de validation. La valeur est supérieure à la valeur maximale autorisée, "{0}".
javax.faces.validator.DoubleRangeValidator.MINIMUM={1} : erreur de validation. La valeur est inférieure à la valeur minimale autorisée, "{0}".
javax.faces.validator.DoubleRangeValidator.NOT_IN_RANGE={2} : erreur de validation. Lattribut spécifié nest pas compris entre les valeurs attendues {0} et {1}.
javax.faces.validator.DoubleRangeValidator.TYPE={0} : erreur de validation. La valeur nest pas du type correct.
...

يحتوي الملف على رسائل مقسمة إلى فئات:

  • أخطاء في أحد المكونات، السطر 3،
  • أخطاء التحويل بين المكون ونموذجه، السطر 12
  • أخطاء التحقق من الصحة عند وجود أدوات التحقق من الصحة في الصفحة، السطر 23.

الخطأ الذي حدث في المكون id="saisie1" هو خطأ تحويل من نوع String إلى نوع Integer. رسالة الخطأ المرتبطة به هي تلك الموجودة في السطر 18 من ملف الرسائل.

javax.faces.converter.IntegerConverter.INTEGER_detail={2} : «{0}» doit être un nombre compris entre -2147483648 et 2147483647. Exemple : {1}

فيما يلي نسخة من رسالة الخطأ التي تم عرضها:

يمكننا أن نرى في الرسالة:

  • تم استبدال المعلمة {2} بمعرف المكون الذي حدث فيه خطأ التحويل،
  • تم استبدال المعلمة {0} بالقيمة التي تم إدخالها في [1] للمكون،
  • تم استبدال المعلمة {1} بالرقم 9346.

تحتوي معظم الرسائل المتعلقة بالمكونات على نسختين: نسخة موجزة ونسخة مفصلة. وهذا هو الحال بالنسبة للسطور 16-18:

javax.faces.converter.IntegerConverter.INTEGER={2} : «{0}» doit être un nombre composé d’un ou de plusieurs chiffres.
javax.faces.converter.IntegerConverter.INTEGER_detail={2} : «{0}» doit être un nombre compris entre -2147483648 et 2147483647. Exemple : {1}

الرسالة التي تحتوي على مفتاح _detail (السطر 2) هي ما يُعرف بالرسالة التفصيلية. أما الأخرى فهي ما يُعرف بالرسالة الموجزة. تعرض علامة <h:message> الرسالة التفصيلية بشكل افتراضي. يمكن تغيير هذا السلوك باستخدام السمتين showSummary و showDetail. وهذا ما يتم تنفيذه للمكون الذي يحمل المعرف saisie2:


        <!-- ligne 3 -->
        <h:outputText value="#{msg['saisie2.prompt']}" />
        <h:inputText id="saisie2" value="#{form.saisie2}"  styleClass="saisie"/>
        <h:message for="saisie2" showSummary="true" showDetail="false" styleClass="error"/>
<h:outputText value="#{form.saisie2}"/>

السطر 2: المكون saisie2 مرتبط بحقل form.saisie2 التالي:


  private Integer saisie2 = 0;

والنتيجة التي تم الحصول عليها هي كما يلي:

  • في [1]، الرسالة التفصيلية؛ في [2]، الرسالة الموجزة.

تعرض علامة <h:messages> جميع رسائل الخطأ الملخصة من جميع المكونات، بالإضافة إلى رسائل الخطأ غير المرتبطة بمكون معين، في شكل قائمة. وهنا أيضًا، يمكن للسمات تغيير هذا السلوك الافتراضي:

  • showDetail: true / false لتمكين أو تعطيل الرسائل التفصيلية،
  • showSummary: true / false لعرض أو إخفاء الرسائل الموجزة،
  • globalOnly: true / false لتحديد ما إذا كان سيتم عرض رسائل الخطأ غير المرتبطة بالمكونات فقط. يمكن، على سبيل المثال، إنشاء مثل هذه الرسالة بواسطة المطور.

يمكن تعديل رسالة الخطأ المرتبطة بعملية التحويل بعدة طرق. أولاً، يمكنك توجيه التطبيق لاستخدام ملف رسائل مختلف. يتم إجراء هذا التغيير في [faces-config.xml]:


<faces-config ...">
  <application>
    <resource-bundle>
      <base-name>
        messages
      </base-name>
      <var>msg</var>
    </resource-bundle>
    <message-bundle>messages</message-bundle>
  </application>
...
</faces-config>

تحدد الأسطر 3–8 ملف رسائل، ولكن هذا ليس الملف الذي تستخدمه علامتا <h:message> و <h:messages>. يجب عليك استخدام علامة <message-bundle> في السطر 9 لتعريفه. يخبر السطر 9 علامات <h:message(s)> أنه يجب البحث في ملف [messages.properties] قبل ملف [javax.faces.Messages.properties]. لذا، إذا أضفنا الأسطر التالية إلى ملف [messages_fr.properties]:


# conversions
javax.faces.converter.IntegerConverter.INTEGER=erreur
javax.faces.converter.IntegerConverter.INTEGER_detail=erreur d\u00e9taill\u00e9e

يصبح الخطأ الذي يتم إرجاعه لمكونات input1 و input2 كما يلي:

Image

هناك طريقة أخرى لتعديل رسالة خطأ التحويل وهي استخدام سمة converterMessage الخاصة بالمكون، كما هو موضح أدناه لمكون saisie3:


        <!-- ligne 4 -->
        <h:outputText value="#{msg['saisie3.prompt']}" />
        <h:inputText id="saisie3" value="#{form.saisie3}"  styleClass="saisie" required="true" requiredMessage="#{msg['data.required']}" converterMessage="#{msg['integer.required']}"/>
        <h:message for="saisie3" styleClass="error"/>
<h:outputText value="#{form.saisie3}"/>

يرتبط مكون saisie3 بحقل form.saisie3 التالي:


  private Integer saisie3 = 0;
  • السطر 3، تحدد السمة converterMessage بشكل صريح الرسالة التي سيتم عرضها في حالة حدوث خطأ في التحويل،
  • السطر 3، تشير السمة required="true" إلى أن الإدخال مطلوب. لا يمكن أن يظل الحقل فارغًا. يعتبر الحقل فارغًا إذا لم يحتوِ على أي أحرف أو إذا احتوى على سلسلة من المسافات. مرة أخرى، توجد رسالة افتراضية في [javax.faces.Messages.properties]:
javax.faces.component.UIInput.REQUIRED={0} : erreur de validation. Vous devez indiquer une valeur.

تسمح لك السمة requiredMessage باستبدال هذه الرسالة الافتراضية. إذا كان ملف [messages.properties] يحتوي على الرسائل التالية:


...
data.required=Vous devez entrer une donnée
integer.required=Vous devez entrer un nombre entier
 

يمكن الحصول على النتيجة التالية:

أو هذه:

التحقق من أن المدخلات هي بالفعل عدد صحيح لا يكفي دائمًا. في بعض الأحيان، تحتاج إلى التحقق من أن الرقم الذي تم إدخاله يقع ضمن نطاق معين. في مثل هذه الحالات، يتم استخدام أداة التحقق من الصحة. يقدم المدخل رقم 4 مثالاً على ذلك. وفيما يلي كود [index.xhtml]:


        <!-- ligne 5 -->
        <h:outputText value="#{msg['saisie4.prompt']}" />
        <h:inputText id="saisie4" value="#{form.saisie4}"  styleClass="saisie" required="true" requiredMessage="#{msg['data.required']}" converterMessage="#{msg['integer.required']}" validatorMessage="#{msg['saisie4.error']}">
          <f:validateLongRange minimum="1" maximum="10" />
        </h:inputText>
        <h:message for="saisie4" styleClass="error"/>
<h:outputText value="#{form.saisie4}"/>

السطر 3: المكون saisie4 مرتبط بنموذج form.saisie4 التالي:


  private Integer saisie4 = 0;

الأسطر 3–5: تحتوي العلامة <h:inputText> على علامة فرعية <f:validateLongRange> تقبل سمتين اختياريتين، هما minimum و maximum. تسمح لك هذه العلامة، المعروفة أيضًا باسم أداة التحقق من الصحة، بإضافة قيد على قيمة الإدخال: يجب ألا تكون القيمة عددًا صحيحًا فحسب، بل عددًا صحيحًا ضمن النطاق [minimum، maximum] في حالة وجود كل من السمتين minimum و maximum؛ وأكبر من أو يساوي minimum في حالة وجود السمة minimum فقط؛ وأصغر من أو يساوي maximum في حالة وجود السمة maximum فقط. يحتوي أداة التحقق من الصحة <f:validateLongRange> على رسائل خطأ افتراضية في [javax.faces.Messages.properties]:

1
2
3
javax.faces.validator.LongRangeValidator.MINIMUM={1} : erreur de validation. La valeur est inférieure à la valeur minimale autorisée, "{0}".
javax.faces.validator.LongRangeValidator.NOT_IN_RANGE={2} : erreur de validation. L’attribut spécifié n’est pas compris entre les valeurs attendues {0} et {1}.
javax.faces.validator.LongRangeValidator.TYPE={0} : erreur de validation. La valeur n’est pas du type correct.

مرة أخرى، من الممكن استبدال هذه الرسائل برسائل أخرى. هناك سمة validatorMessage تسمح لك بتعريف رسالة محددة للمكون. وبالتالي، باستخدام كود JSF التالي:


        <h:inputText id="saisie4" value="#{form.saisie4}"  styleClass="saisie" required="true" requiredMessage="#{msg['data.required']}" converterMessage="#{msg['integer.required']}" validatorMessage="#{msg['saisie4.error']}">
          <f:validateLongRange minimum="1" maximum="10" />
</h:inputText>

والرسالة التالية في [messages.properties]:


saisie4.error=4-Vous devez entrer un nombre entier dans l'intervalle [1,10]

يتم الحصول على النتيجة التالية:

Image

2.8.5.2. المدخلات 5 و 6: إدخال عدد حقيقي

يتبع إدخال الأعداد الحقيقية قواعد مشابهة لتلك الخاصة بإدخال الأعداد الصحيحة. فيما يلي كود XHTML للمدخلتين 5 و 6:


<!-- ligne 6 -->
        <h:outputText value="#{msg['saisie5.prompt']}" />
        <h:inputText id="saisie5" value="#{form.saisie5}"  styleClass="saisie" required="true" requiredMessage="#{msg['data.required']}" converterMessage="#{msg['double.required']}"/>
        <h:message for="saisie5" styleClass="error"/>
        <h:outputText value="#{form.saisie5}"/>
        <!-- ligne 7 -->
        <h:outputText value="#{msg['saisie6.prompt']}"/>
        <h:inputText id="saisie6" value="#{form.saisie6}"  styleClass="saisie" required="true" requiredMessage="#{msg['data.required']}" converterMessage="#{msg['double.required']}" validatorMessage="#{msg['saisie6.error']}">
          <f:validateDoubleRange minimum="0.0"/>
        </h:inputText>
        <h:message for="saisie6" styleClass="error"/>
        <h:outputText value="#{form.saisie6}"/>

عناصر نموذج [Form.java] المتعلقة بمكونات saisie5 و saisie6:


  private Double saisie5 = 0.0;
  private Double saisie6 = 0.0;

رسائل الخطأ المرتبطة بالمحولات وأدوات التحقق من صحة مكونات saisie5 و saisie6، في [messages.properties]:


double.required=Vous devez entrer un nombre
saisie6.error=6-Vous devez entrer un nombre >=0

فيما يلي مثال على التنفيذ:

Image

2.8.5.3. المدخلات 7: إدخال قيمة منطقية

عادةً ما يتم إدخال القيمة المنطقية باستخدام مربع اختيار. إذا تم ذلك باستخدام حقل إدخال، يتم تحويل السلسلة "true" إلى القيمة المنطقية true، ويتم تحويل أي سلسلة أخرى إلى القيمة المنطقية false.

كود XHTML الخاص بالمثال:


<!-- ligne 8 -->
        <h:outputText value="#{msg['saisie7.prompt']}"/>
        <h:inputText id="saisie7" value="#{form.saisie7}"  styleClass="saisie" required="true" requiredMessage="#{msg['data.required']}" converterMessage="#{msg['double.required']}"/>
        <h:message for="saisie7" styleClass="error"/>
        <h:outputText value="#{form.saisie7}"/>

قالب مكون saisie7:


  private Boolean saisie7 = true;

فيما يلي مثال على الإدخال والاستجابة له:

في [1]، القيمة التي تم إدخالها. عند التحويل، تصبح هذه السلسلة "x" القيمة المنطقية false. ويظهر ذلك في [2]. لم تتغير قيمة [3] للنموذج. فهي لا تتغير إلا بعد نجاح جميع عمليات التحويل والتحقق من الصحة على الصفحة. ولم يكن هذا هو الحال في هذا المثال.

2.8.5.4. الإدخال 8: إدخال تاريخ

في هذا المثال، يتم إدخال تاريخ باستخدام كود XHTML التالي:


<!-- ligne 9 -->
        <h:outputText value="#{msg['saisie8.prompt']}"/>
        <h:inputText id="saisie8" value="#{form.saisie8}"  styleClass="saisie" required="true" requiredMessage="#{msg['date.required']}" converterMessage="#{msg['saisie8.error']}">
          <f:convertDateTime pattern="dd/MM/yyyy"/>
        </h:inputText>
        <h:message for="saisie8" styleClass="error"/>
        <h:outputText value="#{form.saisie8}">
          <f:convertDateTime pattern="dd/MM/yyyy"/>
        </h:outputText>

يستخدم مكون saisie8 في السطر 3 محولًا بين java.lang.String و java.util.Date. وفيما يلي قالب form.saisie8 المرتبط بمكون saisie8:


  private Date saisie8 = new Date();

يستخدم المكون المحدد في الأسطر 7–9 محولًا أيضًا، ولكن في الاتجاه java.util.Date --> java.lang.String فقط.

يدعم محول <f:convertDateTime> العديد من السمات، بما في ذلك سمة pattern، التي تحدد تنسيق السلسلة المراد تحويلها إلى تاريخ أو التنسيق الذي يجب عرض التاريخ به.

عند طلب صفحة [index.xhtml] لأول مرة، يتم عرض السطر 8 السابق على النحو التالي:

يعرض كل من الحقلين [1] و[2] قيمة نموذج form.saisie8:


  private Date saisie8 = new Date();

حيث يتم تعيين saisie8 على التاريخ الحالي. المحول المستخدم في كلتا الحالتين لعرض التاريخ هو كما يلي:


            <f:convertDateTime pattern="dd/MM/yyyy"/>

حيث يشير dd (اليوم) إلى رقم اليوم، و MM (الشهر) إلى رقم الشهر، و yyyy (السنة) إلى السنة. في [1]، يُستخدم المحول للتحويل العكسي java.lang.String --> java.util.Date. لذلك يجب أن يتبع التاريخ المدخل تنسيق "dd/MM/yyyy" ليكون صالحًا.

توجد رسائل افتراضية للتواريخ غير الصالحة في [javax.faces.Messages.properties]:

javax.faces.converter.DateTimeConverter.DATE={2} : «{0}» n’a pas pu être interprété en tant que date.
javax.faces.converter.DateTimeConverter.DATE_detail={2} : «{0}» n’a pas pu être interprété en tant que date. Exemple : {1} 

والتي يمكن استبدالها برسائلك الخاصة. على سبيل المثال:


<h:inputText id="saisie8" value="#{form.saisie8}"  styleClass="saisie" required="true" requiredMessage="#{msg['date.required']}" converterMessage="#{msg['saisie8.error']}">
  <f:convertDateTime pattern="dd/MM/yyyy"/>
</h:inputText>

الرسالة التي تظهر في حالة حدوث خطأ في التحويل ستكون الرسالة الرئيسية التالية: saisie8.error:


saisie8.error=8-Vous devez entrer une date valide au format jj/mm/aaaa

إليك مثال:

Image

2.8.5.5. المدخلة 9: إدخال سلسلة ذات طول محدود

يوضح المثال 9 كيفية فرض أن يكون عدد أحرف السلسلة المدخلة ضمن نطاق محدد:


<!-- ligne 10 -->
        <h:outputText value="#{msg['saisie9.prompt']}"/>
        <h:inputText id="saisie9" value="#{form.saisie9}"  styleClass="saisie" required="true" requiredMessage="#{msg['data.required']}" validatorMessage="#{msg['saisie9.error']}">
          <f:validateLength minimum="4" maximum="4"/>
        </h:inputText>
        <h:message for="saisie9" styleClass="error"/>
        <h:outputText value="#{form.saisie9}"/>

السطر 4: يتطلب أداة التحقق من الصحة <f:validateLength minimum="4" maximum="4"/> أن تحتوي السلسلة المدخلة على 4 أحرف بالضبط. يمكنك استخدام إحدى السمتين فقط: minimum لتحديد الحد الأدنى لعدد الأحرف، أو maximum لتحديد الحد الأقصى.

القالب form.saisie9 لمكون saisie9 في السطر 3 هو كما يلي:


  private String saisie9 = "";

هناك رسائل خطأ افتراضية لهذا النوع من التحقق من الصحة:

javax.faces.validator.LengthValidator.MAXIMUM={1} : erreur de validation. La longueur est supérieure à la valeur maximale autorisée, "{0}".
javax.faces.validator.LengthValidator.MINIMUM={1} : erreur de validation. La longueur est inférieure à la valeur minimale autorisée, "{0}".

والتي يمكن استبدالها باستخدام السمة validatorMessage، كما هو موضح في السطر 3 أعلاه. الرسالة الخاصة بالمفتاح "saisie9.error" هي كما يلي:


saisie9.error=9-Vous devez entrer une chaîne de 4 caractères exactement

فيما يلي مثال على التنفيذ:

Image

2.8.5.6. الإدخال 9B: إدخال سلسلة يجب أن تتوافق مع نمط معين

يوضح المدخل 9B كيفية فرض أن تحتوي السلسلة المدخلة على عدد من الأحرف ضمن نطاق معين:


<!-- ligne 10B -->
        <h:outputText value="#{msg['saisie9B.prompt']}"/>
        <h:inputText id="saisie9B" value="#{form.saisie9B}"  styleClass="saisie" required="true" requiredMessage="#{msg['data.required']}" validatorMessage="#{msg['saisie9B.error']}">
          <f:validateRegex pattern="^\s*\d{2}:\d{2}\s*$"/>
        </h:inputText>
        <h:message for="saisie9B" styleClass="error"/>
        <h:outputText value="#{form.saisie9B}"/>

السطر 4: يتطلب أداة التحقق من الصحة <f:validateRegex pattern="^\s*\d{2}:\d{2}\s*$"/> أن تتطابق السلسلة المدخلة مع نمط تعبير عادي، وفي هذه الحالة: تسلسل من 0 أو أكثر من المسافات، و2 رقم، وعلامة النقطتين (:)، و2 رقم، وتسلسل من 0 أو أكثر من المسافات.

نمط form.saisie9B لمكون saisie9B في السطر 3 هو كما يلي:


private String saisie9B;

توجد رسائل خطأ افتراضية لهذا النوع من عمليات التحقق من الصحة:

1
2
3
4
5
6
javax.faces.validator.RegexValidator.PATTERN_NOT_SET=Le modèle d’expression régulière doit être défini.
javax.faces.validator.RegexValidator.PATTERN_NOT_SET_detail=La valeur définie du modèle d’expression régulière ne peut pas être vide.
javax.faces.validator.RegexValidator.NOT_MATCHED=Discordance du modèle d’expression régulière.
javax.faces.validator.RegexValidator.NOT_MATCHED_detail=Discordance du modèle d’expression régulière «{0}».
javax.faces.validator.RegexValidator.MATCH_EXCEPTION=Erreur dans l’expression régulière.
javax.faces.validator.RegexValidator.MATCH_EXCEPTION_detail=Erreur dans l’expression régulière,  «{0}»

والتي يمكن استبدالها باستخدام السمة validatorMessage، كما هو موضح في السطر 3 أعلاه. الرسالة الخاصة بمفتاح saisie9.error هي كما يلي:


saisie9B.error=La cha\u00eene saisie ne respecte pas le format hh:mm

فيما يلي مثال على التنفيذ:

Image

2.8.5.7. المدخل 10: اكتب طريقة تحقق محددة

للتلخيص: تتيح لك JSF التحقق، من بين القيم المدخلة، من صحة الأرقام (الأعداد الصحيحة، الأعداد العائمة)، والتواريخ، وطول السلسلة، وما إذا كان الإدخال يتوافق مع تعبير عادي. تتيح لك JSF إضافة أدوات التحقق من الصحة والمحولات الخاصة بك إلى تلك الموجودة بالفعل. لم يتم تناول هذا الموضوع هنا، ولكن يمكنك الرجوع إلى [ref2] لمزيد من التفاصيل.

نقدم هنا طريقة أخرى: طريقة تتضمن التحقق من صحة البيانات المدخلة باستخدام طريقة من نموذج النموذج. وإليك مثال على ذلك:


<!-- ligne 11 -->
        <h:outputText value="#{msg['saisie10.prompt']}"/>
        <h:inputText id="saisie10" value="#{form.saisie10}"  styleClass="saisie" required="true" requiredMessage="#{msg['data.required']}" validator="#{form.validateSaisie10}"/>
        <h:message for="saisie10" styleClass="error"/>
        <h:outputText value="#{form.saisie10}"/>

القالب form.saisie10 المرتبط بمكون saisie10 في السطر 3 هو كما يلي:


  private Integer saisie10 = 0;

نريد أن يكون الرقم المدخل <1 أو >7. لا يمكننا التحقق من ذلك باستخدام أدوات التحقق الأساسية في JSF. لذلك، نكتب طريقة التحقق الخاصة بنا لمكون saisie10. ونحدد ذلك باستخدام سمة validator الخاصة بالمكون المراد التحقق منه:


          <h:inputText id="saisie10" value="#{form.saisie10}"  styleClass="saisie" required="true" requiredMessage="#{msg['data.required']}" validator="#{form.validateSaisie10}"/>

يتم التحقق من صحة المكون saisie10 بواسطة الطريقة form.validateSaisie10. الطريقة هي كما يلي:


  public void validateSaisie10(FacesContext context, UIComponent component, Object value) {
    int saisie = (Integer) value;
    if (!(saisie < 1 || saisie > 7)) {
      FacesMessage message = Messages.getMessage(null, "saisie10.incorrecte", null);
      message.setSeverity(FacesMessage.SEVERITY_ERROR);
      throw new ValidatorException(message);
    }
}

يجب أن تكون توقيع طريقة التحقق من الصحة هي تلك الموضحة في السطر 1:

  • FacesContext context: سياق تنفيذ الصفحة — يوفر الوصول إلى معلومات متنوعة، بما في ذلك كائنات الطلب HttpServletRequest والاستجابة HttpServletResponse،
  • UIComponent component: المكون المراد التحقق من صحته. يتم تمثيل العلامة <h:inputText> بواسطة مكون UIInput مشتق من UIComponent. هنا، يتم تمرير مكون UIInput هذا كمعلمة ثانية،
  • Object value: القيمة المدخلة المراد التحقق منها، محولة إلى نوع النموذج. من المهم أن نفهم هنا أنه في حالة فشل تحويل String إلى نوع النموذج، لا يتم تنفيذ طريقة التحقق من الصحة. عند استدعاء طريقة validateSaisie10، فهذا يعني أن تحويل String إلى Integer قد نجح. وبالتالي، فإن المعلمة الثالثة تكون من نوع Integer.
  • السطر 2: يتم تحويل القيمة المدخلة إلى النوع int،
  • السطر 3: نتحقق من أن القيمة المدخلة أقل من 1 أو أكبر من 7. إذا كان الأمر كذلك، تكتمل عملية التحقق من الصحة. إذا لم يكن الأمر كذلك، يجب على أداة التحقق من الصحة الإبلاغ عن الخطأ عن طريق إلقاء استثناء ValidatorException.

تحتوي فئة ValidatorException على منشئين:

  • يأخذ المنشئ [1] رسالة خطأ من نوع FacesMessage كمعلمة. هذا النوع من الرسائل هو الذي يتم عرضه بواسطة العلامتين <h:messages> و <h:message
  • كما تتيح لك دالة الإنشاء [2] تغليف كائن Throwable أو السبب المشتق للخطأ.

نحتاج إلى إنشاء رسالة من نوع FacesMessage. تحتوي هذه الفئة على عدة منشئات:

يحدد المنشئ [1] خصائص كائن FacesMessage:

  • FacesMessage.Severity severity: مستوى خطورة مأخوذ من التعداد التالي: SEVERITY_ERROR، SEVERITY_FATAL، SEVERITY_INFO، SEVERITY_WARN،
  • String summary: النسخة الموجزة لرسالة الخطأ — التي يتم عرضها بواسطة العلامات <h:message showSummary="true"> و <h:messages
  • String detail: النسخة التفصيلية لرسالة الخطأ — التي يتم عرضها بواسطة العلامات <h:message> و <h:messages showDetail="true">.

يمكن استخدام أي من المنشئات؛ ويمكن تعيين المعلمات المفقودة لاحقًا باستخدام طرق set.

لا يسمح لك المنشئ [1] بتحديد رسالة من ملف رسائل مدعوم باللغات. وهذا أمر مؤسف بالطبع. يتناول ديفيد جيري وكاي هورستمان [ref2] هذا القصور في كتابهما "Core JavaServer Faces" باستخدام فئة الأداة المساعدة com.corejsf.util.Messages. هذه هي الفئة المستخدمة في السطر 4 من كود Java لإنشاء رسالة الخطأ. وهي تحتوي فقط على طرق ثابتة، بما في ذلك طريقة getMessage المستخدمة في السطر 4:


   public static FacesMessage getMessage(String bundleName, String resourceId, Object[] params)

تقبل الطريقة getMessage ثلاثة معلمات:

  • String bundleName: اسم ملف الرسائل بدون اللاحقة .properties ولكن مع اسم الحزمة. هنا، يمكن أن تكون المعلمة الأولى هي `messages` للإشارة إلى ملف `[messages.properties]`. قبل استخدام الملف المحدد بواسطة المعلمة الأولى، تحاول `getMessage` استخدام ملف رسائل التطبيق، إن وجد. وبالتالي، إذا كنا قد أعلنا في `[faces-config.xml]` عن ملف رسائل باستخدام العلامة:

  <application>
...
    <message-bundle>messages</message-bundle>
</application>

يمكننا تمرير القيمة null كمعلمة أولى لطريقة getMessage. وهذا ما تم فعله هنا (انظر [web.xmlالصفحة 120

  • String resourceId: مفتاح الرسالة المراد استردادها من ملف الرسائل. لقد رأينا أن الرسالة يمكن أن تحتوي على نسخة موجزة ونسخة مفصلة. resourceId هو معرف النسخة الموجزة. سيتم استرداد النسخة المفصلة تلقائيًا باستخدام مفتاح resourceId_detail. وبالتالي، سيكون لدينا رسالتان في [messages.properties] للخطأ في الإدخال رقم 10:

saisie10.incorrecte=10-Saisie  10 incorrecte
saisie10.incorrecte_detail=10-Vous devez entrer un nombre entier <1 ou >7

تتضمن الرسالة من نوع FacesMessage التي تنتجها طريقة Messages.getMessage كلاً من النسخة الموجزة والنسخة التفصيلية إذا تم العثور عليهما. يجب أن تكون النسختان موجودتين؛ وإلا، يتم إصدار استثناء [NullPointerException

  • Object[] params: المعلمات الفعلية للرسالة إذا كانت تحتوي على معلمات رسمية {0}، {1}، ... سيتم استبدال هذه المعلمات الرسمية بعناصر مصفوفة params.

لنعد إلى كود طريقة التحقق من صحة مكون saisie10:


  public void validateSaisie10(FacesContext context, UIComponent component, Object value) {
    int saisie = (Integer) value;
    if (!(saisie < 1 || saisie > 7)) {
      FacesMessage message = Messages.getMessage(null, "saisie10.incorrecte", null);
      message.setSeverity(FacesMessage.SEVERITY_ERROR);
      throw new ValidatorException(message);
    }
}
  • في [4]، يتم إنشاء FacesMessage باستخدام الطريقة الثابتة Messages.getMessage،
  • في [5]، يتم تعيين مستوى خطورة الرسالة،
  • في [6]، يتم إلقاء استثناء ValidatorException مع الرسالة التي تم إنشاؤها مسبقًا. تم استدعاء طريقة التحقق من الصحة بواسطة كود XHTML التالي:

<!-- ligne 11 -->
        <h:outputText value="#{msg['saisie10.prompt']}"/>
        <h:inputText id="saisie10" value="#{form.saisie10}"  styleClass="saisie" required="true" requiredMessage="#{msg['data.required']}" validator="#{form.validateSaisie10}"/>
        <h:message for="saisie10" styleClass="error"/>
<h:outputText value="#{form.saisie10}"/>

السطر 3: يتم تنفيذ طريقة التحقق من الصحة للمكون الذي يحمل المعرف "saisie10". وبالتالي، فإن رسالة الخطأ التي تم إنشاؤها بواسطة طريقة validateSaisie10 مرتبطة بهذا المكون ويتم عرضها بواسطة السطر 4 (السمة for="saisie10"). يتم عرض النسخة التفصيلية بشكل افتراضي بواسطة العلامة <h:message>.

فيما يلي مثال على التنفيذ:

Image

2.8.5.8. المدخلتان 11 و12: التحقق من صحة مجموعة من المكونات

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

Image

نريد أن يكون البندان 11 و 12 عددين صحيحين مجموعهما يساوي 10.

سيكون كود JSF كما يلي:


<!-- line 12 -->
        <h:outputText value="#{msg['saisie11.prompt']}"/>
        <h:inputText id="saisie11" value="#{form.saisie11}"  styleClass="saisie" required="true" requiredMessage="#{msg['data.required']}" converterMessage="#{msg['integer.required']}"/>
        <h:panelGroup>
          <h:message for="saisie11" styleClass="error"/>
          <h:outputText value="#{form.errorSaisie11}" styleClass="error"/>
        </h:panelGroup>
        <h:outputText value="#{form.saisie11}"/>
        <!-- line 13 -->
        <h:outputText value="#{msg['saisie12.prompt']}"/>
        <h:inputText id="saisie12" value="#{form.saisie12}" styleClass="saisie" required="true" requiredMessage="#{msg['data.required']}" converterMessage="#{msg['integer.required']}"/>
        <h:panelGroup>
          <h:message for="saisie12" styleClass="error"/>
          <h:outputText value="#{form.errorSaisie12}" styleClass="error"/>
        </h:panelGroup>
        <h:outputText value="#{form.saisie12}"/>

والنموذج المرتبط به:


  private Integer saisie11 = 0;
  private Integer saisie12 = 0;
  private String errorSaisie11 = "";
private String errorSaisie12 = "";

في السطر 3 من كود JSF، نستخدم التقنيات التي تم عرضها سابقًا للتحقق من أن القيمة التي تم إدخالها لمكون saisie11 هي بالفعل عدد صحيح. وينطبق الأمر نفسه، في السطر 11، على مكون saisie12. للتحقق من أن saisie11 + saisie12 = 10، يمكننا إنشاء أداة تحقق محددة. وهذا هو الحل المفضل. مرة أخرى، سنرجع إلى [ref2] لمعرفة المزيد. هنا، نتبع نهجًا مختلفًا.

يتم التحقق من صحة صفحة [index.xhtml] بواسطة زر [Validate] الذي يكون كود JSF الخاص به كما يلي:


<!-- boutons de commande -->
      <h:panelGrid columns="2">
        <h:commandButton value="#{msg['submit']}" action="#{form.submit}"/>
        ...
      </h:panelGrid>

حيث تكون الرسالة msg['submit'] كما يلي:


submit=Valider

كما يظهر في السطر 3، سيتم تنفيذ طريقة form.submit لمعالجة النقر على زر [Validate]. وهي كما يلي:


  // actions
  public String submit() {
    // latest validations
    validateForm();
    // we return the same form
    return null;
  }
 
  // global validations
  private void validateForm() {
    if ((saisie11 + saisie12) != 10) {
...
}

من المهم أن نفهم أنه عند تنفيذ طريقة الإرسال:

  • يكون قد تم تنفيذ جميع أدوات التحقق من صحة النموذج والمحولات واجتيازها،
  • وقد تلقت الحقول في نموذج [Form.java] القيم التي أرسلها العميل.

في الواقع، دعونا نعيد النظر في دورة معالجة JSF POST:

طريقة submit هي معالج أحداث. وهي تتعامل مع حدث النقر على زر [Validate]. ومثل جميع معالجات الأحداث، يتم تشغيلها في المرحلة [E]، بمجرد تنفيذ جميع أدوات التحقق من الصحة والمحولات ونجاحها [C] وتحديث النموذج بالقيم المرسلة [D]. لذا لم تعد هناك حاجة هنا لإلقاء استثناءات من نوع [ValidatorException] كما فعلنا سابقًا. سنقوم ببساطة بإعادة إرسال النموذج مع رسائل الخطأ:

في [1]، سننبه المستخدم، وفي [2] و[3]، سنعرض مؤشر خطأ. في كود JSF، سيتم إنشاء الرسالة [1] على النحو التالي:


<h:form id="formulaire">
      <h:messages globalOnly="true" />
      <h:panelGrid columns="4" columnClasses="col1,col2,col3,col4" border="1">
        <!-- ligne 1 -->
        ...

في السطر 2، تعرض علامة <h:messages>، بشكل افتراضي، النسخة الملخصة من رسائل الخطأ لجميع إدخالات مكونات النموذج غير الصالحة بالإضافة إلى جميع رسائل الخطأ غير المرتبطة بالمكونات. تحدد السمة globalOnly="true" العرض على هذه الأخيرة.

يتم عرض الرسالتين [2] و[3] باستخدام علامات <h:outputText> البسيطة:


<!-- line 12 -->
        <h:outputText value="#{msg['saisie11.prompt']}"/>
        <h:inputText id="saisie11" value="#{form.saisie11}"  styleClass="saisie" required="true" requiredMessage="#{msg['data.required']}" converterMessage="#{msg['integer.required']}"/>
        <h:panelGroup>
          <h:message for="saisie11" styleClass="error"/>
          <h:outputText value="#{form.errorSaisie11}" styleClass="error"/>
        </h:panelGroup>
        <h:outputText value="#{form.saisie11}"/>
        <!-- line 13 -->
        ...
          <h:outputText value="#{form.errorSaisie12}" styleClass="error"/>
        ...

الأسطر 4–7: يحتوي مكون saisie11 على رسالتين محتملتين للخطأ:

  • إحداها تشير إلى تحويل غير صالح أو بيانات مفقودة. هذه الرسالة، التي تم إنشاؤها بواسطة JSF نفسها، ستكون مضمنة في نوع FacesMessage وسيتم عرضها بواسطة العلامة <h:message> في السطر 5،
  • والأخرى التي سنقوم بإنشائها إذا لم يكن مجموع input11 و input12 يساوي 10. وسيتم عرضها في السطر 6. وستكون رسالة الخطأ مضمنة في القالب form.errorSaisie11.

تتعلق هاتان الرسالتان بأخطاء لا يمكن أن تحدث في الوقت نفسه. يتم إجراء الفحص input11 + input12 = 10 في طريقة submit، التي لا تُنفَّذ إلا في حالة عدم وجود أخطاء متبقية في النموذج. وعند تنفيذها، يكون مكون input11 قد تم التحقق من صحته، ويكون نموذج form.input11 الخاص به قد تلقى قيمته. ولن يتم عرض الرسالة الموجودة في السطر 5 بعد ذلك. وعلى العكس، إذا تم عرض الرسالة الموجودة في السطر 5، فهذا يعني أن هناك خطأ واحد على الأقل متبقي في النموذج ولن يتم تنفيذ طريقة الإرسال. ولن يتم عرض الرسالة الموجودة في السطر 6. ولضمان وجود رسالتي الخطأ المحتملتين في نفس عمود الجدول، تم تجميعهما داخل علامة <h:panelGroup> (السطران 4 و7).

طريقة الإرسال هي كما يلي:


  // actions
  public String submit() {
    // latest validations
    validateForm();
    // we return the same form
    return null;
  }
 
  // global validations
  private void validateForm() {
    if ((saisie11 + saisie12) != 10) {
      // global msg
      FacesMessage message = Messages.getMessage(null, "saisies11et12.incorrectes", null);
      message.setSeverity(FacesMessage.SEVERITY_ERROR);
      FacesContext context = FacesContext.getCurrentInstance();
      context.addMessage(null, message);
      // field-related msg
      message = Messages.getMessage(null, "error.sign", null);
      setErrorSaisie11(message.getSummary());
      setErrorSaisie12(message.getSummary());
    } else {
      setErrorSaisie11("");
      setErrorSaisie12("");
    }
}
  • السطر 4: تستدعي طريقة submit طريقة validateForm لإجراء عمليات التحقق النهائية،
  • السطر 11: نتحقق مما إذا كان input11 + input12 = 10،
  • وإذا لم يكن الأمر كذلك، في السطرين 13-14، يتم إنشاء رسالة FacesMessage برقم تعريف `saisies11et12.incorrectes`. الرسالة هي كما يلي:

saisies11et12.incorrectes=La propriété saisie11+saisie12=10 n'est pas vérifiée
  • تُضاف الرسالة التي تم إنشاؤها بهذه الطريقة (السطران 15-16) إلى قائمة رسائل الخطأ الخاصة بالتطبيق. هذه الرسالة غير مرتبطة بمكون معين. إنها رسالة عامة للتطبيق. سيتم عرضها بواسطة العلامة <h:messages globalOnly="true"/> الموضحة أعلاه،
  • السطر 18: نقوم بإنشاء رسالة FacesMessage جديدة بمعرف "error.sign". وتبدو كما يلي:

error.sign="!"

ذكرنا أن الطريقة الثابتة [Messages.getMessage] تنشئ رسالة FacesMessage مع نسخة موجزة ونسخة مفصلة، إن وجدتا. هنا، لا توجد سوى النسخة الموجزة من رسالة error.sign. نحصل على النسخة الموجزة للرسالة m باستخدام m.getSummary(). في السطرين 19 و20، يتم وضع النسخة الموجزة لرسالة error.sign في حقول errorSaisie11 وerrorSaisie12 في النموذج. وسيتم عرضها بواسطة علامات JSF التالية:


          <h:outputText value="#{form.saisie11}"/>
          ...
          <h:outputText value="#{form.saisie12}"/>
  • السطران 22–23: إذا كان الشرط `saisie11 + saisie12 = 10` صحيحًا، فسيتم مسح الحقلين `errorSaisie11` و`errorSaisie12` في النموذج بحيث يتم حذف أي رسالة خطأ سابقة. من المهم أن نتذكر هنا أن النموذج يتم الاحتفاظ به بين الطلبات في جلسة عمل العميل.

فيما يلي مثال على التنفيذ:

لاحظ في العمود [1] أن النموذج قد تلقى القيم المرسلة، مما يشير إلى نجاح جميع عمليات التحقق من الصحة والتحويل بين القيم المرسلة والنموذج. وبالتالي، تمكنت معالجة الأحداث form.submit، التي تتولى معالجة النقر على زر [التحقق من الصحة]، من التنفيذ. وهذه المعالجة هي التي أنتجت الرسائل المعروضة في [2] و[3]. يمكننا أن نرى أن النموذج تم تحديثه على الرغم من رفض النموذج وإعادته إلى العميل. قد ترغب في عدم تحديث النموذج في مثل هذه الحالة. في الواقع، إذا ألغى المستخدم التحديث باستخدام زر [Cancel] [4]، فلن تتمكن من العودة إلى النموذج الأولي ما لم تكن قد قمت بحفظه.

2.8.5.9. إرسال نموذج POST دون التحقق من صحة المدخلات

لنأخذ النموذج أعلاه ونفترض أن المستخدم، دون أن يدرك أخطائه، يريد التخلي عن إرسال النموذج. سيستخدم عندئذٍ زر [Cancel] الذي تم إنشاؤه بواسطة كود JSF التالي:


<!-- boutons de commande -->
      <h:panelGrid columns="2">
        <h:commandButton value="#{msg['submit']}" action="#{form.submit}"/>
        <h:commandButton value="#{msg['cancel']}" immediate="true" action="#{form.cancel}"/>
      </h:panelGrid>

السطر 4، الرسالة msg['cancel'] هي كما يلي:


cancel=Annuler

لن يتم تنفيذ طريقة form.cancel المرتبطة بزر [إلغاء] إلا إذا كان النموذج صالحًا. وهذا ما أوضحناه بالنسبة لطريقة form.submit المرتبطة بزر [إرسال]. إذا أراد المستخدم إلغاء إرسال النموذج، فلا داعي بالطبع للتحقق من صحة إدخالاته. يتم تحقيق هذه النتيجة باستخدام السمة immediate="true"، التي توجه JSF لتنفيذ طريقة form.cancel دون المرور بمرحلة التحقق من الصحة والتحويل. لنعد إلى دورة معالجة JSF POST:

تتم معالجة الأحداث الخاصة بمكونات الإجراء <h:commandButton> و <h:commandLink> التي تحتوي على السمة immediate="true" في المرحلة [C]، وبعد ذلك تنتقل دورة JSF مباشرةً إلى المرحلة [E] لعرض الاستجابة.

طريقة form.cancel هي كما يلي:


  public String cancel() {
    saisie1 = 0;
    saisie2 = 0;
    saisie3 = 0;
    saisie4 = 0;
    saisie5 = 0.0;
    saisie6 = 0.0;
    saisie7 = true;
    saisie8 = new Date();
    saisie9 = "";
    saisie10 = 0;
    return null;
}

إذا نقرت على زر [إلغاء] في النموذج السابق، فستظهر الصفحة التالية:

  • يتم عرض النموذج مرة أخرى لأن معالج الأحداث form.cancel يضبط مفتاح التنقل على null. وبالتالي يتم العودة إلى الصفحة [index.xhtml
  • وقد تم تعديل نموذج [Form.java] بواسطة طريقة form.cancel. وينعكس ذلك في العمود [2]، الذي يعرض هذا النموذج،
  • بينما يعكس العمود [3] القيمة التي تم إرسالها للمكونات.

لنعد إلى كود JSF لمكون saisie1 [4]؛


          <!-- ligne 1 -->
          <h:outputText value="#{msg['saisie1.prompt']}"/>
          <h:inputText id="saisie1" value="#{form.saisie1}" styleClass="saisie"/>
          <h:message for="saisie1" styleClass="error"/>
<h:outputText value="#{form.saisie1}"/>

السطر 4: يتم ربط قيمة مكون saisie1 بنموذج form.saisie1. وينتج عن ذلك عدة أمور:

  • أثناء طلب GET إلى [index.xhtml]، سيعرض مكون saisie1 قيمة نموذج form.saisie1،
  • أثناء طلب POST إلى [index.xhtml]، يتم تعيين القيمة المرسلة لمكون saisie1 إلى نموذج form.saisie1 فقط إذا نجحت جميع عمليات التحقق من صحة النموذج والتحويلات. سواء تم تحديث النموذج ` ` بالقيم المرسلة أم لا، إذا تم إرسال النموذج بعد طلب POST، تعرض المكونات القيمة التي تم إرسالها وليس قيمة النموذج المرتبط بها. يظهر هذا في لقطة الشاشة أعلاه، حيث لا تحتوي الأعمدة [2] و[3] على نفس القيم.

2.9. مثال mv-jsf2-07: الأحداث المتعلقة بالتغييرات في حالة مكونات JSF

2.9.1. التطبيق

يوضح التطبيق مثالاً لطلب POST يتم تنفيذه دون استخدام زر أو رابط. النموذج كما يلي:

يرتبط محتوى combo2 [2] بالعنصر المحدد في combo1 [1]. عند تغيير التحديد في [1]، يتم إرسال طلب POST، يتم خلاله تحديث محتوى combo2 ليعكس العنصر المحدد في [1]، ثم يتم إرسال النموذج. لا يتم إجراء أي تحقق من الصحة خلال طلب POST هذا.

2.9.2. مشروع NetBeans

مشروع NetBeans الخاص بالتطبيق هو كما يلي:

يوجد نموذج واحد [index.xhtml] مع قالبه [Form.java].

2.9.3. بيئة التطبيق

ملف الرسائل [messages_fr.properties]:


app.titre=intro-07
app.titre2=JSF - Listeners
combo1.prompt=combo1
combo2.prompt=combo2
saisie1.prompt=Nombre entier de type int
submit=Valider
raz=Raz
data.required=Donnée requise
integer.required=Entrez un nombre entier
saisie.type=Type de la saisie
saisie.champ=Champ de saisie
saisie.erreur=Erreur de saisie
bean.valeur=Valeurs du modèle du formulaire

ورقة الأنماط [styles.css]:


.info{
   font-family: Arial,Helvetica,sans-serif;
   font-size: 14px;
   font-weight: bold
}
 
.col1{
   background-color: #ccccff
}
 
.col2{
   background-color: #ffcccc
}
 
.col3{
   background-color: #ffcc66
}
 
.col4{
   background-color: #ccffcc
}
 
.error{
   color: #ff0000
}
 
.saisie{
   background-color: #ffcccc;
   border-color: #000000;
   border-width: 5px;
   color: #cc0033;
   font-family: cursive;
   font-size: 16px
}
 
.combo{
  color: green;
}
 
.entete{
   font-family: 'Times New Roman',Times,serif;
   font-size: 14px;
   font-weight: bold
}

2.9.4. نموذج [index.xhtml]

يكون شكل [index.xhtml] كما يلي:


<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core">
  <h:head>
    <title>JSF</title>
    <h:outputStylesheet library="css" name="styles.css"/>
    ...
  </h:head>
  <h:body style="background-image: url('${request.contextPath}/resources/images/standard.jpg');">
    <h2><h:outputText value="#{msg['app.titre2']}"/></h2>
    <h:form id="formulaire">
      <h:messages globalOnly="true"/>
      <h:panelGrid columns="4" border="1" columnClasses="col1,col2,col3,col4">
        <!-- headers -->
        <h:outputText value="#{msg['saisie.type']}" styleClass="entete"/>
        <h:outputText value="#{msg['saisie.champ']}" styleClass="entete"/>
        <h:outputText value="#{msg['saisie.erreur']}" styleClass="entete"/>
        <h:outputText value="#{msg['bean.valeur']}" styleClass="entete"/>
        <!-- line 1 -->
        <h:outputText value="#{msg['combo1.prompt']}"/>
        <h:selectOneMenu id="combo1" value="#{form.combo1}" immediate="true" onchange="submit();" valueChangeListener="#{form.combo1ChangeListener}" styleClass="combo">
          <f:selectItems value="#{form.combo1Items}"/>
        </h:selectOneMenu>
        <h:panelGroup></h:panelGroup>
        <h:outputText value="#{form.combo1}"/>
        <!-- line 2 -->
        <h:outputText value="#{msg['combo2.prompt']}"/>
        <h:selectOneMenu id="combo2" value="#{form.combo2}" styleClass="combo">
          <f:selectItems value="#{form.combo2Items}"/>
        </h:selectOneMenu>
        <h:panelGroup></h:panelGroup>
        <h:outputText value="#{form.combo2}"/>
        <!-- line 3 -->
        <h:outputText value="#{msg['saisie1.prompt']}"/>
        <h:inputText id="saisie1" value="#{form.saisie1}" required="true" requiredMessage="#{msg['data.required']}" styleClass="saisie" converterMessage="#{msg['integer.required']}"/>
        <h:message for="saisie1" styleClass="error"/>
        <h:outputText value="#{form.saisie1}"/>
      </h:panelGrid>
      <!-- control buttons -->
      <h:panelGrid columns="2" border="0">
        <h:commandButton value="#{msg['submit']}"/>
        ...
      </h:panelGrid>
    </h:form>
  </h:body>
</html>

توجد الميزة الجديدة في كود combo1، الأسطر 24–26. تظهر سمات جديدة:

  • onchange: سمة HTML — تعلن عن دالة أو كود JavaScript ليتم تنفيذه عند تغيير العنصر المحدد في combo1. هنا، يقوم كود JavaScript submit() بإرسال النموذج إلى الخادم،
  • valueChangeListener: سمة JSF — تحدد اسم الأسلوب الذي سيتم تنفيذه على الخادم عند تغيير العنصر المحدد في combo1. في المجموع، يتم تنفيذ أسلوبين: أحدهما على جانب العميل، والآخر على جانب الخادم،
  • immediate=true: سمة JSF — تحدد توقيت تنفيذ معالج الأحداث من جانب الخادم: بعد إعادة بناء النموذج كما أدخله المستخدم، ولكن قبل فحوصات التحقق من صحة الإدخال. الهدف هنا هو ملء قائمة combo2 بناءً على العنصر المحدد في قائمة combo1، حتى إذا كانت هناك إدخالات غير صالحة في أماكن أخرى من النموذج. فيما يلي مثال:
  • في [1]، إدخال أولي،
  • في [2]، نغير العنصر المحدد في combo1 من A إلى B.

والنتيجة هي كما يلي:

تم إرسال طلب POST. تم تحديث محتوى combo2 [2] ليتطابق مع العنصر المحدد في combo1 [1]، على الرغم من أن الإدخال [3] كان غير صحيح. تسببت السمة immediate=true في تنفيذ طريقة form.combo1ChangeListener قبل عمليات التحقق من الصحة. بدون هذه السمة، لم تكن الطريقة لتنفذ لأن دورة المعالجة كانت ستتوقف عند عمليات التحقق من الصحة بسبب الخطأ في [3].

الرسائل المرتبطة بالنموذج هي كما يلي في [messages.properties]:


app.titre=intro-07
app.titre2=JSF - Listeners
combo1.prompt=combo1
combo2.prompt=combo2
saisie1.prompt=Nombre entier de type int
submit=Valider
raz=Raz
data.required=Donnée requise
integer.required=Entrez un nombre entier
saisie.type=Type de la saisie
saisie.champ=Champ de saisie
saisie.erreur=Erreur de saisie
bean.valeur=Valeurs du modèle du formulaire

تم تعيين نطاق [Form.java] على request:


package forms;
 
...
 
@ManagedBean
@RequestScoped
public class Form {

السطر 6: قمنا بتعيين نطاق الفئة إلى request.

2.9.5. فئة [Form.java]

فئة [Form.java] هي كما يلي:


package forms;
 
import java.util.logging.Logger;
import javax.enterprise.context.RequestScoped;
import javax.faces.bean.ManagedBean;
import javax.faces.context.FacesContext;
import javax.faces.event.ValueChangeEvent;
import javax.faces.model.SelectItem;
 
@ManagedBean
@RequestScoped
public class Form {
  
  public Form() {
  }
  
// form fields
  private String combo1="A";
  private String combo2="A1";
  private Integer saisie1=0;
  
  // working fields
  final private String[] combo1Labels={"A","B","C"};
  private String combo1Label="A";
  private static final Logger logger=Logger.getLogger("forms.Form");
  
  // methods
  public SelectItem[] getCombo1Items(){
    // init combo1
    SelectItem[] combo1Items=new SelectItem[combo1Labels.length];
    for(int i=0;i<combo1Labels.length;i++){
      combo1Items[i]=new SelectItem(combo1Labels[i],combo1Labels[i]);
    }
    return combo1Items;
  }
  
  public SelectItem[] getCombo2Items(){
    // init combo2 as a function of combo1
    SelectItem[] combo2Items=new SelectItem[5];
    for(int i=1;i<=combo2Items.length;i++){
      combo2Items[i-1]=new SelectItem(combo1Label+i,combo1Label+i);
    }
    return combo2Items;
  }
  
  // listeners
  public void combo1ChangeListener(ValueChangeEvent event){
    // follow-up
    logger.info("combo1ChangeListener");
    // retrieve the posted value of combo1
    combo1Label=(String)event.getNewValue();
    // we return the answer because we want to short-circuit the validations
    FacesContext.getCurrentInstance().renderResponse();
  }
  
  public String raz(){
    // follow-up
    logger.info("raz");
    // raz du formulaire
    combo1Label="A";
    combo1="A";
    combo2="A1";
    saisie1=0;
    return null;
  }
  
// getters - setters
  ...
}

دعونا نربط نموذج [index.xhtml] بنموذجه [Form.java]:

يتم إنشاء قائمة combo1 بواسطة كود JSF التالي:


        <h:selectOneMenu id="combo1" value="#{form.combo1}" immediate="true" onchange="submit();" valueChangeListener="#{form.combo1ChangeListener}" styleClass="combo">
          <f:selectItems value="#{form.combo1Items}"/>
</h:selectOneMenu>

يستمد العناصر من خلال طريقة getCombo1Items الخاصة بنموذجه (السطر 2). وقد تم تعريف هذه الطريقة في الأسطر 28-35 من كود جافا. وتقوم هذه الطريقة بإنشاء قائمة مكونة من ثلاثة عناصر: {"A"، "B"، "C"}.

يتم إنشاء قائمة combo2 بواسطة كود JSF التالي:


        <h:selectOneMenu id="combo2" value="#{form.combo2}" styleClass="combo">
          <f:selectItems value="#{form.combo2Items}"/>
</h:selectOneMenu>

يحصل على عناصره عبر طريقة getCombo2Items الخاصة بنموذجه (السطر 2). يتم تعريف هذه الطريقة في الأسطر 37–44 من كود Java. وهي تولد قائمة من خمسة عناصر {"X1", "X2", "X3", "X4", "X5"}, حيث X هو عنصر combo1Label من السطر 16. لذلك، عند إنشاء النموذج في البداية، تحتوي قائمة combo2 على العناصر {"A1"، "A2"، "A3"، "A4"، "A5"}.

عندما يغير المستخدم العنصر المحدد في قائمة combo1،

  • سيتم معالجة الحدث onchange="submit();" بواسطة متصفح العميل. سيتم بعد ذلك إرسال النموذج إلى الخادم،
  • وعلى جانب الخادم، سيكتشف JSF أن مكون combo1 قد غيّر قيمته. وسيتم تنفيذ طريقة combo1ChangeListener في الأسطر 47–54. تتلقى طريقة ValueChangeListener كائنًا من النوع javax.faces.event.ValueChangeEvent كمعلمة. يتيح لك هذا الكائن استرداد القيم القديمة والجديدة للمكون الذي تم تغييره باستخدام الطرق التالية:

Image

هنا، المكون هو قائمة combo1 من النوع UISelectOne. قيمته من النوع String.

  • السطر 51 من نموذج Java: يتم تخزين القيمة الجديدة لـ combo1 في combo1Label، والتي تُستخدم لإنشاء العناصر في قائمة combo2،
  • السطر 53: يتم إرسال الاستجابة. من المهم ملاحظة هنا أن معالج combo1ChangeListener يتم تنفيذه مع السمة immediate="true". وبالتالي، يتم تنفيذه بعد تحديث شجرة مكونات الصفحة بالقيم المرسلة وقبل عملية التحقق من صحة القيم المرسلة. ومع ذلك، نريد تجاوز عملية التحقق من الصحة هذه لأن قائمة combo2 يجب تحديثها حتى إذا كانت هناك إدخالات غير صالحة في أماكن أخرى من النموذج. لذلك نطلب إرسال الاستجابة على الفور دون المرور بمرحلة التحقق من صحة المدخلات.
  • سيتم إرسال النموذج بالضبط كما تم إدخاله. ومع ذلك، فإن العناصر الموجودة في قوائم combo1 و combo2 ليست قيمًا منشورة. سيتم إعادة إنشاؤها عن طريق استدعاء طريقتي getCombo1Items و getCombo2Items. ستستخدم الطريقة الأخيرة بعد ذلك القيمة الجديدة لـ combo1Label التي تم تعيينها بواسطة combo1ChangeListener، وستتغير العناصر الموجودة في قائمة combo2.

2.9.6. زر [ Reset]

نريد أن يعيد زر [Reset] النموذج إلى حالته الأولية، كما هو موضح أدناه:

في [1]، يتم إرسال النموذج الموجود قبل زر [Raz]؛ وفي [2]، تظهر نتيجة عملية الإرسال.

على الرغم من بساطة هذه الحالة من الناحية الوظيفية، إلا أن التعامل معها يتبين أنه معقد للغاية. يمكننا تجربة حلول متنوعة، بما في ذلك الحل المستخدم لزر [Cancel] في المثال السابق:


       <h:commandButton value="#{msg['raz']}" immediate="true" action="#{form.raz}"/>

حيث تكون طريقة form.raz كما يلي:


  public String raz(){
    // raz du formulaire
    combo1Label="A";
    combo1="A";
    combo2="A1";
    saisie1=0;
    return null;
}

النتيجة التي يتم الحصول عليها عند النقر على زر [Clear] في المثال السابق هي كما يلي:

يُظهر العمود [1] أن طريقة form.raz قد تم تنفيذها. ومع ذلك، يستمر العمود [1] في عرض القيم التي تم إدخالها:

  • بالنسبة إلى combo1، كانت القيمة المنشورة هي "B". وبالتالي، تم تحديد هذا العنصر في القائمة،
  • بالنسبة إلى combo2، كانت القيمة المنشورة "B5". ونتيجة لتنفيذ form.raz، تم تغيير العناصر {"B1"، ...، "B5"} في combo2 إلى {"A1"، ...، "A5"}. لم يعد العنصر "B5" موجودًا وبالتالي لا يمكن تحديده. ثم يتم عرض العنصر الأول في القائمة،
  • بالنسبة لـ input1، كانت القيمة المنشورة هي 10.

هذا هو السلوك الطبيعي مع السمة immediate="true". للحصول على نتيجة مختلفة، يجب عليك إرسال القيم التي تريد رؤيتها في النموذج الجديد حتى لو كان المستخدم قد أدخل قيمًا مختلفة. يتم تحقيق ذلك باستخدام القليل من JavaScript من جانب العميل. يصبح النموذج كما يلي:


<script language="javascript">
  function raz(){
    document.forms['formulaire'].elements['formulaire:combo1'].value="A";
    document.forms['formulaire'].elements['formulaire:combo2'].value="A1";
    document.forms['formulaire'].elements['formulaire:saisie1'].value=0;
    //document.forms['form'].submit();
  }
</script>
...
<h:commandButton value="#{msg['raz']}" onclick='raz()' immediate="true" action="#{form.raz}"/>
  • السطر 10: توجه السمة onclick='raz()' المتصفح لتنفيذ دالة JavaScript raz عندما ينقر المستخدم على زر [Reset].
  • السطر 3: يتم تعيين القيمة "A" لعنصر HTML المسمى 'form:combo1'. العناصر المختلفة في السطر 3 هي كما يلي:
    • document: الصفحة المعروضة بواسطة المتصفح،
    • document.forms: جميع النماذج في المستند،
    • document.forms['form']: النموذج الذي يحمل السمة name="form
    • document.forms['form'].elements: جميع عناصر النموذج الذي يحتوي على السمة name="form
    • document.forms['form'].elements['form:combo1']: عنصر النموذج الذي تم تعيين السمة name له على "form:combo1"
    • document.forms['form'].elements['form:combo1'].value: القيمة التي سيتم إرسالها بواسطة عنصر النموذج الذي يحتوي على السمة name="form:combo1".

للعثور على سمات name للعناصر المختلفة في الصفحة المعروضة بواسطة المتصفح، يمكنك عرض كود المصدر الخاص بها (أدناه باستخدام IE7):

<form id="formulaire" name="formulaire" ...>
...
<select id="formulaire:combo1" name="formulaire:combo1" ...>

بعد توضيح ذلك، يمكننا أن نرى أنه في كود JavaScript الخاص بوظيفة raz:

  • يضمن السطر 3 أن القيمة التي يتم إرسالها لمكون combo1 ستكون السلسلة A،
  • السطر 4 يضمن أن القيمة التي يتم إرسالها لمكون combo2 ستكون السلسلة A1،
  • السطر 5 يضمن أن القيمة التي يتم إرسالها لمكون saisie1 ستكون السلسلة 0.

بمجرد الانتهاء من ذلك، سيتم إرسال النموذج POST، المرتبط بأي زر من نوع <h:commandButton> (السطر 10). سيتم تنفيذ طريقة form.raz، وسيتم إرجاع النموذج كما تم إرساله. ثم نحصل على النتيجة التالية:

تخفي هذه النتيجة الكثير من الأمور. يتم إرسال القيم "A" و"A1" و"0" من مكونات combo1 و combo2 و saisie1 إلى الخادم. لنفترض أن القيمة السابقة لـ combo1 كانت "B". ثم يحدث تغيير في قيمة مكون combo1، ويجب أيضًا تنفيذ طريقة form.combo1ChangeListener. لدينا معالجان للأحداث مع السمة immediate="true". هل سيتم تنفيذ كلاهما؟ إذا كان الأمر كذلك، بأي ترتيب؟ أم واحد فقط؟ إذا كان الأمر كذلك، أيهما؟

لمعرفة المزيد، نقوم بإنشاء سجلات في التطبيق:


package forms;
 
import java.util.logging.Logger;
...
public class Form {
  
...  
// form fields
  private String combo1="A";
  private String combo2="A1";
  private Integer saisie1=0;
  
  // working fields
  final private String[] combo1Labels={"A","B","C"};
  private String combo1Label="A";
  private static final Logger logger=Logger.getLogger("forms.Form");
  
  // listener
  public void combo1ChangeListener(ValueChangeEvent event){
    // follow-up
    logger.info("combo1ChangeListener");
    // retrieve the posted value of combo1
    combo1Label=(String)event.getNewValue();
    // we return the answer because we want to short-circuit the validations
    FacesContext.getCurrentInstance().renderResponse();
  }
  
  public String raz(){
    // follow-up
    logger.info("raz");
    // raz du formulaire
    combo1Label="A";
    combo1="A";
    combo2="A1";
    saisie1=0;
    return null;
  }
...
}
  • السطر 16: يتم إنشاء مسجل. تسمح لنا المعلمة getLogger بالتمييز بين مصادر السجلات. هنا، يُسمى المسجل forms.Form،
  • السطر 21: يتم تسجيل استدعاء طريقة combo1ChangeListener،
  • السطر 30: يتم تسجيل الدخول إلى طريقة raz.

ما هي السجلات التي يتم إنشاؤها عند الضغط على زر [مسح] أو عند تغيير قيمة combo1؟ دعونا ننظر في سيناريوهات مختلفة:

  • نستخدم زر [Clear] بينما العنصر المحدد في combo1 هو "A". وبالتالي، فإن "A" هي القيمة الأخيرة لمكون combo1. وقد رأينا أن زر [Clear] ينفذ دالة JavaScript التي ترسل القيمة "A" لمكون combo1. وبالتالي، لا يغير مكون combo1 قيمته. ثم تظهر السجلات أن طريقة form.raz هي الوحيدة التي يتم تنفيذها:
  
  • نستخدم زر [Clear] عندما لا يكون العنصر المحدد في combo1 هو "A". وبالتالي، يغير مكون combo1 قيمته: كانت قيمته الأخيرة ليست "A"، وسيقوم زر [Clear] بتعيينها إلى "A". ثم تظهر السجلات أن طريقتين يتم تنفيذهما، بالترتيب التالي: combo1ChangeListener، raz:
  
  • نقوم بتغيير قيمة combo1 دون استخدام زر [Raz]. تظهر السجلات أن طريقة combo1ChangeListener هي الوحيدة التي يتم تنفيذها:
  

2.10. مثال mv-jsf2-08: العلامة <h:dataTable>

2.10.1. التطبيق

يعرض التطبيق قائمة بالأشخاص مع خيار حذفهم:

  • في [1]، قائمة بالأشخاص،
  • في [2]، الروابط التي تسمح لك بحذفهم.

2.10.2. مشروع NetBeans

مشروع NetBeans للتطبيق هو كما يلي:

يوجد نموذج واحد [index.xhtml] مع القالب الخاص به [Form.java].

2.10.3. بيئة التطبيق

ملف التكوين [faces-config.xml]:


<?xml version='1.0' encoding='UTF-8'?>
 
<!-- =========== FULL CONFIGURATION FILE ================================== -->
 
<faces-config version="2.0"
              xmlns="http://java.sun.com/xml/ns/javaee" 
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
              xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd">
 
  <application>
    <resource-bundle>
      <base-name>
        messages
      </base-name>
      <var>msg</var>
    </resource-bundle>
    <message-bundle>messages</message-bundle>
  </application>
</faces-config>

ملف الرسائل [messages_fr.properties]:


app.titre=intro-08
app.titre2=JSF - DataTable
submit=Valider
personnes.headers.id=Id
personnes.headers.nom=Nom
personnes.headers.prenom=Pr\u00e9nom

ورقة الأنماط [styles.css]:


.headers {
   text-align: center;
   font-style: italic;
   color: Snow;
   background: Teal;
}
 
.id {
   height: 25px;
   text-align: center;
   background: MediumTurquoise;
}
 
.nom {
   text-align: left;
   background: PowderBlue;
}
.prenom {
   width: 6em;
   text-align: left;
   color: Black;
   background: MediumTurquoise;
}

2.10.4. نموذج [index.xhtml] وقالب [Form.java] الخاص به

دعونا نراجع طريقة العرض المرتبطة بصفحة [index.xhtml]:

  

فيما يلي شكل ملف [index.xhtml]:


<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core">
  <h:head>
    <title>JSF</title>
    <h:outputStylesheet library="css" name="styles.css"/>
  </h:head>
  <h:body style="background-image: url('${request.contextPath}/resources/images/standard.jpg');">
    <h2><h:outputText value="#{msg['app.titre2']}"/></h2>
    <h:form id="formulaire">
      <h:dataTable value="#{form.personnes}" var="personne" headerClass="headers" columnClasses="id,nom,prenom">
  ........................
      </h:dataTable>
    </h:form>
  </h:body>
</html>

السطر 14: تستخدم علامة <h:dataTable> الحقل #{form.personnes} كمصدر للبيانات. وهي كما يلي:

private List<Person> people;

فئة [Person] هي كما يلي:


package forms;
 
public class Personne {
  // data
  private int id;
  private String nom;
  private String prénom;
  
  // manufacturers
  public Personne(){
    
  }
  
  public Personne(int id, String nom, String prénom){
    this.id=id;
    this.nom=nom;
    this.prénom=prénom;
  }
  
  // toString
  public String toString(){
    return String.format("Personne[%d,%s,%s]", id,nom,prénom);
  }
  
  // getter and setters
...
}

لنعد إلى محتوى علامة <h:dataTable>:


<h:dataTable value="#{form.personnes}" var="personne" headerClass="headers" columnClasses="id,nom,prenom">
...
</h:dataTable>
  • تحدد السمة var="person" اسم المتغير الذي يمثل الشخص الحالي داخل علامة <h:dataTable
  • وتحدد السمة headerClass="headers" نمط رؤوس أعمدة الجدول،
  • وتحدد السمة columnClasses="...." نمط كل عمود في الجدول.

دعونا نفحص أحد أعمدة الجدول ونرى كيف تم إنشاؤه:

  

فيما يلي كود XHTML الخاص بعمود "المعرف":


<h:dataTable value="#{form.personnes}" var="personne" headerClass="headers" columnClasses="id,nom,prenom">
        <h:column>
          <f:facet name="header">
            <h:outputText value="#{msg['personnes.headers.id']}"/>
          </f:facet>
          <h:outputText value="#{personne.id}"/>
        </h:column>
        ...
      </h:dataTable>
 
lignes 3-5 : la balise <f:facet name="header"> définit le titre de la colonne,
ligne 4 : le titre de la colonne est pris dans le fichier des messages,
ligne 6 : personne fait référence à l'attribut var de la balise <h:dataTable ...> (ligne 1). On écrit donc l'id de la personne courante.
 
 
<h:dataTable value="#{form.personnes}" var="personne" headerClass="headers" columnClasses="id,nom,prenom">
        <h:column>
          <f:facet name="header">
            <h:outputText value="#{msg['personnes.headers.id']}"/>
          </f:facet>
          <h:outputText value="#{personne.id}"/>
        </h:column>
        <h:column>
          <f:facet name="header">
            <h:outputText value="#{msg['personnes.headers.nom']}"/>
          </f:facet>
          <h:outputText value="#{personne.nom}"/>
        </h:column>
        <h:column>
          <f:facet name="header">
            <h:outputText value="#{msg['personnes.headers.prenom']}"/>
          </f:facet>
          <h:outputText value="#{personne.prénom}"/>
        </h:column>
...
      </h:dataTable>
  • الأسطر 3–7: عمود معرف الجدول،
  • الأسطر 8–13: عمود «اللقب» في الجدول،
  • الأسطر 14–19: عمود الاسم الأول في الجدول.

الآن، دعونا نفحص عمود رابط [إزالة]:

يتم إنشاء هذا العمود بواسطة الكود التالي:


<h:dataTable value="#{form.personnes}" var="personne" headerClass="headers" columnClasses="id,nom,prenom">
...
        <h:column>
          <h:commandLink value="Retirer" action="#{form.retirerPersonne}">
            <f:setPropertyActionListener target="#{form.personneId}" value="#{personne.id}"/>
          </h:commandLink>
        </h:column>
      </h:dataTable>

يتم إنشاء رابط [إزالة] بواسطة الأسطر 4–6. عند النقر على الرابط، سيتم تنفيذ طريقة [Form].removePerson. حان الوقت لفحص فئة [Form.java]:


package forms;
 
import java.util.ArrayList;
import java.util.List;
import javax.enterprise.context.RequestScoped;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;
 
@ManagedBean
@SessionScoped
public class Form {
 
  // model
  private List<Personne> personnes;
  private int personneId;
 
  // manufacturer
  public Form() {
    // initialization of the list of persons
    personnes = new ArrayList<Personne>();
    personnes.add(new Personne(1, "dupont", "jacques"));
    personnes.add(new Personne(2, "durand", "élise"));
    personnes.add(new Personne(3, "martin", "jacqueline"));
  }
 
  public String retirerPersonne() {
    // search for the selected person
    int i = 0;
    for (Personne personne : personnes) {
      // current person = selected person?
      if (personne.getId() == personneId) {
        // delete the current person from the list
        personnes.remove(i);
        // we're done
        break;
      } else {
        // next person
        i++;
      }
    }
    // we test on the same page
    return null;
  }
  
  // getters and setters
...
}
  • الأسطر 18–24: يقوم المنشئ بتهيئة قائمة الأشخاص من السطر 14،
  • السطر 10: نظرًا لأن هذه القائمة يجب أن تستمر عبر الطلبات، فإن نطاق الفول هو الجلسة.

عند تنفيذ الطريقة [removePerson] في السطر 26، يكون الحقل في السطر 15 قد تم تهيئته بمعرف الشخص الذي تم النقر على رابط [Remove] الخاص به:


          <h:commandLink value="Retirer" action="#{form.retirerPersonne}">
            <f:setPropertyActionListener target="#{form.personneId}" value="#{personne.id}"/>
</h:commandLink>

تسمح علامة <f:setPropertyActionListener> بنقل المعلومات إلى النموذج. هنا، يتم نسخ قيمة السمة value إلى حقل النموذج المحدد بواسطة السمة target. وبالتالي، يتم نسخ معرف الشخص الحالي — الذي سيتم إزالته من قائمة الأشخاص — إلى حقل [Form].personneId عبر أداة الحصول على القيمة الخاصة بهذا الحقل. ويتم ذلك قبل تنفيذ الأسلوب المشار إليه بواسطة السمة action في السطر 1.

الأسطر 26–43: تقوم الطريقة [supprimerPersonne] بإزالة الشخص الذي يساوي معرفه *personId*.

2.11. مثال mv-jsf2-09: تخطيط تطبيق JSF

2.11.1. التطبيق

يوضح التطبيق كيفية تصميم تطبيق JSF باستخدام عرضين:

يحتوي التطبيق على عرضين:

  • في [1]، الصفحة 1،
  • في [2]، الصفحة 2.

يمكنك التنقل بين الصفحتين. ما نريد إظهاره هنا هو أن الصفحتين 1 و 2 تشتركان في تخطيط مشترك، كما هو موضح في لقطات الشاشة أعلاه.

2.11.2. مشروع NetBeans

مشروع NetBeans الخاص بالتطبيق هو كما يلي:

يتكون التطبيق حصريًا من صفحات XHTML. ولا يوجد قالب Java مرتبط به.

2.11.3. صفحة [layout.xhtml]

تحدد صفحة [layout.xhtml] تخطيط صفحات التطبيق:


<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:ui="http://java.sun.com/jsf/facelets">
  <h:head>
    <title>JSF</title>
    <h:outputStylesheet library="css" name="styles.css"/>
  </h:head>
  <h:body style="background-image: url('${request.contextPath}/resources/images/standard.jpg');">
    <h:form id="formulaire">
      <table style="width: 400px">
        <tr>
          <td colspan="2" bgcolor="#ccccff">
            <ui:include src="entete.xhtml"/>
          </td>
        </tr>
        <tr style="height: 200px">
          <td bgcolor="#ffcccc">
            <ui:include src="menu.xhtml"/>
          </td>
          <td>
            <ui:insert name="contenu" >
              <h2>Contenu</h2>
            </ui:insert>
          </td>
        </tr>
        <tr bgcolor="#ffcc66">
          <td colspan="2">
            <ui:include src="basdepage.xhtml"/>
          </td>
        </tr>         
      </table>
    </h:form>
  </h:body>
</html>

في السطر 7، يظهر مساحة اسم جديدة، **ui**. تحتوي مساحة الاسم هذه على العلامات المستخدمة لتنسيق صفحات التطبيق. تُستخدم العلامات الموجودة في مساحة الاسم هذه في الأسطر 17 و22 و25 و32.

تعرض صفحة [layout.xhtml] المعلومات في جدول HTML (السطر 14). يمكنك طلب هذه الصفحة باستخدام متصفح:

  • في [1]، عنوان URL المطلوب.

تم إنشاء المنطقة [2] بواسطة كود XHTML التالي:


  <h:body style="background-image: url('${request.contextPath}/resources/images/standard.jpg');">
    <h:form id="formulaire">
      <table style="width: 400px">
        <tr>
          <td colspan="2" bgcolor="#ccccff">
            <ui:include src="entete.xhtml"/>
          </td>
        </tr>
...       
      </table>
    </h:form>
</h:body>

تسمح علامة <ui:include> في السطر 6 بتضمين كود XHTML خارجي في الصفحة. ملف [entete.xhtml] هو كما يلي:


<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html">
  <body>
    <h2>entête</h2>
  </body>
</html>

سيتم إدراج الكود بالكامل من الأسطر 3–8 في [layout.xhtml]. وبالتالي، سيتم إدراج العلامات <html> و <body> داخل علامة <td>. وهذا لا يسبب أي أخطاء. لذلك، فإن الصفحات المضمنة عبر <ui:include> هي صفحات XHTML كاملة. بصريًا، لن يكون هناك تأثير سوى على السطر 6. وتوجد العلامات <html> و <body> لأسباب تتعلق بقواعد اللغة.

تم إنشاء المنطقة [3] بواسطة كود XHTML التالي:


<h:form id="formulaire">
      <table style="width: 400px">
        <tr style="height: 200px">
          <td bgcolor="#ffcccc">
            <ui:include src="menu.xhtml"/>
          </td>
...
        </tr>
...
      </table>
    </h:form>

تتضمن علامة <ui:include> في السطر 5 الملف [menu.xhtml] التالي:


<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html">
  <body>
    <h2>menu</h2>
  </body>
</html>

تم إنشاء المنطقة [4] بواسطة كود XHTML التالي:


<h:form id="formulaire">
      <table style="width: 400px">
...
        <tr bgcolor="#ffcc66">
          <td colspan="2">
            <ui:include src="basdepage.xhtml"/>
          </td>
        </tr>         
      </table>
    </h:form>

تتضمن علامة <ui:include> في السطر 6 الملف [basdepage.xhtml] التالي:


<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html">
  <body>
    <h2>bas de page</h2>
  </body>
</html>

تم إنشاء المنطقة [5] بواسطة كود XHTML التالي:


    <h:form id="formulaire">
...
          <td>
            <ui:insert name="contenu" >
              <h2>Contenu</h2>
            </ui:insert>
          </td>
 ...
      </table>
</h:form>

تحدد علامة <ui:insert> في السطر 5 منطقة تسمى "content". هذه منطقة يمكن أن تحتوي على محتوى متغير. سنرى كيف. عندما طلبنا الصفحة [layout.xhtml]، لم يتم تحديد أي محتوى للمنطقة المسماة "content". في هذه الحالة، يتم استخدام محتوى علامة <ui:insert> في الأسطر 4–6. وبالتالي يتم عرض السطر 5.

2.11.4. الصفحة [page1.xhtml]

صفحة [layout.xhtml] غير مخصصة للعرض. فهي تعمل كقالب لصفحتي [page1.xhtml] و [page2.xhtml]. ويُشار إلى هذا باسم قالب الصفحة. صفحة [page1.xhtml] هي كما يلي:


<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:ui="http://java.sun.com/jsf/facelets">
  <ui:composition template="layout.xhtml">
    <ui:define name="contenu">
      <h2>page 1</h2>
      <h:commandLink value="page 2" action="page2"/>
    </ui:define>
  </ui:composition>
</html>
  • يستخدم السطر 6 مساحة اسم ui؛
  • في السطر 7، نحدد أن الصفحة مرتبطة بقالب [layout.xhtml] باستخدام علامة <ui:composition
  • السطر 8، يضمن هذا الارتباط أن كل علامة <ui:define> ستكون مرتبطة بعلامة <ui:insert> في القالب المستخدم، في هذه الحالة [layout.xhtml]. يتم إنشاء الارتباط عبر سمة name لكلتا العلامتين. يجب أن تكونا متطابقتين.

الصفحة المعروضة هي [layout.xhtml]، حيث يتم استبدال محتوى كل علامة <ui:insert> بمحتوى علامة <ui:define> من الصفحة المطلوبة. هنا، يبدو الأمر كما لو أن الصفحة المعروضة هي:


<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:ui="http://java.sun.com/jsf/facelets">
  <h:head>
    <title>JSF</title>
    <h:outputStylesheet library="css" name="styles.css"/>
  </h:head>
  <h:body style="background-image: url('${request.contextPath}/resources/images/standard.jpg');">
    <h:form id="formulaire">
      <table style="width: 400px">
        <tr>
          <td colspan="2" bgcolor="#ccccff">
            <ui:include src="entete.xhtml"/>
          </td>
        </tr>
        <tr style="height: 200px">
          <td bgcolor="#ffcccc">
            <ui:include src="menu.xhtml"/>
          </td>
          <td>
              <h2>page 1</h2>
              <h:commandLink value="page 2" action="page2"/>
          </td>
        </tr>
        <tr bgcolor="#ffcc66">
          <td colspan="2">
            <ui:include src="basdepage.xhtml"/>
          </td>
        </tr>         
      </table>
    </h:form>
  </h:body>
</html>

تم إدراج السطرين 25 و26 من [page1.xhtml] بدلاً من علامة <ui:insert> في [layout.xml].

تشبه الصفحة [page2.xhtml] الصفحة [page1.xhtml]:


<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:ui="http://java.sun.com/jsf/facelets">
  <ui:composition template="layout.xhtml">
    <ui:define name="contenu">
      <h2>page 2</h2>
      <h:commandLink value="page 1" action="page1"/>
    </ui:define>
  </ui:composition>
</html>

2.12. الخلاصة

الدراسة التي نقدمها هنا عن JSF 2 ليست شاملة على الإطلاق. ومع ذلك، فهي كافية لفهم الأمثلة التالية. لمزيد من القراءة، انظر [ref2].

2.13. الاختبار باستخدام Eclipse

دعونا نوضح كيفية اختبار مشاريع Maven باستخدام SpringSource Tool Suite:

  • في [1]، قم باستيراد مشروع Maven [2] بالنقر على الزر [3]. هنا، نستخدم مشروع Maven [mv-jsf2-09] لـ Eclipse
  • في [4]، تم التعرف على المشروع المستورد بشكل صحيح كمشروع Maven [5]،
  • في [6]، تم استيراد المشروع إلى مستكشف المشاريع،
  • في [7]، نقوم بتشغيله على خادم Tomcat [8] [9]،
  • في [10]، تم تشغيل Tomcat
  • في [11]، يتم عرض الصفحة الرئيسية لمشروع [mv-jsf2-09] [11] في متصفح داخل Eclipse.