2. Java Server Faces
نقدم الآن إطار عمل Java Server Faces. سيتم استخدام الإصدار 2، لكن الأمثلة تعرض بشكل أساسي ميزات الإصدار 1. سنعرض من الإصدار 2 الميزات الضرورية فقط للتطبيق النموذجي الذي سيلي.
2.1. مكانة JSF في تطبيق ويب
أولاً، لنحدد مكان JSF في تطوير تطبيق ويب. في أغلب الأحيان، سيتم بناء هذا التطبيق على بنية متعددة الطبقات مثل التالية:
![]() |
- الطبقة [web] هي الطبقة التي تتعامل مع مستخدم تطبيق الويب. يتفاعل المستخدم مع تطبيق الويب من خلال صفحات الويب التي يتم عرضها بواسطة متصفح. تقع JSF في هذه الطبقة وفقط في هذه الطبقة،
- تنفذ الطبقة [métier] قواعد إدارة التطبيق، مثل حساب الراتب أو الفاتورة. تستخدم هذه الطبقة البيانات الواردة من المستخدم عبر الطبقة [web] ومن نظام إدارة قواعد البيانات (SGBD) عبر الطبقة [DAO]،
- تدير الطبقة [DAO] (كائنات الوصول إلى البيانات)، والطبقة [jpa] (واجهة برمجة تطبيقات الثبات في Java) ومحرك الأقراص JDBC الوصول إلى بيانات نظام إدارة قواعد البيانات (SGBD). تعمل الطبقة [jpa] كـ ORM (مُخطط علاقات الكائنات). وهي تشكل جسراً بين الكائنات التي تعالجها الطبقة [DAO] والصفوف والأعمدة لبيانات قاعدة البيانات العلائقية،
- ويمكن تحقيق تكامل الطبقات بواسطة حاوية Spring أو EJB3 (Enterprise Java Bean).
الأمثلة الواردة أدناه لتوضيح JSF، ستستخدم طبقة واحدة فقط، وهي الطبقة [web]:
![]() |
بمجرد اكتساب أساسيات JSF، سنقوم ببناء تطبيقات Java EE متعددة الطبقات.
2.2. نموذج التطوير MVC لـ JSF
JSF ينفذ نموذج الهندسة المعمارية المعروف باسم MVC (النموذج – العرض – وحدة التحكم) بالطريقة التالية:
![]() |
تنفذ هذه البنية نمط التصميم MVC (النموذج، العرض، وحدة التحكم). تتم معالجة طلب العميل وفقًا للخطوات الأربع التالية:
- الطلب - يقوم متصفح العميل بإرسال طلب إلى وحدة التحكم [Faces Servlet]. تمر جميع طلبات العملاء عبر هذه الوحدة. وهي بمثابة بوابة الدخول إلى التطبيق. وهي تمثل الحرف C في MVC،
- المعالجة - يقوم وحدة التحكم C بمعالجة هذا الطلب. وللقيام بذلك، يستعين بمديري الأحداث الخاصة بالتطبيق المكتوب [2a]. وقد يحتاج هؤلاء المديرون إلى مساعدة من طبقة الأعمال [2b]. بمجرد معالجة طلب العميل، يمكن أن يؤدي ذلك إلى استدعاء استجابات متنوعة. ومن الأمثلة النموذجية على ذلك:
- صفحة أخطاء إذا تعذر معالجة الطلب بشكل صحيح؛
- صفحة تأكيد في الحالات الأخرى،
- التنقل - يختار وحدة التحكم الاستجابة (= العرض) التي سيتم إرسالها إلى العميل. يتطلب اختيار الاستجابة التي سيتم إرسالها إلى العميل عدة خطوات:
- اختيار Facelet الذي سيقوم بإنشاء الاستجابة. وهذا ما يُسمى العرض V، حرف V في MVC. يعتمد هذا الاختيار عمومًا على نتيجة تنفيذ الإجراء الذي طلبه المستخدم؛
- تزويد هذه الوحدة النمطية (Facelet) بالبيانات التي تحتاجها لتوليد هذه الاستجابة. في الواقع، غالبًا ما تحتوي هذه الوحدة النمطية على معلومات يحسبها وحدة التحكم. تشكل هذه المعلومات ما يُسمى بالنموذج M للعرض، وهو حرف M في MVC،
تتمثل الخطوة 3 إذن في اختيار عرض V وبناء النموذج M اللازم له.
- الاستجابة - يطلب وحدة التحكم C من Facelet المختارة أن تعرض نفسها. وتستخدم هذه الأخيرة النموذج M الذي أعدته وحدة التحكم C لتهيئة الأجزاء الديناميكية من الاستجابة التي يجب أن ترسلها إلى العميل. قد تتنوع الشكل الدقيق لهذه الأجزاء: فقد يكون تدفقًا HTML، أو PDF، أو ملف Excel، ...
في مشروع JSF:
- المراقب C هو السيرفلت [javax.faces.webapp.FacesServlet]. ويوجد هذا في مكتبة [javaee.jar]،
- يتم تنفيذ طرق العرض V بواسطة صفحات تستخدم تقنية Facelets،
- يتم تنفيذ نماذج M ومديري الأحداث من خلال فئات Java تُعرف غالبًا باسم "backing beans" أو ببساطة "beans".
الآن، دعونا نوضح العلاقة بين بنية الويب MVC وبنية الطبقات. هذان مفهومان مختلفان يتم الخلط بينهما أحيانًا. لنأخذ تطبيق ويب JSF أحادي الطبقة:
![]() |
إذا قمنا بتنفيذ الطبقة [web] مع JSF، فسنحصل على بنية ويب MVC ولكن ليس بنية متعددة الطبقات. هنا، ستتولى الطبقة [web] كل شيء: العرض، والوظيفة، والوصول إلى البيانات. مع JSF، ستقوم الحبوب (beans) بهذه المهمة.
الآن، لننظر إلى بنية ويب متعددة الطبقات:
![]() |
يمكن تنفيذ الطبقة [web] بدون إطار عمل وبدون اتباع النموذج MVC. لدينا إذن بنية متعددة الطبقات، لكن طبقة الويب لا تنفذ النموذج MVC.
في MVC، ذكرنا أن النموذج M هو نموذج العرض V، c.a.d. مجموعة البيانات المعروضة بواسطة العرض V. غالبًا ما يُعطى تعريف آخر للنموذج M لـ MVC:
![]() |
يعتبر العديد من المؤلفين أن ما يقع على يمين الطبقة [web] يشكل النموذج M لـ MVC. لتجنب الغموض، سنستخدم مصطلح:
- نموذج المجال عند الإشارة إلى كل ما يقع على يمين الطبقة [web]،
- نموذج العرض عند الإشارة إلى البيانات المعروضة بواسطة عرض 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 يدعم EJB (Enterprise Java Bean) بينما Tomcat لا يدعمه. لن تستخدم أمثلة JSF الخاصة بنا أي EJB. لذلك هنا، يمكننا اختيار أي خادم،
- في [6]، نختار إصدار Java EE 6 Web،
- في [7]، المشروع الذي تم إنشاؤه.
دعونا ندرس عناصر المشروع ونوضح دور كل منها.
![]() |
- في [1]: الفروع المختلفة للمشروع:
- [Web Pages]: سيحتوي على صفحات الويب (.xhtml، .jsp، .html)، والموارد (الصور، والمستندات المختلفة)، وتكوين طبقة الويب وكذلك تكوين إطار العمل JSF؛
- [Source packages]: فئات Java الخاصة بالمشروع؛
- [Dependencies]: أرشيفات .jar اللازمة للمشروع والتي يديرها إطار العمل Maven؛
- [Java Dependencies]: أرشيفات .jar اللازمة للمشروع والتي لا يديرها إطار العمل Maven؛
- [Project Files]: ملف تكوين Maven و Netbeans،
![]() |
- في [2]: الفرع [Web Pages]،
وهي تحتوي على الصفحة التالية [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] نجد بالتالي artifactId التالية: spring-context، spring-core، spring-beans، ... في المجموعة [javax.faces]، نجد artifactId و 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، الإصدار، التعبئة). عندما تكون معلومة packaging غائبة كما هو الحال هنا، يتم استخدام ملف jar packaging. يتم إضافة معلومة أخرى، وهي 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] بواسطة عملية بناء (build) المشروع،
- [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 بتنزيلها تلقائيًا. يبحث عنها في ما يُسمى بالمستودعات. يتم استخدام المستودع المركزي (Central Repository) تلقائيًا. يمكن إضافة مستودعات أخرى بفضل العلامة <repository>. هنا تمت إضافة مستودعين:
- الأسطر 46-51: مستودع لمكتبة JSF 2،
- الأسطر 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] و Classpath الخاصة بالتطبيق، بحثًا عن الملفات ذات اللاحقة .tld (TagLib Definition). وهنا، سيجدها في الأرشيف [jsf-impl.jar] و [1,2]:
![]() |
دعونا نفحص ملف [3] وملف [HTML_basic.tld]:
- في السطر 19، عنوان URI لمكتبة العلامات،
- في السطر 16، اسمها المختصر.
توجد تعريفات العلامات المختلفة <h:xx> في هذا الملف. تتم إدارة هذه العلامات بواسطة فئات Java الموجودة أيضًا في الأداة [jsf-impl.jar].
لنعد إلى مشروعنا JSF. فقد تم إثرائه بفرع جديد:
![]() |
يحتوي الفرع [Other Sources] [1] على الملفات التي يجب أن تكون في مسار فئة المشروع (Classpath) والتي ليست من كود 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 خدمة c.a.d، وهي فئة Java قادرة على معالجة طلبات العملاء. يعمل تطبيق 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 المختارة أن تعرض نفسها. وتستخدم هذه الأخيرة النموذج 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: تحدد معلمة مخصصة لـ servlet [Faces Servlet]. يحدد المعلمة javax.faces.PROJECT_STAGE المرحلة التي يمر بها المشروع قيد التنفيذ. في مرحلة التطوير (Development)، تعرض الخدمة (servlet) [Faces Servlet] رسائل خطأ مفيدة في عملية تصحيح الأخطاء. في مرحلة الإنتاج (Production)، لا يتم عرض هذه الرسائل بعد الآن،
- الأسطر 17-19: مدة الجلسة بالدقائق. يتواصل العميل مع التطبيق من خلال سلسلة من دورات الطلب/الاستجابة. تستخدم كل دورة اتصال TCP-IP خاص بها، ويكون جديدًا في كل دورة جديدة. كذلك، إذا أرسل العميل C طلبين D1 و D2، فإن الخادم S لا يملك الوسائل لمعرفة أن الطلبين ينتميان إلى نفس العميل C. فالخادم S لا يملك ذاكرة العميل. هذا هو ما يقتضيه بروتوكول HTTP المستخدم (بروتوكول النقل HyperText): يتواصل العميل مع الخادم من خلال سلسلة من دورات طلب العميل / استجابة الخادم باستخدام اتصال جديد TCP-IP في كل مرة. ويُطلق على هذا اسم بروتوكول بدون حالة. في بروتوكولات أخرى، مثل FTP (File Transfer Protocol)، يستخدم العميل 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]، كما تم شرحه، يتم تقديم الصفحة الرئيسية (welcome-file) [index.xhtml].
قد يثير فضولنا إلقاء نظرة على شفرة المصدر المستلمة [3]:
لقد تلقينا 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، الإصدار، الحزمة (النوع) والنطاق). نبدأ بتحديد [groupId] [3]،
- في [4]، نكتب [espace] لعرض قائمة العناصر المحتملة. هنا [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 (Object Relational Mapper) وهذا كل ما نعرفه. يمكننا عندئذٍ الانتقال إلى موقع [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] المستخدم. ما على القارئ سوى استخدامه ليكون في نفس ظروف المستند. بالإضافة إلى ذلك، يتم التعرف على مشاريع Maven من قبل برامج Java الرئيسية (Eclipse، Netbeans، IntelliJ، Jdeveloper). وبالتالي، يمكن للقارئ استخدام برنامج IDE المفضل لديه لاختبار الأمثلة.
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['clé'] أو bean.champ. في أمثلةنا، سيكون 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.champ} على أنه [Bean].getChamp()، حيث [Bean] هي مثيل لفئة Java تم تسميتها بـ bean و getChamp، الـgetter المرتبط بحقل champ في 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{
// اللغة المحلية للصفحات
private String locale="fr";
public ChangeLocale() {
}
...
public String getLocale() {
return locale;
}
}
- السطر 11: الحقل local،
- السطر 17: جتر الخاص به،
- السطر 7: التعليق التوضيحي ManagedBean يجعل من فئة Java ChangeLocale حبة معترف بها من قبل JSF. يتم تعريف bean بواسطة اسم. يمكن تحديد هذا الاسم بواسطة السمة name للتعليق التوضيحي: @ManagedBean(name= "xx "). في حالة عدم وجود السمة name، يتم استخدام اسم الفئة مع تحويل الحرف الأول إلى حرف صغير. وبالتالي، فإن اسم bean ChangeLocale هو changeLocale. يجب الانتباه إلى أن التعليق التوضيحي ManagedBean ينتمي إلى الحزمة javax.faces.bean.ManagedBean وليس إلى الحزمة javax.annotations.ManagedBean.
- السطر 8: تحدد التعليقات التوضيحية SessionScoped نطاق الفول. هناك العديد منها. سنستخدم بشكل شائع الثلاثة التالية:
- RequestScoped: مدة حياة البين هي مدة دورة طلب المتصفح / استجابة الخادم. إذا كان هذا البين ضروريًا مرة أخرى لمعالجة طلب جديد من نفس المتصفح أو من متصفح آخر، فسيتم إنشاء مثيل له مرة أخرى،
- 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]. وهذا أمر إلزامي بالنسبة لـ beans ذات النطاق Session التي قد يضطر خادم الويب إلى تسلسلها في ملفات. سنعود لاحقًا إلى bean [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.titre']} بالصيغة #{bean['champ']}. 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[_CodeLangue][_CodePays].properties. لا تحدد العلامة <base-name> سوى الجزء الأول من الاسم. أما الباقي فهو ضمني. قد توجد عدة ملفات رسائل، واحد لكل لغة:
![]() |
- في ملف [1]، نرى أربعة ملفات رسائل تتوافق مع اسم قاعدة الرسائل المحدد في ملف [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، على سبيل المثال ressources.messages، إذا كان الملف [messages.properties] موجودًا في المجلد [ressources] التابع لـ Classpath. نظرًا لأن الاسم، في السطر 14، لا يتضمن حزمة، يجب وضع الملف [messages.properties] في جذر المجلد [src / main / resources]:
![]() |
في [1]، في علامة التبويب [Projects] لمشروع Netbeans، يظهر الملف [messages.properties] كقائمة بالإصدارات المختلفة للرسائل المحددة. يتم تحديد الإصدارات من خلال سلسلة تتكون من واحد إلى ثلاثة رموز [codeLangue_codePays_codeVariante]. في [1]، تم استخدام الرمز [codeLangue] فقط: 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.titre. يتم البحث عن هذه الرسالة والعثور عليها في الملف [messages.properties] للغة النشطة في الوقت الحالي. على سبيل المثال، بالنسبة للغة الفرنسية:
welcome.titre=Tutoriel JSF (JavaServer Faces)
تكون الرسالة على شكل مفتاح=قيمة. يصبح السطر 8 من الملف [index.xhtml] كما يلي بعد تقييم التعبير #{msg['welcome.titre']}:
<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، يتم تحديد النموذج بواسطة معرف النموذج. في هذا المثال، لن يتم استخدام معرف النموذج وكان من الممكن حذفه.
- السطور 18-21: تحدد العلامة <h:panelGrid> هنا جدولًا HTML مكونًا من عمودين. وهي تولد العلامة <table>،
- يحتوي النموذج على ثلاثة روابط تطلق معالجته، في الأسطر 19 و20 و23. تحتوي العلامة <h:commandLink> على سمتين على الأقل:
- value: نص الرابط؛
- 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>. عندما ينقر المستخدم على الرابط [Français]، سيتم تنفيذ كود جافا سكريبت. يتم تضمين هذا الكود في الصفحة التي استلمها المتصفح، ويقوم المتصفح بتنفيذه. يُستخدم كود جافا سكريبت على نطاق واسع في تقنيات JSF و AJAX (Asynchronous Javascript And Xml). ويهدف بشكل عام إلى تحسين سهولة الاستخدام وسرعة استجابة تطبيقات الويب. وغالبًا ما يتم إنشاؤه تلقائيًا بواسطة أدوات برمجية، ولذلك لا داعي لفهمه. ولكن في بعض الأحيان قد يضطر المطور إلى إضافة كود جافا سكريبت إلى صفحاته. وعندها تصبح معرفة جافا سكريبت ضرورية.
لا داعي هنا لفهم كود جافا سكريبت الذي تم إنشاؤه لعلامة JSF <h:commandLink>. ومع ذلك، يمكن ملاحظة نقطتين:
- يستخدم كود جافا سكريبت المعرف النموذجي الذي أعطيناه لعلامة JSF <h:form>،
- يُنشئ JSF معرّفات تلقائية لجميع العلامات التي لم يتم تعريف السمة id فيها. نرى مثالاً على ذلك هنا: j_idt8. إن إعطاء معرف واضح للعلامات يتيح فهم كود جافا سكريبت الذي تم إنشاؤه بشكل أفضل إذا لزم الأمر. وهذا هو الحال بشكل خاص عندما يتعين على المطور نفسه إضافة كود جافا سكريبت الذي يتعامل مع مكونات الصفحة. فهو يحتاج عندئذٍ إلى معرفة معرفات id لمكوناته.
ماذا سيحدث عندما ينقر المستخدم على الرابط [Français] في الصفحة أعلاه؟ لننظر إلى بنية تطبيق JSF:
![]() |
سيتلقى وحدة التحكم [Faces Servlet] طلب المتصفح العميل بالشكل التالي HTTP:
- السطران 1-2: يطلب المتصفح URL [http://localhost:8080/mv-jsf2-02/faces/index.xhtml]. الأمر دائمًا على هذا النحو: يتم إرسال المدخلات التي تم إدخالها في نموذج JSF الذي تم الحصول عليه في البداية باستخدام URL 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 الحرف :. وبالتالي، فإن السلسلة formulaire:j_idt8=formulaire:j_idt8 هي التي يتم إرسالها إلى الخادم. ربما نتذكر أننا سبق أن صادفنا المعرف j_idt8 عندما درسنا الرمز HTML الذي تم إنشاؤه لعلامة
<h:commandLink value="#{msg['welcome.langue1']}" action="#{changeLocale.setFrenchLocale}"/>
وقد تم إنشاؤه تلقائيًا بواسطة JSF. وما يهمنا هنا هو أن وجود هذا المعرف في سلسلة القيم المرسلة من متصفح العميل يسمح لـ JSF بمعرفة أن الرابط [Français] قد تم النقر عليه. وبالتالي، سيستخدم السمة action المذكورة أعلاه لتحديد كيفية معالجة السلسلة المستلمة. يشير السمة action="#{changeLocale.setFrenchLocale}" إلى JSF بأن طلب العميل يجب معالجته بواسطة الطريقة [setFrenchLocale] لكائن يسمى changeLocale. نتذكر أن هذا الكائن (bean) تم تعريفه بواسطة تعليقات توضيحية في فئة Java [ChangeLocale]:
@ManagedBean
@SessionScoped
public class ChangeLocale implements Serializable{
يتم تعريف اسم bean بواسطة السمة name للتعليق التوضيحي @ManagedBean. في حالة عدم وجود هذه السمة، يتم استخدام اسم الفئة كاسم bean مع تحويل الحرف الأول إلى حرف صغير.
لنعد إلى استعلام المتصفح:
![]() |
وإلى العلامة <h:commandLink> التي أنشأت الرابط [Français] الذي تم النقر عليه:
<h:commandLink value="#{msg['welcome.langue1']}" action="#{changeLocale.setFrenchLocale}"/>
سيقوم وحدة التحكم بنقل طلب المتصفح إلى مدير الأحداث المحدد بواسطة السمة action لعلامة <h:commandLink>. يجب أن يكون لمدير الأحداث M المشار إليه بواسطة السمة action لأمر <h:commandLink> التوقيع التالي:
- لا يتلقى أي معلمات. سنرى أنه يمكنه مع ذلك الوصول إلى طلب العميل،
- ويجب أن يعرض نتيجة C من النوع String. يمكن أن تكون سلسلة الأحرف C هذه:
- إما اسم صفحة JSF من المشروع؛
- أو اسم محدد في قواعد التنقل في الملف [faces-config.xml] ومرتبط بصفحة JSF في المشروع؛
- أو مؤشر null، إذا كان متصفح العميل لا يجب أن يغير الصفحة،
في بنية JSF أعلاه، سيستخدم وحدة التحكم [Faces Servlet] السلسلة C التي يعرضها مدير الأحداث وربما ملف التكوين الخاص به [faces-config.xml] لتحديد أي صفحة JSF، يجب إرسالها ردًا على العميل [4].
في العلامة
<h:commandLink value="#{msg['welcome.langue1']}" action="#{changeLocale.setFrenchLocale}"/>
، فإن معالج حدث النقر على الرابط [Français] هو الطريقة [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{
// إعدادات اللغة للصفحات
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]. يتيح سياق تنفيذ الطلب الذي تم الحصول عليه بهذه الطريقة الوصول إلى المعلمات التي أرسلها متصفح العميل إلى الخادم باستخدام الطريقة التالية:
إذا كانت المعلمات المرسلة (POST) بواسطة متصفح العميل هي التالية:
فإن الطريقة 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]. سيؤدي النقر على الرابط [Anglais] (السطر 14) إلى تغيير الموقع إلى en (انظر الطريقة changeLocale.setEnglishLocale). سيتم عندئذٍ استخدام الملف [messages_en.properties] وستظهر الصفحة باللغة الإنجليزية:
![]() | ![]() |
في كل مرة يتم فيها عرض الصفحة [index.xhtml]، يتم تنفيذ العلامة <f:view>:
<f:view locale="#{changeLocale.locale}">
وبالتالي يتم إعادة تنفيذ الطريقة [ChangeLocale].getLocale(). وبما أننا أعطينا نطاق 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. تنفيذ المشروع
أصبح مشروعنا الآن كاملاً. يمكننا الآن تنفيذه (Clean and Build):
![]() |
- يؤدي بناء المشروع إلى إنشاء المجلد [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]، c.a.d. 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:
- تحديد الفاصوليا ونطاقها باستخدام التعليقات التوضيحية @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>
<resource-bundle>
<base-name>
messages
</base-name>
<var>msg</var>
</resource-bundle>
</application>
<!-- حبوب مُدارة -->
<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-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: الاسم الكامل للفئة المرتبطة بالبيان؛
- السطر 23: نطاق البين. القيم الممكنة هي 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"/>
في السمة action، يتم تعيين مفتاح التنقل welcome المحدد في [faces-config]،
- لا يلزم تعديل الطريقتين setFrenchLocale و setEnglishLocale اللتين يجب أن تعيدا مفتاح تنقل، لأنهما كانتا تعيدان القيمة null للإشارة إلى البقاء على نفس الصفحة.
2.4.10. الخلاصة
لنعد إلى مشروع Netbeans الذي كتبناه:
![]() |
يغطي هذا المشروع البنية التالية:
![]() |
في كل مشروع JSF، سنجد العناصر التالية:
- صفحات JSF [A] التي يتم إرسالها [4] إلى متصفحات العملاء بواسطة وحدة التحكم [Faces Servlet] [3]،
- ملفات الرسائل [C] التي تسمح بتغيير لغة الصفحات JSF،
- فئات Java [B] التي تعالج الأحداث التي تحدث على متصفح العميل [2a, 2b] و/أو التي تستخدم كنماذج للصفحات JSF [3]. في أغلب الأحيان، يتم تطوير واختبار الطبقات [métier] و [DAO] بشكل منفصل. ثم يتم اختبار الطبقة [web] مع طبقة [métier] وهمية. إذا كانت الطبقتان [métier] و [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: معلمة لـ servlet [Faces Servlet]. وهي تطلب أن تكون التعليقات في facelet من النوع:
<!-- اللغات -->
يتم تجاهلها. بدون هذا المعامل، تسبب التعليقات مشاكل يصعب فهمها،
- الأسطر 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]:
<!-- السطر 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 من جدول الإدخالات:
![]() |
يعرض السطر 2 الحقل [1]، والأسطر 3-6: الحقل [2]، والسطر 7: الحقل [3].
يستخدم السطران 5 و7 تعبير EL الذي يستخدم bean النموذج المحدد في الفئة [Form.java] بالطريقة التالية:
package forms;
import javax.enterprise.context.RequestScoped;
import javax.faces.bean.ManagedBean;
@ManagedBean
@RequestScoped
public class Form {
- السطر 7 يحدد bean بدون اسم. سيكون هذا هو اسم الفئة التي تبدأ بحرف صغير: form،
- ويكون نطاق bean هو request. وهذا يعني أنه في دورة طلب العميل / استجابة الخادم، يتم إنشاء مثيل له عندما يحتاج الطلب إليه ويتم حذفه عند إرجاع الاستجابة إلى العميل.
في الكود أدناه من الصفحة [index.xhtml]:
<!-- السطر 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 الخاصة بـ«bean form». لفهم الروابط التي تربط الصفحة P بنموذجها M، يجب العودة إلى دورة « » (الطلب من العميل / الاستجابة من الخادم) التي تميز تطبيق الويب:
![]() |
يجب التمييز بين الحالة التي يتم فيها إرسال الصفحة P كاستجابة للمتصفح (الخطوة 4)، على سبيل المثال عند الطلب الأولي للصفحة، والحالة التي يقوم فيها المستخدم بإحداث حدث على الصفحة P، ويتم معالجته بواسطة وحدة التحكم [Faces Servlet] (الخطوة 1).
يمكن التمييز بين هاتين الحالتين من منظور المتصفح:
- عند الطلب الأولي للصفحة، يقوم المتصفح بعملية GET على URL للصفحة،
- عند إرسال القيم التي تم إدخالها في الصفحة، يقوم المتصفح بإجراء عملية POST على URL للصفحة.
في كلتا الحالتين، يتم طلب نفس URL. ووفقًا لطبيعة طلب المتصفح GET أو POST، ستختلف معالجة الطلب.
[cas 1 – demande initiale de la page P]
يطلب المتصفح URL للصفحة باستخدام GET. سيتخطى وحدة التحكم [Faces Servlet] مباشرةً إلى الخطوة [4] لعرض الاستجابة وسيتم إرسال الصفحة [index.xhtml] إلى العميل. سيطلب وحدة التحكم JSF من كل علامة في الصفحة أن يتم عرضها. لنأخذ مثالاً من السطر 5 من كود [index.xhtml]:
<h:inputText id="inputText" value="#{form.inputText}"/>
تؤدي العلامة JSF <h:inputText value="قيمة"/> إلى إنشاء العلامة HTML <input type="text" value="قيمة"/>. تواجه الفئة المسؤولة عن معالجة هذه العلامة التعبير #{form.inputText} الذي يجب عليها تقييمه:
- إذا لم يكن bean form موجودًا بعد، يتم إنشاؤه عن طريق إنشاء مثيل للفئة forms.Form،
- يتم تقييم التعبير #{form.inputText} عن طريق استدعاء الطريقة form.getInputText()،
- يتم إدراج النص <input id="formulaire:inputText" type="text" name="formulaire:inputText" value="texte" /> في التدفق HTML الذي سيتم إرساله إلى العميل إذا افترضنا أن الطريقة form.getInputText() قد أعادت السلسلة "texte". كما ستقوم JSF بتسمية (name) المكون HTML الموجود في التدفق. يتم إنشاء هذا الاسم من معرفات id للمكون JSF الذي تم تحليله ومعرفات مكوناته الأصلية، وهي هنا العلامة <h:form id="formulaire"/>.
يجب ملاحظة أنه إذا تم استخدام التعبير #{M.champ} في صفحة P، حيث M هو نموذج bean للصفحة P، فيجب أن يتوفر في هذا النموذج الطريقة العامة getChamp(). يجب أن يكون من الممكن تحويل النوع الذي تنتجه هذه الطريقة إلى نوع String. أحد النماذج M الممكنة والشائعة هو التالي:
حيث T هو نوع يمكن تحويله إلى نوع String، ربما باستخدام طريقة toString.
وفي حالة عرض الصفحة P أيضًا، فإن معالجة السطر:
<h:outputText value="#{form.inputText}"/>
سيكون مماثلاً وسيتم إنشاء التدفق التالي HTML:
داخليًا في الخادم، يتم تمثيل الصفحة 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).
[cas 2 – traitement de la page P]
![]() |
نحن الآن في الخطوة [1] أعلاه حيث سيتلقى وحدة التحكم [Faces Servlet] طلبًا POST من متصفح العميل الذي أرسل إليه سابقًا الصفحة [index.xhtml]. نحن الآن في مرحلة معالجة حدث للصفحة. ستحدث عدة خطوات قبل أن يتم معالجة الحدث في [2a]. دورة معالجة طلب POST بواسطة وحدة التحكم JSF هي كما يلي:

- في [A]، بفضل الحقل المخفي javax.faces.ViewState، يتم إعادة تكوين العرض الذي تم إرساله في البداية إلى متصفح العميل. هنا، تستعيد مكونات الصفحة القيمة التي كانت لها في الصفحة المرسلة. يستعيد مكوننا inputText قيمته "نص"،
- في [B]، تُستخدم القيم التي أرسلها متصفح العميل لتحديث مكونات العرض. وبالتالي، إذا في حقل الإدخال HTML المسمى inputText، كتب المستخدم "jean"، فإن القيمة "jean" تحل محل القيمة "نص". الآن تعكس طريقة العرض الصفحة كما عدّلها المستخدم وليس كما تم إرسالها إلى المتصفح،
- في [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 على طريقة set،
- بمجرد تحديث نموذج 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(saisie). في هذه الخطوة، قد تحدث عمليات تحويل وتحقق من الصحة قد تفشل. في هذه الحالة، لا تتم معالجة الحدث الذي تسبب في POST للصفحة P ويتم إعادة إرسال الصفحة مرة أخرى إلى العميل بالشكل الذي أدخله.
سيكون النموذج [Form.java] للصفحة [index.xhtml] كما يلي:
package forms;
import javax.enterprise.context.RequestScoped;
import javax.faces.bean.ManagedBean;
@ManagedBean
@RequestScoped
public class Form {
/** ينشئ مثيلًا جديدًا من النموذج */
public Form() {
}
// حقول النموذج
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";
// الأحداث
public String submit(){
return null;
}
// مُستردات ومُعيّنات
...
}
تُستخدم حقول الأسطر 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">
<!-- اللغات -->
<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">
<!-- رؤوس -->
<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 -->
...
<!-- السطر 2 -->
...
<!-- السطر 3 -->
...
<!-- السطر 4 -->
...
<!-- السطر 5 -->
...
<!-- السطر 6 -->
...
<!-- السطر 7 -->
...
<!-- السطر 8 -->
...
<!-- السطر 9 -->
...
<!-- السطر 10 -->
...
<!-- السطر 11 -->
...
<!-- السطر 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: يتم تعريف ورقة الأنماط للصفحة داخل العلامة HTML <head>، بواسطة العلامة:
<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 لتحديد نمط النصوص في السطر الأول من الجدول:
<!-- رؤوس -->
<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 لتحديد نمط النصوص في العمود الأول من الجدول:
<!-- السطر 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 التالية:
style="background-image: url('${request.contextPath}/resources/images/standard.jpg');"
الذي سيكون صالحًا بغض النظر عن السياق.
2.5.10. دورتا طلب العميل / استجابة الخادم لنموذج
لنعد إلى ما تم شرحه سابقًا في الفقرة 2.5.7 في حالة عامة ونطبقه على النموذج قيد الدراسة. سيتم اختبار هذا النموذج في بيئة JSF التقليدية:
![]() |
هنا، لن يكون هناك مديرو أحداث ولا طبقة [métier]. وبالتالي، لن تكون هناك خطوات [2x]. وسنميّز بين الحالة التي يطلب فيها المتصفح النموذج F في البداية، والحالة التي يثير فيها المستخدم حدثًا في النموذج F، فيتم معالجته بواسطة وحدة التحكم [Faces Servlet]. هناك دورتان مختلفتان لطلب العميل/استجابة الخادم.
- الأول يتوافق مع الطلب الأولي للصفحة ويتم إثارته بواسطة عملية GET للمتصفح على عنصر التحكم URL للنموذج،
- والثانية تتعلق بإرسال القيم التي تم إدخالها في الصفحة، ويتم تشغيلها بواسطة عملية POST على نفس العملية URL.
وفقًا لطبيعة الطلب GET أو POST للمتصفح، يختلف معالجة الطلب بواسطة وحدة التحكم [Faces Servlet].
[cas 1 – demande initiale du formulaire F]
يطلب المتصفح URL للصفحة باستخدام GET. سيتخطى وحدة التحكم [Faces Servlet] مباشرة إلى خطوة [4] لعرض الاستجابة. سيتم تهيئة النموذج [index.xhtml] بواسطة نموذجه [Form.java] وإرساله إلى العميل الذي يتلقى العرض التالي:

في هذه الحالة، تكون التبادلات بين العميل والخادم HTTP كما يلي:
طلب HTTP من العميل:
في السطر 1، نرى GET من المتصفح.
استجابة HTTP من الخادم:
غير معروض هنا، يتبع السطر 7 سطر فارغ ورمز HTML الخاص بالنموذج. هذا هو الرمز الذي يفسره المتصفح ويعرضه.
[cas 2 – traitement des valeurs saisies dans le formulaire F]
يقوم المستخدم بملء النموذج والتحقق من صحته باستخدام الزر [Valider]. ثم يطلب المتصفح رمز URL الخاص بالنموذج مع رمز POST. يقوم وحدة التحكم [Faces Servlet] بمعالجة هذا الطلب، وتحديث نموذج [Form.java] للنموذج [index.xhtml]، وإعادة إرسال النموذج [index.xhtml] المحدث بواسطة هذا النموذج الجديد. لنستعرض هذه الدورة من خلال مثال:

في المثال أعلاه، قام المستخدم بإدخال البيانات وتأكيدها. ويتلقى كاستجابة العرض التالي:

في هذه الحالة، تكون التبادلات بين العميل والخادم HTTP كما يلي:
طلب HTTP من العميل:
في السطر 1، POST الذي أنشأه المتصفح. في السطر 14، القيم التي أدخلها المستخدم. يمكننا على سبيل المثال اكتشاف النص الذي تم إدخاله في حقل الإدخال:
في السطر 14، تم إرسال الحقل المخفي javax.faces.ViewState. يمثل هذا الحقل، في شكل مشفر، حالة النموذج كما تم إرسالها في البداية إلى المتصفح عند GET الأولي.
رد HTTP من الخادم:
غير معروض هنا، يتبع السطر 6 سطر فارغ ورمز HTML للنموذج المحدث بواسطة نموذجه الجديد الناتج عن POST.
سنقوم الآن بفحص المكونات المختلفة لهذا النموذج.
2.5.11. العلامة <h:inputText>
تولد العلامة <h:inputText> علامة HTML <input type="text" ...>.
لننظر إلى الكود التالي:
<!-- السطر 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] وقم بتحقق صحة النموذج باستخدام الزر [Valider] [2]. نحصل كاستجابة على الصفحة [3, 4]:
![]() |
يتم إرسال قيمة الحقل [1] على النحو التالي:
في [2]، يتم التحقق من صحة النموذج باستخدام الزر التالي:
<h:commandButton id="submit" type="submit" value="#{msg['form.submitText']}"/>
لا تحتوي العلامة <h:commandButton> على سمة action. في هذه الحالة، لا يتم استدعاء أي معالج أحداث ولا أي قاعدة تنقل. بعد المعالجة، يتم إرجاع نفس الصفحة. لنراجع دورة معالجتها:

- في [A]، يتم استعادة الصفحة P كما تم إرسالها. وهذا يعني أن المكون ذو المعرف inputText يتم استعادته بقيمته الأولية "نص"،
- في [B]، يتم تعيين القيم التي أرسلها المتصفح (التي أدخلها المستخدم) إلى مكونات الصفحة P. هنا، يتلقى المكون ذو المعرف inputText القيمة "نص جديد"،
- في [C]، تتم عمليات التحويل والتحقق من الصحة. هنا، لا توجد أي منها. في النموذج M، الحقل المرتبط بالمكون ذي المعرف inputText هو التالي:
private String inputText="texte";
نظرًا لأن القيم المدخلة من النوع String، فلا توجد تحويلات يجب إجراؤها. علاوة على ذلك، لم يتم إنشاء أي قاعدة للتحقق من الصحة. سنقوم بإنشائها لاحقًا.
- في [D]، يتم تعيين القيم المدخلة إلى النموذج. يتلقى الحقل inputText من [Form.java] القيمة "نص جديد"،
- في [E]، لا يتم إجراء أي شيء لأنه لم يتم ربط أي معالج أحداث بالزر [Valider].
- في [F]، يتم إرسال الصفحة P مرة أخرى إلى العميل لأن الزر [Valider] لا يحتوي على سمة action. ثم يتم تنفيذ الأسطر التالية من [index.xhtml]:
<!-- السطر 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>، باستثناء أن كل حرف يكتبه المستخدم يتم استبداله بصريًا بحرف *.
لننظر إلى الكود التالي:
<!-- السطر 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] لتوليد نص حقل الإدخال. هناك استثناء عندما يكون هذا الحقل من نوع "كلمة مرور". لا تستخدم العلامة <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] وقم بتأكيد النموذج باستخدام الزر [Valider] [2]. نحصل كاستجابة على الصفحة [3]:
![]() |
يتم إرسال قيمة الحقل [1] على النحو التالي:
أدى التحقق من صحة النموذج بواسطة [2] إلى تحديث النموذج [Form.java] عن طريق الإدخال [1]. ثم تلقى الحقل inputSecret في [Form.java] القيمة mdp. ونظرًا لأن النموذج [index.xhtml] لم يحدد أي قاعدة تنقل أو أي معالج أحداث، يتم إعادة عرضه بعد تحديث نموذجه. نعود إذن إلى العرض الذي تم عند الطلب الأولي للصفحة [index.xhtml] حيث تغيرت قيمة الحقل inputSecret في النموذج من [3].
2.5.13. العلامة <h:inputTextArea>
تولد العلامة <h:inputTextArea> علامة HTML <textarea ...>نص</textarea>. وهو حقل إدخال مشابه لحقل العلامة <h:inputText>، إلا أنه هنا يمكن كتابة عدة أسطر من النص.
لننظر إلى الكود التالي:
<!-- السطر 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] وقم بتأكيد النموذج باستخدام الزر [Valider] [2]. نحصل كاستجابة على الصفحة [3]:
![]() |
قيمة الحقل [1] المرسلة هي التالية:
أدى التحقق من صحة النموذج بواسطة [2] إلى تحديث النموذج [Form.java] عن طريق الإدخال [1]. ثم تلقى الحقل textArea في [Form.java] القيمة "Tutoriel JSF\npartie1". يُظهر إعادة عرض [index.xhtml] أن الحقل textArea في النموذج قد تم تحديثه بالفعل إلى [3].
2.5.14. علامة <h:selectOneListBox>
تولد العلامة <h:selectOneListBox> علامة HTML <select>...</select>. بصريًا، تولد قائمة منسدلة أو قائمة مع شريط تمرير.
لننظر إلى الكود التالي:
<!-- السطر 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: إن اختيار العنصر الذي قيمته="2" من القائمة يترجم إلى وجود السمة selected="selected".
الآن، أدناه، لنختر [1] قيمة جديدة من القائمة ونقوم بتحقيق صحة النموذج باستخدام الزر [Valider] [2]. نحصل في المقابل على الصفحة [3]:
![]() |
قيمة الحقل [1] التي تم إرسالها هي التالية:
أدى التحقق من صحة النموذج بواسطة [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>:
<!-- السطر 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] قيمة جديدة من القائمة ونقوم بتحقيق صحة النموذج باستخدام الزر [Valider] [2]. نحصل في المقابل على الصفحة [3]:
![]() |
القيمة التي تم إرسالها للحقل [1] هي التالية:
أدى التحقق من صحة النموذج بواسطة [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> التي تسمح للمستخدم بتحديد عدة عناصر في قائمة.
لننظر إلى الكود التالي:
<!-- السطر 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"};
...
// مُستقبلات ومُعيّنات
public String getSelectManyListBoxValue(){
return getValue(selectManyListBox);
}
private String getValue(String[] chaines){
String value="[";
for(String chaine : chaines){
value+=" "+chaine;
}
return value+"]";
}
لو تم استدعاء الطريقة getSelectManyListBox، لكنا حصلنا على مصفوفة من String. لإدراج هذا العنصر في التدفق HTML، كان سيتعين على وحدة التحكم استدعاء طريقتها toString. ولكن هذه الطريقة، بالنسبة للمصفوفة، لا تقوم سوى بإرجاع "hashcode" الخاص بها وليس قائمة عناصرها كما نرغب. لذلك نستخدم الطريقة getSelectManyListBoxValue المذكورة أعلاه للحصول على سلسلة أحرف تمثل محتوى المصفوفة،
- السطر 12 من الكود XHTML يولد الزر [5]. عند النقر على هذا الزر، يتم تنفيذ كود جافا سكريبت الخاص بالسمة 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: عند النقر على الزر [Raz]، يتم تنفيذ كود جافا سكريبت الخاص بالسمة onclick. يتم تمثيل الصفحة في المتصفح بواسطة شجرة كائنات تُسمى غالبًا DOM (نموذج كائنات المستند). يمكن الوصول إلى كل كائن في الشجرة بواسطة كود جافا سكريبت عبر سمة name الخاصة به. تسمى القائمة الموجودة في السطر 3 من كود HTML أعلاه formulaire:selectManyListBox. يمكن الإشارة إلى النموذج نفسه بعدة طرق. هنا، يُشار إليه بالرمز this.form حيث يشير this إلى الزر [Raz] ويشير this.form إلى النموذج الذي يوجد فيه هذا الزر. توجد القائمة form:selectManyListBox في هذا النموذج نفسه. كما أن الترميز this.form['formulaire:selectManyListBox'] يشير إلى موقع القائمة في شجرة مكونات النموذج. يحتوي الكائن الذي يمثل القائمة على سمة selectedIndex التي تمثل رقم العنصر المحدد في القائمة. يبدأ هذا الرقم من 0 للإشارة إلى العنصر الأول في القائمة. تشير القيمة -1 إلى عدم تحديد أي عنصر في القائمة. يؤدي كود جافا سكريبت الذي يعطي القيمة -1 للسمة selectedIndex إلى إلغاء تحديد جميع عناصر القائمة إن وجدت.
الآن، أدناه، لنختر قيمًا جديدة من القائمة (لتحديد عدة عناصر في القائمة، اضغط باستمرار على مفتاح Ctrl أثناء النقر) ونؤكد النموذج باستخدام الزر [Valider] [2]. نحصل على الصفحة التالية كاستجابة:
![]() |
قيمة الحقل [1] المرسلة هي كما يلي:
أدى التحقق من صحة النموذج بواسطة [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، نرى أن معلمة الطريقة هي مصفوفة من String. هنا ستكون المصفوفة {"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 الذي يتم تنفيذه هو التالي:
<!-- السطر 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] هي التالية:
2.5.17. العلامة <h:selectManyMenu>
العلامة <h:selectManyMenu> مطابقة للعلامة <h:selectManyListBox size="1">. الرمز JSF الذي تم تنفيذه في المثال هو كما يلي:
<!-- السطر 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، أن العنصرين "واحد" و "اثنان" محددان (وجود السمة selected).
من الصعب تقديم لقطة شاشة لمثال على التنفيذ لأنه لا يمكن عرض العناصر المحددة في القائمة. يُطلب من القارئ إجراء الاختبار بنفسه (لتحديد عدة عناصر في القائمة، اضغط باستمرار على مفتاح Ctrl أثناء النقر).
2.5.18. العلامة <h:inputHidden>
لا يوجد تمثيل مرئي لعلامة <h:inputHidden>. فهي تُستخدم فقط لإدراج علامة <input type="hidden" value="..."/> في تدفق الصفحة. تُدرج قيمها داخل علامة <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 في المثال هو التالي:
<!-- السطر 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>
عند إرسال النموذج POST، سيتم إرسال القيمة "الأصلية" للحقل المسمى formulaire:inputHidden في السطر 3 مع القيم الأخرى في النموذج. الحقل
private String inputHidden;
مع هذه القيمة، وهي القيمة التي كان يحتوي عليها في البداية. سيتم تضمين هذه القيمة في الصفحة الجديدة التي يتم إرسالها إلى العميل. وبالتالي، نحصل دائمًا على لقطة الشاشة أعلاه.
القيمة التي تم إرسالها للحقل المخفي هي التالية:
2.5.19. العلامة <h:selectBooleanCheckBox>
تولد العلامة <h:selectBooleanCheckBox> علامة HTML <input type="checkbox" ...>.
لنأخذ الرمز JSF التالي:
<!-- السطر 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] لتحديد المربع أو عدم تحديده. وبما أن الطريقة تجعل القيمة المنطقية 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é(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]. من المثير للاهتمام هنا ملاحظة أن الحقل المخفي javax.faces.ViewState هو الذي سمح لـ JSF بتحديد أن خانة الاختيار التي كانت محددة في البداية قد تم إلغاء تحديدها من قبل المستخدم. في الواقع، لا تشكل قيمة الخانة غير المحددة جزءًا من القيم التي يرسلها المتصفح. بفضل شجرة المكونات المخزنة في الحقل المخفي 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 التالي:
<!-- السطر 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"};
يحدد هذا الجدول:
- عند عرض الصفحة، يتم تحديد المربعات المطلوب تحديدها. ويتم ذلك من خلال قيمتها، c.a.d. حقلها 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، فسيرسل المتصفح قيم المربعات الأربعة للتحديد بالشكل التالي:
ونموذج المربعات الأربعة
private String[] selectManyCheckbox=new String[]{"1","3"};
الجدول {"2","4"}.
لنتحقق من ذلك أدناه. في [1]، نقوم بإجراء التغيير، وفي [2] نقوم بالتحقق من صحة النموذج. وفي [3] النتيجة التي تم الحصول عليها:
![]() |
القيم التي تم إرسالها لحقول [1] هي التالية:
2.5.21. العلامة <h:selectOneRadio>
تقوم العلامة <h:selectOneRadio> بإنشاء مجموعة من أزرار الاختيار المتنافية.
لنأخذ الرمز JSF التالي:
<!-- السطر 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";
يحدد هذا النموذج:
- عند عرض الصفحة، زر الاختيار الوحيد الذي يجب تحديده. ويتم ذلك من خلال قيمته، c.a.d. حقل 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éféré : <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é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="formulaire:selectOneRadio"، أي أن الحقول الأربعة HTML تحمل نفس الاسم. وهذا هو الشرط للحصول على مجموعة من أزرار الاختيار الحصرية: عندما يتم تحديد أحدها، لا يتم تحديد البقية.
أدناه، في [1]، يتم تحديد أحد أزرار الاختيار، وفي [2] يتم إرسال النموذج، وفي [3] النتيجة التي تم الحصول عليها:
![]() |
القيمة التي تم إرسالها للحقل [1] هي التالية:
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">
<!-- اللغات -->
<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">
...
<!-- السطر 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}"/>
<!-- السطر 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}"/>
<!-- السطر 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}"/>
<!-- السطر 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}"/>
<!-- السطر 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"/>
...
<!-- السطر 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}"/>
<!-- السطر 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 ليست هي getter لحقل خاص selectOneListBox1Items،
- فالفئة javax.faces.model.SelectItem لها منشئون متعددون.

نستخدم السطر 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، استنادًا إلى المعلومات المسجلة في قاعدة البيانات. وللقيام بذلك، ستستدعي الطبقة [métier] [2b].
2.7. مثال mv-jsf2-05: التنقل – الجلسة – إدارة الاستثناءات
2.7.1. التطبيق
التطبيق هو نفسه كما في المثال السابق، باستثناء أن النموذج يظهر الآن في شكل مساعد متعدد الصفحات:
![]() |
- في [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]، تم تقسيم الصفحة JSF [index.xhtml] إلى ثلاث صفحات [form1.xhtml, form2.xhtml, form3.xhtml] تم توزيع الإدخالات عليها. الصفحة [form4.xhtml] هي نسخة من الصفحة [index.xhtml] من المشروع السابق. في [2]، تظل الفئة [Form.java] دون تغيير. وستُستخدم كنموذج للصفحات الأربع السابقة JSF،
- في [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">
<!-- روابط -->
<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"/>
<!-- السطر 1 -->
<h:outputText value="inputText" styleClass="info"/>
<h:panelGroup>
<h:outputText value="#{msg['form.loginPrompt']}"/>
<h:inputText id="inputText" value="#{form.inputText}"/>
</h:panelGroup>
<!-- السطر 2 -->
<h:outputText value="inputSecret" styleClass="info"/>
<h:panelGroup>
<h:outputText value="#{msg['form.passwdPrompt']}"/>
<h:inputSecret id="inputSecret" value="#{form.inputSecret}"/>
</h:panelGroup>
<!-- السطر 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>
<!-- روابط -->
<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 الخاصة بها تشير إلى طريقة في bean form مسؤولة عن إرجاع مفتاح التنقل. الطرق المشار إليها في [Form.java] هي التالية:
// الأحداث
public String doAction2(){
return "form2";
}
public String doAction4(){
return "form4";
}
public String doAlea(){
// رقم عشوائي بين 1 و 3
int i=1+(int)(3*Math.random());
// نقوم بإرجاع مفتاح التنقل
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]، يتم إنشاء كائن [Form.java] جديد تمامًا قبل تحديثه من POST إلى [form3.xhtml]. في هذه الحالة، يعود الحقل inputText في النموذج إلى قيمته الافتراضية:
private String inputText="texte";
ويحتفظ بها: في الواقع، في POST من [form3.xhtml]، لا يوجد ما يقوم بتحديث الحقل inputText الذي يشكل جزءًا من نموذج [form1.xhtml] وليس من نموذج [form3.xhtml]،
- يؤدي مفتاح التنقل "form1" إلى عرض [form1.xhtml]. تعرض الصفحة نموذجها. في حالتنا هذه، سيعرض حقل الإدخال login المرتبط بالنموذج inputText القيمة texte وليس القيمة "نص آخر" التي تم إدخالها في [1]. لكي يحتفظ الحقل inputText بالقيمة التي تم إدخالها في [1]، يجب أن تكون مدة صلاحية النموذج [Form.java] هي "الجلسة" وليس "الطلب". في هذه الحالة،
- عند انتهاء POST من [form1.xhtml]، سيتم وضع النموذج في جلسة عمل العميل. سيحتوي الحقل inputText على القيمة "نص آخر"،
- عند تنفيذ POST من [form3.xhtml]، سيتم البحث عن النموذج في هذه الجلسة وتحديثه بواسطة POST من [form3.xhtml]. لن يتم تحديث الحقل inputText بواسطة هذا POST بل سيحتفظ بالقيمة "نص آخر" التي تم الحصول عليها في نهاية POST من [form1.xhtml] [1].
وبالتالي، فإن إعلان bean [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] اعتراض أي استثناء يصل من الطبقة [métier] وتزويد وحدة التحكم [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. على سبيل المثال، إذا طلب متصفح الصفحة P التي لا توجد في سياق التطبيق، فإن هذا لا يؤثر على الرد. إن حاوية السيرفلت هي التي تولد هذا الرد عن طريق إرسال صفحة خطأ افتراضية. يحتوي السطر الأول من تدفق HTTP في الرد على رمز خطأ 404 يشير إلى أن الصفحة P المطلوبة غير موجودة. قد نرغب في إنشاء استجابة تتوافق، على سبيل المثال، مع المخطط التصميمي للتطبيق أو تقدم روابط لحل المشكلة. في هذه الحالة، سنستخدم علامة <error-page> مع علامة <error-code>404</error-code>.
أعلاه، رمز الخطأ 500 HTTP هو الرمز الذي يتم إرجاعه في حالة "تعطل" التطبيق. هذا هو الرمز الذي سيتم إرجاعه إذا تم رفع استثناء إلى حاوية السيرفلت. لذلك، من المحتمل أن تكون علامتا <error-page> في الأسطر 28-35 زائدتين عن الحاجة. وقد تم وضعهما معًا لتوضيح طريقتين لإدارة الخطأ.
2.7.4.2. محاكاة الاستثناء
يتم إنشاء استثناء بشكل مصطنع بواسطة الرابط [Lancer une exception]:
![]() |
![]() |
يؤدي النقر على الرابط [Lancer une exception] [1] إلى عرض الصفحة [2].
في كود الصفحات [formx.xhtml]، يتم إنشاء الرابط [Lancer une exception] بالطريقة التالية:
<!-- روابط -->
<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 التي ستتدخل في معالجة الكائن request، لديها حق الوصول إلى هذين الكائنين. يمكن لكل منها الوصول إلى عناصر الطلب الأولي request، وإعداد الرد من خلال إثراء الكائن response. على سبيل المثال، يمكن لطبقة من localisation تحديد localisation للرد باستخدام الطريقة response.setLocale(Locale l).
يمكن للطبقات المختلفة تبادل المعلومات عبر الكائن request. يحتوي هذا الكائن على قاموس سمات، فارغ عند إنشائه، يمكن إثرائه بواسطة طبقات المعالجة المتتالية. ويمكن لهذه الطبقات وضع المعلومات اللازمة لطبقة المعالجة التالية في سمات الكائن request. توجد طريقتان لإدارة سمات الكائن request:
- void setAttribute(String s, Object o) التي تسمح بإضافة كائن o محدد بالسلسلة s إلى السمات،
- Object getAttribute(String s) التي تسمح بالحصول على السمة o المحددة بالسلسلة s.
عندما تصل استثناء إلى حاوية السيرفلت، تقوم هذه الأخيرة بوضع السمات التالية في الطلب قيد المعالجة:
المفتاح | القيمة |
رمز الخطأ HTTP الذي سيتم إرساله إلى العميل | |
نوع الاستثناء في Java مصحوبًا برسالة الخطأ. | |
URL المطلوب عند حدوث الاستثناء | |
البرنامج الخادم الذي كان يعالج الطلب عند حدوث الاستثناء |
سنستخدم سمات الطلب هذه في الصفحة [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>
<!-- روابط -->
<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. يتم تحويل هذه الصفحة إلى سيرفلت بواسطة حاوية السيرفلت، c.a.d. إلى فئة 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 (نص) سيتم عرضه في متصفح العميل.
عندما نكتب #{expression} في الصفحة JSF، يمكن أن تكون expression مفتاح أحد سمات الكائنات request أو session أو application المذكورة أعلاه. ويتم البحث عن السمة المطابقة بالتتابع في هذه الكائنات الثلاثة. وبالتالي، يتم تقييم #{مفتاح} على النحو التالي:
- request.getAttribute(مفتاح)
- session.getAttribute(مفتاح)
- application.getAttribute(مفتاح)
بمجرد الحصول على قيمة غير null، يتوقف تقييم #{مفتاح}. قد ترغب في أن تكون أكثر دقة من خلال تحديد السياق الذي يجب البحث فيه عن السمة:
- #{requestScope['clé']} للبحث عن السمة في كائن request،
- #{sessionScope['clé']} للبحث عن السمة في كائن session،
- #{applicationScope['clé']} للبحث عن السمة في كائن application.
وهذا ما تم في الصفحة [exception.xhtml] الصفحة 116. والسمات المستخدمة هي التالية:
المفتاح | المجال | القيمة |
الطلب | انظر الفقرة 2.7.4.3. | |
كما سبق | كما سبق | |
كما سبق | نفس الشيء | |
نفس الشيء | نفس الشيء |
تمت إضافة الرسائل المختلفة اللازمة للصفحة 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">
<!-- السطر 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"/>
<!-- السطر 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}"/>
<!-- السطر 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}"/>
<!-- السطر 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}"/>
<!-- السطر 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}"/>
<!-- السطر 6 -->
...
<!-- السطر 7 -->
...
<!-- السطر 8 -->
...
<!-- السطر 9 -->
...
<!-- السطر 10 -->
...
<!-- السطر 11 -->
...
<!-- السطر 12 -->
...
<!-- السطر 13 -->
...
</h:panelGrid>
<!-- أزرار التحكم -->
<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() {
}
// الإدخالات
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 = "";
// الإجراءات
public String submit() {
...
}
public String cancel() {
...
}
// أدوات التحقق من الصحة
public void validateSaisie10(FacesContext context, UIComponent component, Object value) {
...
}
// مُستردات ومُعيّنات
...
}
الجديد هنا هو أن حقول النموذج لم تعد من النوع String فحسب، بل من أنواع مختلفة.
2.8.5. المدخلات المختلفة في النموذج
سنقوم الآن بدراسة الإدخالات المختلفة في النموذج بالتتابع.
2.8.5.1. المدخلات من 1 إلى 4: إدخال عدد صحيح
تعرض الصفحة [index.xhtml] الإدخال 1 بالشكل التالي:
<!-- السطر 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]، نرى أن قيمة الحقل Integer saisie1 في النموذج لم تتغير.
دعونا نوضح ما حدث. للقيام بذلك، لنعد إلى دورة معالجة صفحة 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]، يتم إعادة عرض الصفحة ويتم تنفيذ الكود التالي مرة أخرى:
<!-- السطر 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]:
يحتوي الملف على رسائل مقسمة إلى فئات:
- أخطاء في أحد المكونات، السطر 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.
تتوفر معظم الرسائل المتعلقة بالمكونات في نسختين: نسخة موجزة (summary) ونسخة مفصلة (detail). وهذا هو الحال في الأسطر 16-18:
الرسالة التي تحمل المفتاح _detail (السطر 2) هي الرسالة التي تسمى التفصيلية. والأخرى هي الرسالة التي تسمى الملخصة. تعرض العلامة <h:message> الرسالة التفصيلية بشكل افتراضي. يمكن تغيير هذا السلوك بواسطة السمتين showSummary و showDetail. وهذا ما تم فعله لمكون id saisie2:
<!-- السطر 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]:
# التحويلات
javax.faces.converter.IntegerConverter.INTEGER=erreur
javax.faces.converter.IntegerConverter.INTEGER_detail=erreur d\u00e9taill\u00e9e
فسيصبح الخطأ الذي يتم إرجاعه للمكونات saisie1 و saisie2 كما يلي:

الطريقة الأخرى لتعديل رسالة خطأ التحويل هي استخدام السمة converterMessage للمكون كما هو موضح أدناه للمكون saisie3:
<!-- السطر 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]:
يسمح السمة requiredMessage باستبدال هذه الرسالة الافتراضية. إذا كان الملف [messages.properties] يحتوي على الرسائل التالية:
...
data.required=Vous devez entrer une donnée
integer.required=Vous devez entrer un nombre entier
فسيكون من الممكن الحصول على النتيجة التالية:
![]() |
أو هذه:
![]() |
لا يكفي دائمًا التحقق من أن القيمة المدخلة هي عدد صحيح. ففي بعض الأحيان، يتعين التحقق من أن الرقم المدخل يقع ضمن نطاق معين. وفي هذه الحالة، يتم استخدام أداة التحقق من الصحة. وتُعد القيمة المدخلة رقم 4 مثالاً على ذلك. وفيما يلي كودها في [index.xhtml]:
<!-- السطر 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]:
مرة أخرى، من الممكن استبدال هذه الرسائل بأخرى. يوجد سمة 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]
نحصل على النتيجة التالية:

2.8.5.2. الإدخالان 5 و6: إدخال عدد حقيقي
يخضع إدخال الأعداد الحقيقية لقواعد مشابهة لتلك الخاصة بإدخال الأعداد الصحيحة. الرمز XHTML للمدخلتين 5 و 6 هو التالي:
<!-- السطر 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}"/>
<!-- السطر 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
فيما يلي مثال على التنفيذ:

2.8.5.3. الإدخال 7: إدخال قيمة منطقية
يجب أن يتم إدخال قيمة منطقية عادةً باستخدام مربع اختيار. إذا تم إدخالها باستخدام حقل إدخال، يتم تحويل السلسلة "true" إلى قيمة منطقية true وأي سلسلة أخرى إلى قيمة منطقية false.
الرمز XHTML من المثال:
<!-- السطر 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 التالي:
<!-- السطر 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 (day) إلى رقم اليوم، و MM (Month) إلى رقم الشهر، و yyyy (year) إلى السنة. في [1]، يُستخدم المحول للتحويل العكسي java.lang.String --> java.util.Date. لذلك يجب أن تتبع التاريخ الذي يتم إدخاله النموذج "dd/MM/yyyy" ليكون صالحًا.
توجد رسائل افتراضية للتواريخ غير الصالحة في [javax.faces.Messages.properties]:
والتي يمكن استبدالها برسائلك الخاصة. وهكذا في المثال:
<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
فيما يلي مثال:
![]()
2.8.5.5. الإدخال 9: إدخال سلسلة ذات طول محدد
يوضح الإدخال 9 كيفية فرض عدد أحرف محدد على سلسلة تم إدخالها:
<!-- السطر 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 = "";
توجد رسائل خطأ افتراضية لهذا النوع من التحقق من الصحة:
والتي يمكن استبدالها باستخدام السمة validatorMessage كما في السطر 3 أعلاه. رسالة المفتاح saisie9.error هي كما يلي:
saisie9.error=9-Vous devez entrer une chaîne de 4 caractères exactement
فيما يلي مثال على التنفيذ:
![]()
2.8.5.6. الإدخال 9B: إدخال سلسلة يجب أن تتوافق مع نموذج
يوضح الإدخال 9B كيفية فرض أن يكون عدد أحرف السلسلة المدخلة ضمن نطاق معين:
<!-- السطر 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 أو أكثر من المسافات، رقمان، علامة :، رقمان، سلسلة من 0 أو أكثر من المسافات.
النمط form.saisie9B للمكون saisie9B في السطر 3 هو التالي:
private String saisie9B;
توجد رسائل خطأ افتراضية لهذا النوع من التحقق من الصحة:
والتي يمكن استبدالها باستخدام السمة validatorMessage كما في السطر 3 أعلاه. رسالة المفتاح saisie9.error هي كما يلي:
saisie9B.error=La cha\u00eene saisie ne respecte pas le format hh:mm
فيما يلي مثال على التنفيذ:
![]()
2.8.5.7. الإدخال 10: كتابة طريقة تحقق محددة
باختصار: يتيح JSF التحقق، من بين القيم المدخلة، من صحة الأرقام (الصحيحة والعشرية) والتواريخ وطول السلاسل ومطابقة المدخلات مع تعبير منتظم. يتيح JSF إضافة أدوات التحقق من الصحة والمحولات الخاصة بك إلى أدوات التحقق من الصحة والمحولات الموجودة. لا يتم تناول هذه النقطة هنا، ولكن يمكنك قراءة [ref2] لمزيد من التفاصيل.
نقدم هنا طريقة أخرى: وهي طريقة تتكون من التحقق من صحة البيانات المدخلة بواسطة طريقة من نموذج النموذج. وهذا هو المثال التالي:
<!-- السطر 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 request و HttpServletResponse response،
- UIComponent component: المكون الذي يجب التحقق من صحته. يتم تمثيل العلامة <h:inputText> بمكون من النوع UIInput مشتق من UIComponent. هنا يتم استلام هذا المكون UIInput كمعلمة ثانية،
- قيمة الكائن: القيمة المدخلة المراد التحقق منها، بعد تحويلها إلى نوع نموذجها. من المهم هنا فهم أنه إذا فشل التحويل من 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،
- ملخص السلسلة: النسخة الملخصة لرسالة الخطأ - يتم عرضها بواسطة العلامات <h:message showSummary="true"> و <h:messages>،
- String detail: النسخة التفصيلية لرسالة الخطأ - يتم عرضها بواسطة العلامات <h:message> و <h:messages showDetail="true">.
يمكن استخدام أي من المنشئين، حيث يمكن إصلاح المعلمات المفقودة لاحقًا بواسطة طرق set.
لا يسمح مُنشئ [1] بتحديد رسالة موجودة في ملف رسائل مُعَرَّب. وهذا أمر مؤسف بالطبع. يملأ David Geary و Cay Horstmann هذه الفجوة في كتابهما "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.xm]، صفحة 120)،
- String resourceId: مفتاح الرسالة المطلوب استخدامه في ملف الرسائل. لقد رأينا أن الرسالة يمكن أن تحتوي في الوقت نفسه على نسخة موجزة ونسخة مفصلة. resourceId هو معرف النسخة الموجزة. سيتم البحث عن النسخة التفصيلية تلقائيًا باستخدام المفتاح resourceId_detail. وبالتالي، سيكون لدينا رسالتان في [messages.properties] للخطأ في الإدخال رقم 10:
saisie10.incorrecte=10-Saisie n° 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 التالي:
<!-- السطر 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>.
فيما يلي مثال على التنفيذ:
![]()
2.8.5.8. المدخلات 11 و 12: التحقق من صحة مجموعة من المكونات
حتى الآن، كانت طرق التحقق التي تناولناها تتحقق من مكون واحد فقط. كيف يمكننا القيام بذلك إذا كان التحقق المطلوب يتعلق بعدة مكونات؟ هذا ما سنراه الآن. في النموذج:

نريد أن تكون الإدخالان 11 و 12 رقمين صحيحين يكون مجموعهما 10.
سيكون الرمز JSF كما يلي:
<!-- السطر 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}"/>
<!-- السطر 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] بواسطة زر [Valider] الذي يكون رمزه JSF كما يلي:
<!-- أزرار التحكم -->
<h:panelGrid columns="2">
<h:commandButton value="#{msg['submit']}" action="#{form.submit}"/>
...
</h:panelGrid>
حيث نص الرسالة msg['submit'] هو كما يلي:
submit=Valider
نرى في السطر 3 أن الطريقة form.submit سيتم تنفيذها لمعالجة النقر على الزر [Valider]. وهي كما يلي:
// الإجراءات
public String submit() {
// آخر عمليات التحقق
validateForm();
// إعادة إرسال النموذج نفسه
return null;
}
// التحقق الشامل
private void validateForm() {
if ((saisie11 + saisie12) != 10) {
...
}
من المهم أن نفهم أنه عند تنفيذ طريقة submit:
- يكون قد تم تنفيذ جميع أدوات التحقق من الصحة والمحولات الخاصة بالنموذج بنجاح،
- وقد تلقت حقول النموذج [Form.java] القيم التي أرسلها العميل.
في الواقع، لنعد إلى دورة معالجة POST JSF:
![]() |
الطريقة submit هي معالج أحداث. وهي تدير الحدث clic على الزر [Valider]. مثل جميع معالجات الأحداث، يتم تنفيذها في المرحلة [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">
<!-- السطر 1 -->
...
في السطر 2، تعرض علامة <h:messages> بشكل افتراضي النسخة الملخصة لرسائل الأخطاء الخاصة بجميع الإدخالات الخاطئة لمكونات النموذج بالإضافة إلى جميع رسائل الأخطاء غير المرتبطة بالمكونات. يحدد السمة globalOnly="true" العرض بهذه الأخيرة.
يتم عرض الرسائل [2] و [3] باستخدام علامات بسيطة <h:outputText>:
<!-- السطر 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}"/>
<!-- السطر 13 -->
...
<h:outputText value="#{form.errorSaisie12}" styleClass="error"/>
...
السطور 4-7، يحتوي المكون saisie11 على رسالتين محتملتين للخطأ:
- تلك التي تشير إلى تحويل خاطئ أو عدم وجود بيانات. هذه الرسالة التي تم إنشاؤها بواسطة JSF نفسها ستكون مضمنة في نوع FacesMessage وسيتم عرضها بواسطة العلامة <h:message> في السطر 5،
- والرسالة التي سنقوم بإنشائها إذا لم يكن الإدخال 11 + الإدخال 12 يساوي 10. سيتم عرضها في السطر 6. وستكون رسالة الخطأ مضمنة في النموذج form.errorSaisie11.
تتعلق الرسالتان بأخطاء لا يمكن أن تحدث في نفس الوقت. يتم إجراء التحقق من أن الإدخال 11 + الإدخال 12 = 10 في الأسلوب submit الذي لا يتم تنفيذه إلا إذا لم يتبق أي خطأ في النموذج. عند تنفيذها، سيكون المكون saisie11 قد تم التحقق منه وسيكون نموذجه form.saise11 قد تلقى قيمته. لن يكون من الممكن عرض الرسالة الموجودة في السطر 5. وبالعكس، إذا تم عرض الرسالة الموجودة في السطر 5، فهذا يعني أنه لا يزال هناك خطأ واحد على الأقل في النموذج ولن يتم تنفيذ الأسلوب submit. لن يتم عرض الرسالة الموجودة في السطر 6. لكي تكون رسالتا الخطأ المحتملتان في نفس عمود الجدول، تم تجميعهما في علامة <h:panelGroup> (السطران 4 و7).
الطريقة submit هي كما يلي:
// الإجراءات
public String submit() {
// التحققات الأخيرة
validateForm();
// إعادة إرسال نفس النموذج
return null;
}
// التحقق الشامل
private void validateForm() {
if ((saisie11 + saisie12) != 10) {
// رسالة عامة
FacesMessage message = Messages.getMessage(null, "saisies11et12.incorrectes", null);
message.setSeverity(FacesMessage.SEVERITY_ERROR);
FacesContext context = FacesContext.getCurrentInstance();
context.addMessage(null, message);
// رسائل مرتبطة بالحقول
message = Messages.getMessage(null, "error.sign", null);
setErrorSaisie11(message.getSummary());
setErrorSaisie12(message.getSummary());
} else {
setErrorSaisie11("");
setErrorSaisie12("");
}
}
- السطر 4: تستدعي الطريقة submit الطريقة validateForm لإجراء عمليات التحقق الأخيرة،
- السطر 11: يتم التحقق مما إذا كان saisie11+saisie12=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 التي تدير النقر على الزر [Valider] من التنفيذ. وهو الذي أنتج الرسائل المعروضة في [2] و [3]. نرى أن النموذج قد تم تحديثه حتى مع رفض النموذج وإعادته إلى العميل. قد نرغب في ألا يتم تحديث النموذج في مثل هذه الحالة. في الواقع، إذا افترضنا أن المستخدم ألغى التحديث باستخدام الزر [Annuler] [4]، فلن نتمكن من العودة إلى النموذج الأصلي إلا إذا كنا قد حفظناه.
2.8.5.9. POST لنموذج بدون التحقق من المدخلات
لنأخذ النموذج أعلاه ونفترض أن المستخدم لا يدرك أخطائه ويريد التخلي عن ملء النموذج. سيستخدم عندئذ الزر [Annuler] الذي تم إنشاؤه بواسطة الكود JSF التالي:
<!-- أزرار التحكم -->
<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 المرتبطة بالزر [Annuler] إلا إذا كان النموذج صالحًا. وهذا ما أوضحناه بالنسبة للطريقة form.submit المرتبطة بالزر [Valider]. إذا أراد المستخدم إلغاء إدخال البيانات في النموذج، فمن غير الضروري بالطبع التحقق من صحة إدخالاته. يتم الحصول على هذه النتيجة باستخدام السمة immediate="true" التي توجه JSF لتنفيذ الطريقة form.cancel دون المرور بمرحلة التحقق من الصحة والتحويل. لنعد إلى دورة معالجة POST JSF:
![]() |
يتم معالجة أحداث مكونات الإجراء <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;
}
إذا استخدمنا الزر [Annuler] في النموذج السابق، نحصل في المقابل على الصفحة التالية:
![]() |
- نحصل مرة أخرى على النموذج لأن مدير الأحداث form.cancel يعرض مفتاح التنقل null. وبالتالي يتم إرجاع الصفحة [index.xhtml]،
- وقد تم تعديل النموذج [Form.java] بواسطة الطريقة form.cancel. وينعكس ذلك في العمود [2] الذي يعرض هذا النموذج،
- أما العمود [3]، فيعكس القيمة التي تم نشرها للمكونات.
لنعد إلى الرمز JSF للمكون saisie1 [4]؛
<!-- السطر 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">
<!-- رؤوس -->
<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"/>
<!-- السطر 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}"/>
<!-- السطر 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}"/>
<!-- السطر 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>
<!-- أزرار التحكم -->
<h:panelGrid columns="2" border="0">
<h:commandButton value="#{msg['submit']}"/>
...
</h:panelGrid>
</h:form>
</h:body>
</html>
الجديد يكمن في كود القائمة combo1، الأسطر 24-26. تظهر سمات جديدة:
- onchange: السمة HTML - تعلن عن وظيفة أو كود جافا سكريبت يجب تنفيذه عند تغيير العنصر المحدد في combo1. هنا، يقوم كود جافا سكريبت 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، يتم تعيين نطاق bean على 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() {
}
// حقول النموذج
private String combo1="A";
private String combo2="A1";
private Integer saisie1=0;
// حقول العمل
final private String[] combo1Labels={"A","B","C"};
private String combo1Label="A";
private static final Logger logger=Logger.getLogger("forms.Form");
// الأساليب
public SelectItem[] getCombo1Items(){
// تعيين 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(){
// تعيين combo2 بناءً على 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;
}
// المستمعون
public void combo1ChangeListener(ValueChangeEvent event){
// المتابعة
logger.info("combo1ChangeListener");
// نسترد القيمة المرسلة من combo1
combo1Label=(String)event.getNewValue();
// نقوم بإرجاع الرد لأننا نريد تجاوز عمليات التحقق من الصحة
FacesContext.getCurrentInstance().renderResponse();
}
public String raz(){
// متابعة
logger.info("raz");
// مسح النموذج
combo1Label="A";
combo1="A";
combo2="A1";
saisie1=0;
return null;
}
// مُستردات - مُعيّنات
...
}
لنربط النموذج [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 من كود Java. وهي تولد قائمة من ثلاثة عناصر {"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 من كود جافا. وهي تُنشئ قائمة مكونة من خمسة عناصر {"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. يتيح هذا الكائن الحصول على القيمة القديمة والقيمة الجديدة للمكون الذي تغيرت قيمته باستخدام الطرق التالية:

هنا، المكون هو القائمة combo1 من النوع UISelectOne. قيمته من النوع String.
- السطر 51 من نموذج Java: يتم تخزين القيمة الجديدة لـ combo1 في combo1Label الذي يُستخدم لتوليد عناصر قائمة combo2،
- السطر 53: يتم إرجاع الرد. يجب أن نتذكر هنا أن المدير combo1ChangeListener يتم تنفيذه مع السمة immediate="true". وبالتالي، يتم تنفيذه بعد المرحلة التي تم فيها تحديث شجرة مكونات الصفحة بالقيم المرسلة وقبل عملية التحقق من صحة القيم المرسلة. ولكننا نريد تجنب عملية التحقق هذه لأن القائمة combo2 يجب تحديثها حتى لو بقيت إدخالات خاطئة في النموذج. لذلك نطلب إرسال الرد فوراً دون المرور بمرحلة التحقق من الإدخالات.
- سيتم إعادة إرسال النموذج كما تم إدخاله. ومع ذلك، فإن عناصر القوائم combo1 و combo2 ليست قيمًا تم إدخالها. سيتم إنشاؤها من جديد عن طريق استدعاء الطرق getCombo1Items و getCombo2Items. ستستخدم هذه الطريقة الأخيرة القيمة الجديدة لـ combo1Label التي حددتها combo1ChangeListener وستتغير عناصر القائمة combo2.
2.9.6. الزر [Raz]
نريد باستخدام الزر [Raz] إعادة النموذج إلى حالته الأولية كما هو موضح أدناه:
![]() |
![]() |
![]() |
في [1]، النموذج قبل POST للزر [Raz]، وفي [2] نتيجة POST.
على الرغم من بساطتها من الناحية الوظيفية، إلا أن إدارة حالة الاستخدام هذه تبدو معقدة إلى حد ما. يمكن تجربة حلول مختلفة، لا سيما تلك المستخدمة للزر [Annuler] في المثال السابق:
<h:commandButton value="#{msg['raz']}" immediate="true" action="#{form.raz}"/>
حيث تكون الطريقة form.raz كما يلي:
public String raz(){
// إعادة تعيين النموذج
combo1Label="A";
combo1="A";
combo2="A1";
saisie1=0;
return null;
}
والنتيجة التي تم الحصول عليها بواسطة الزر [Raz] في المثال السابق هي كما يلي:
![]() |
يُظهر العمود [1] أن الطريقة form.raz قد تم تنفيذها. ومع ذلك، يستمر العمود [1] في عرض القيم التي تم نشرها:
- بالنسبة لـ combo1، كانت القيمة التي تم نشرها هي "B". لذلك يتم تحديد هذا العنصر في القائمة،
- بالنسبة لـ combo2، كانت القيمة المنشورة هي "B5". ونتيجة لتنفيذ form.raz، تم تغيير العناصر {"B1"، ...، "B5"} من combo2 إلى {"A1"، ...، "A5"}. لم يعد العنصر "B5" موجودًا، وبالتالي لا يمكن تحديده. عندئذٍ يتم عرض العنصر الأول في القائمة،
- بالنسبة لـ saisie1، كانت القيمة المرسلة هي 10.
هذا هو السلوك الطبيعي عند استخدام السمة immediate="true". للحصول على نتيجة مختلفة، يجب إرسال القيم التي نريد عرضها في النموذج الجديد حتى لو كان المستخدم قد أدخل قيمًا أخرى. يمكن تحقيق ذلك باستخدام القليل من كود جافا سكريبت على جانب العميل. يصبح النموذج كما يلي:
<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['formulaire'].submit();
}
</script>
...
<h:commandButton value="#{msg['raz']}" onclick='raz()' immediate="true" action="#{form.raz}"/>
- السطر 10، يشير السمة onclick='raz()' إلى تنفيذ وظيفة جافا سكريبت raz عندما ينقر المستخدم على الزر [Raz]،
- السطر 3: يتم تعيين القيمة "A" للعنصر HTML الذي يحمل الاسم 'formulaire:combo1'. العناصر المختلفة في السطر 3 هي كما يلي:
- document: الصفحة المعروضة بواسطة المتصفح،
- document.forms: مجموعة النماذج في المستند،
- document.forms['formulaire']: النموذج الذي يحتوي على السمة name="formulaire",
- documents.forms['formulaire'].elements: مجموعة عناصر النموذج التي تحمل السمة name="formulaire",
- document.forms['formulaire'].elements['formulaire:combo1']: عنصر النموذج الذي يحمل السمة name="formulaire:combo1"
- document.forms['formulaire'].elements['formulaire:combo1'].value: القيمة التي سيتم إرسالها بواسطة عنصر النموذج الذي يحمل السمة name="formulaire:combo1".
لمعرفة سمات name للعناصر المختلفة للصفحة المعروضة بواسطة المتصفح، يمكن الرجوع إلى كود المصدر الخاص بها (أدناه مع IE7):
![]() |
<form id="formulaire" name="formulaire" ...>
...
<select id="formulaire:combo1" name="formulaire:combo1" ...>
بعد توضيح ذلك، يمكننا أن نفهم أنه في كود جافا سكريبت الخاص بالوظيفة 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 {
...
// حقول النموذج
private String combo1="A";
private String combo2="A1";
private Integer saisie1=0;
// حقول العمل
final private String[] combo1Labels={"A","B","C"};
private String combo1Label="A";
private static final Logger logger=Logger.getLogger("forms.Form");
// المستمع
public void combo1ChangeListener(ValueChangeEvent event){
// المتابعة
logger.info("combo1ChangeListener");
// نسترد القيمة المرسلة من combo1
combo1Label=(String)event.getNewValue();
// نقوم بإرجاع الرد لأننا نريد تجاوز عمليات التحقق من الصحة
FacesContext.getCurrentInstance().renderResponse();
}
public String raz(){
// متابعة
logger.info("raz");
// مسح النموذج
combo1Label="A";
combo1="A";
combo2="A1";
saisie1=0;
return null;
}
...
}
- السطر 16: يتم إنشاء مولد سجلات. يسمح معلمة getLogger بالتمييز بين مصادر السجلات. هنا، يُسمى مسجل السجلات forms.Form،
- السطر 21: يتم تسجيل المرور في الطريقة combo1ChangeListener،
- السطر 30: يتم تسجيل المرور في الطريقة raz.
ما هي السجلات التي ينتجها الزر [Raz] أو تغيير قيمة combo1؟ لننظر في حالات مختلفة:
- نستخدم الزر [Raz] في حين أن العنصر المحدد في combo1 هو "A". وبالتالي، فإن "A" هي القيمة الأخيرة للمكون combo1. لقد رأينا أن الزر [Raz] كان ينفذ دالة جافا سكريبت ترسل القيمة "A" للمكون combo1. وبالتالي، لا تتغير قيمة المكون combo1. تُظهر السجلات أن الطريقة form.raz هي الوحيدة التي يتم تنفيذها:
- يتم استخدام الزر [Raz] في حين أن العنصر المحدد في combo1 ليس "A". وبالتالي، تتغير قيمة المكون combo1: لم تكن قيمته الأخيرة "A" وسيقوم الزر [Raz] بإرسال القيمة "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<Personne> personnes;
الفئة [Personne] هي كما يلي:
package forms;
public class Personne {
// البيانات
private int id;
private String nom;
private String prénom;
// الشركات المصنعة
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);
}
// المُستخرجون والمُعيّنون
...
}
لنعد إلى محتوى العلامة <h:dataTable>:
<h:dataTable value="#{form.personnes}" var="personne" headerClass="headers" columnClasses="id,nom,prenom">
...
</h:dataTable>
- يحدد السمة var="personne" اسم المتغير الذي يمثل الشخص الحالي داخل العلامة <h:datatable>،
- السمة headerClass="headers" تحدد نمط عناوين أعمدة الجدول،
- السمة columnClasses="...." تحدد نمط كل عمود من أعمدة الجدول.
دعونا نلقي نظرة على أحد أعمدة الجدول ونرى كيف تم إنشاؤه:
![]() |
الرمز XHTML للعمود Id هو التالي:
<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: عمود الاسم الأول للجدول.
الآن، دعونا نلقي نظرة على عمود الروابط [Retirer]:
![]() |
يتم إنشاء هذا العمود بواسطة الكود التالي:
<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>
يتم إنشاء الرابط [Retirer] بواسطة الأسطر 4-6. عند النقر على الرابط، سيتم تنفيذ الطريقة [Form].retirerPersonne. حان الوقت لفحص الفئة [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 {
// نموذج
private List<Personne> personnes;
private int personneId;
// منشئ
public Form() {
// تهيئة قائمة الأشخاص
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() {
// البحث عن الشخص المحدد
int i = 0;
for (Personne personne : personnes) {
// الشخص الحالي = الشخص المحدد؟
if (personne.getId() == personneId) {
// حذف الشخص الحالي من القائمة
personnes.remove(i);
// انتهينا
break;
} else {
// الشخص التالي
i++;
}
}
// يتم الاختبار على نفس الصفحة
return null;
}
// المُستردات والمُعيّنات
...
}
- الأسطر 18-24: يقوم المنشئ بتهيئة قائمة الأشخاص في السطر 14،
- السطر 10: نظرًا لأن هذه القائمة يجب أن تظل موجودة طوال فترة الطلبات، فإن نطاق الفول هو الجلسة.
عند تنفيذ الطريقة [retirerPersonne] في السطر 26، تم تهيئة الحقل في السطر 15 بواسطة معرف الشخص الذي تم النقر على رابطه [Retirer]:
<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] بحذف الشخص الذي يساوي id له personneId.
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 منطقة تسمى المحتوى. وهي منطقة يمكن أن تستقبل محتوى متغيرًا. سنرى كيف. عندما طلبنا الصفحة [layout.xhtml]، لم يتم تعريف أي محتوى للمنطقة المسماة "المحتوى". في هذه الحالة، يتم استخدام محتوى العلامة <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:
![]() |
- في [1]، نستورد مشروع Maven [2] الذي نحدده باستخدام الزر [3]. نأخذ هنا مشروع Maven [mv-jsf2-09] لـ Eclipse
- في [4]، تم التعرف على المشروع المستورد بشكل صحيح كمشروع Maven [5]،
![]() |
- إلى [6]، تم استيراد المشروع إلى مستكشف المشاريع،
- في [7]، يتم تشغيله على خادم [8] Tomcat [9]،
![]() |
- في [10]، تم تشغيل Tomcat 7،
- في [11]، يتم عرض الصفحة الرئيسية للمشروع [mv-jsf2-09] [11] في متصفح داخلي في Eclipse.

























































































































































