10. الإصدار 5 - تطبيق PAM Web / JSF
10.1. بنية التطبيق
ستكون بنية تطبيق الويب PAM كما يلي:
![]() |
في هذا الإصدار، سيستضيف خادم Glassfish جميع طبقات التطبيق:
- يتم استضافة الطبقة [web] بواسطة حاوية السيرفلت الخاصة بالخادم (1 أدناه)
- يتم استضافة الطبقات الأخرى [metier, DAO, jpa] بواسطة حاوية EJB3 الخاصة بالخادم (2 أدناه)
![]() |
تمت كتابة عناصر [metier, DAO] للتطبيق الذي يعمل في الحاوية EJB3 بالفعل في تطبيق العميل/الخادم الذي تمت دراسته في الفقرة 7.1، والذي كانت بنيته كما يلي:
![]() |
كانت الطبقات [metier, DAO] تعمل في الحاوية EJB3 لخادم Glassfish، بينما كانت الطبقة [ui] تعمل في تطبيق وحدة التحكم أو Swing على جهاز آخر:
![]() |
في بنية التطبيق الجديد:
![]() |
يجب كتابة الطبقة [web / jsf] فقط. الطبقات الأخرى [metier, DAO, jpa] مضمونة.
في المستند [ref3]، يظهر أن تطبيق الويب الذي يتم فيه تنفيذ طبقة الويب باستخدام Java Server Faces له بنية مشابهة لما يلي:
![]() |
تنفذ هذه البنية نمط التصميم MVC (النموذج، العرض، وحدة التحكم). تتم معالجة طلب العميل على النحو التالي:
إذا تم تقديم الطلب باستخدام GET، يتم تنفيذ الخطوتين التاليتين:
- الطلب - يقوم متصفح العميل بإرسال طلب إلى وحدة التحكم [Faces Servlet]. تمر جميع طلبات العملاء عبر هذه الوحدة. وهي بمثابة بوابة الدخول إلى التطبيق. وهي تمثل حرف C في MVC.
- الاستجابة - يطلب وحدة التحكم C عرض الصفحة JSF المختارة. هذه هي طريقة العرض، وهي الحرف V في MVC. تستخدم الصفحة JSF نموذج M لتهيئة الأجزاء الديناميكية من الاستجابة التي يجب أن ترسلها إلى العميل. هذا النموذج هو فئة Java يمكنها الاستعانة بطبقة [métier] [4a] لتزويد العرض V بالبيانات التي تحتاجها.
إذا تم إجراء الطلب باستخدام POST، يتم إدراج خطوتين إضافيتين بين الطلب والاستجابة:
- الطلب - يقوم متصفح العميل بإرسال طلب إلى وحدة التحكم [Faces Servlet].
- المعالجة - يقوم وحدة التحكم C بمعالجة هذا الطلب. في الواقع، يرافق الطلب POST بيانات يجب معالجتها. للقيام بذلك، تستعين وحدة التحكم بمديري أحداث خاصين بالتطبيق المكتوب [2a]. قد تحتاج هذه المديرين إلى طبقة الأعمال [2b]. قد يضطر مدير الحدث إلى تحديث بعض نماذج M [2c]. بمجرد معالجة طلب العميل، يمكن أن يستدعي هذا الطلب استجابات متنوعة. ومن الأمثلة النموذجية على ذلك:
- صفحة أخطاء إذا تعذر معالجة الطلب بشكل صحيح
- صفحة تأكيد في الحالات الأخرى
يعيد مدير الحدث إلى وحدة التحكم [Faces Servlet] نتيجة من نوع سلسلة أحرف تسمى مفتاح التنقل.
- التنقل - يختار وحدة التحكم الصفحة JSF (= العرض) لإرسالها إلى العميل. يتم هذا الاختيار بناءً على مفتاح التنقل الذي أعاده مدير الأحداث.
- الاستجابة - سترسل الصفحة المختارة JSF الاستجابة إلى العميل. وهي تستخدم نموذجها M لتهيئة أجزائها الديناميكية. يمكن لهذا النموذج أيضًا الاستعانة بالطبقة [métier] [4a] لتزويد الصفحة JSF بالبيانات التي تحتاجها.
في مشروع JSF:
- وحدة التحكم C هي السيرفلت [javax.faces.webapp.FacesServlet]. وتوجد هذه السيرفلت في المكتبة [jsf-api.jar].
- يتم تنفيذ العروض V بواسطة صفحات JSF.
- يتم تنفيذ النماذج M ومديري الأحداث بواسطة فئات Java تُسمى غالبًا "backing beans".
- في الإصدارين JSF و 1.x، يتم تعريف حبات (beans) وقواعد التنقل بين الصفحات في الملف [faces-config.xml]. ويحتوي هذا الملف على قائمة العروض وقواعد الانتقال من واحدة إلى أخرى. بدءًا من الإصدار JSF 2، يمكن تعريف الحبوب باستخدام التعليقات التوضيحية، ويمكن إجراء الانتقالات بين الصفحات بشكل "ثابت" في كود الحبوب.
10.2. طريقة عمل التطبيق
عند طلب التطبيق لأول مرة، تظهر الصفحة التالية:
![]() |
ثم يتم ملء النموذج ثم طلب الراتب:
![]() |
نحصل على النتيجة التالية:
![]() |
تحسب هذه النسخة راتباً وهمياً. لا يجب الانتباه إلى محتوى الصفحة بل إلى تنسيقها. عند استخدام الزر [Raz]، نعود إلى الصفحة [A].
يتم الإبلاغ عن الإدخالات الخاطئة، كما هو موضح في المثال التالي:
![]() |
10.3. مشروع Netbeans
سنقوم بإنشاء نسخة أولية من التطبيق حيث سيتم محاكاة الطبقة [métier]. سيكون لدينا البنية التالية:
![]() |
عندما يطلب مديرو الأحداث أو النماذج بيانات من الطبقة [métier] [2b, 4a]، ستزودهم هذه الطبقة ببيانات وهمية. الهدف هو الحصول على طبقة ويب تستجيب بشكل صحيح لطلبات المستخدم. وعندما يتحقق ذلك، لن يتبقى لنا سوى تثبيت طبقة الخادم التي تم تطويرها في الفقرة 7.1:
![]() |
وستكون هذه هي النسخة 2 من النسخة الويب لتطبيقنا PAM.
مشروع Netbeans للإصدار 1 هو مشروع Maven التالي:
![]() |
- في [1]، ملفات التكوين
- في [2]، الصفحات XHTML وورقة الأنماط
- في [3]، وفئات الطبقة في [web]
- في [4]، الكائنات المتبادلة بين الطبقة [web] والطبقة [métier] والطبقة [métier] نفسها
- إلى [5]، وملف الرسائل الخاص بتدويل التطبيق
- في [6]، تبعيات التطبيق
سنستعرض بعض هذه العناصر.
10.3.1. ملفات التكوين
الملف [web.xml] هو الملف الذي تم إنشاؤه افتراضيًا بواسطة Netbeans مع تكوين صفحة استثناء إضافية:
- السطر 30: [index.html] هي الصفحة الرئيسية للتطبيق
- الأسطر 32-39: تكوين صفحة الاستثناء
الصفحة [exception.html] مأخوذة من [ref3]. ورمزها هو التالي:
أي استثناء لا يتم التعامل معه بشكل صريح بواسطة كود تطبيق الويب سيؤدي إلى عرض صفحة مشابهة لتلك الموضحة أدناه:
![]() |
سيكون ملف [faces-config.xml] كما يلي:
يجب ملاحظة النقاط التالية:
- الأسطر 9-14: سيتم استخدام الملف [messages.properties] لتدويل الصفحات. وسيكون متاحًا في الصفحات XHTML عبر المفتاح msg.
- السطر 15: يحدد الملف [messages.properties] على أنه يجب استكشافه أولاً بالنسبة لرسائل الخطأ المعروضة بواسطة العلامات <h:messages> و <h:message>. وهذا يسمح بإعادة تعريف بعض رسائل الخطأ الافتراضية في JSF. هذه الإمكانية غير مستخدمة هنا.
10.3.2. ورقة الأنماط
ملف [styles.css] هو التالي:
.libelle{
background-color: #ccffff;
font-family: 'Times New Roman',Times,serif;
font-size: 14px;
font-weight: bold
}
body{
background-color: #ffccff
}
.error{
color: #ff3333
}
.info{
background-color: #99cc00
}
.titreInfos{
background-color: #ffcc00
}
فيما يلي أمثلة على كود JSF الذي يستخدم هذه الأنماط:
| |
| |
| ![]() |
10.3.3. ملف الرسائل
ملف الرسائل [messages_fr.properties] هو التالي:
form.titre=Feuille de salaire
form.comboEmployes.libell\u00e9=Employ\u00e9
form.heuresTravaill\u00e9es.libell\u00e9=Heures travaill\u00e9es
form.joursTravaill\u00e9s.libell\u00e9=Jours travaill\u00e9s
form.heuresTravaill\u00e9es.required=Indiquez le nombre d'heures travaill\u00e9es
form.heuresTravaill\u00e9es.validation=Donn\u00e9e incorrecte
form.joursTravaill\u00e9s.required=Indiquez le nombre de jours travaill\u00e9s
form.joursTravaill\u00e9s.validation=Donn\u00e9e incorrecte
form.btnSalaire.libell\u00e9=Salaire
form.btnRaz.libell\u00e9=Raz
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\u00e9e lors de l'erreur
exception.servletName=Nom de la servlet demand\u00e9e lorsque l'erreur s'est produite
form.infos.employ\u00e9=Informations Employ\u00e9
form.employe.nom=Nom
form.employe.pr\u00e9nom=Pr\u00e9nom
form.employe.adresse=Adresse
form.employe.ville=Ville
form.employe.codePostal=Code postal
form.employe.indice=Indice
form.infos.cotisations=Informations Cotisations sociales
form.cotisations.csgrds=CSGRDS
form.cotisations.csgd=CSGD
form.cotisations.retraite=Retraite
form.cotisations.secu=S\u00e9curit\u00e9 sociale
form.infos.indemnites=Informations Indemnit\u00e9s
form.indemnites.salaireHoraire=Salaire horaire
form.indemnites.entretienJour=Entretien / Jour
form.indemnites.repasJour=Repas / Jour
form.indemnites.cong\u00e9sPay\u00e9s=Cong\u00e9s pay\u00e9s
form.infos.salaire=Informations Salaire
form.salaire.base=Salaire de base
form.salaire.cotisationsSociales=Cotisations sociales
form.salaire.entretien=Indemnit\u00e9s d'entretien
form.salaire.repas=Indemnit\u00e9s de repas
form.salaire.net=Salaire net
يتم استخدام جميع هذه الرسائل في الصفحة [index.xhtml] باستثناء تلك الموجودة في الأسطر 11-15 المستخدمة في الصفحة [exception.xhtml].
10.3.4. نطاق الفاصوليا
سيكون للـ bean [web.forms.Form] نطاق request:
import java.io.Serializable;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;
@ManagedBean
@RequestScoped
public class Form implements Serializable {
سيكون نطاق bean [web.utils.ChangeLocale] هو التطبيق:
package web.utils;
import java.io.Serializable;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;
@ManagedBean
@SessionScoped
public class ChangeLocale implements Serializable{
// la locale des pages
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;
}
public void setLocale(String locale) {
this.locale = locale;
}
}
10.3.5. الطبقة [métier]
تنفذ الطبقة [métier] الواجهة IMetierLocal التالية:
package metier;
import java.util.List;
import javax.ejb.Local;
import jpa.Employe;
@Local
public interface IMetierLocal {
// obtenir la feuille de salaire
FeuilleSalaire calculerFeuilleSalaire(String SS, double nbHeuresTravaillées, int nbJoursTravaillés );
// liste des employés
List<Employe> findAllEmployes();
}
هذه الواجهة هي تلك المستخدمة في الجزء الخاص بالخادم من تطبيق العميل/الخادم الموصوف في الفقرة 7.1.
تقوم الفئة Metier التي سنستخدمها لاختبار الطبقة [web] بتنفيذ هذه الواجهة على النحو التالي:
package metier;
...
public class Metier implements IMetierLocal {
// dictionnaire des employes indexé par le n° SS
private Map<String,Employe> hashEmployes=new HashMap<String,Employe>();
// liste des employés
private List<Employe> listEmployes;
// obtenir la feuille de salaire
public FeuilleSalaire calculerFeuilleSalaire(String SS,
double nbHeuresTravaillées, int nbJoursTravaillés) {
// on récupère l'employé de n° SS
Employe e=hashEmployes.get(SS);
// on rend une feuille de salaire fiictive
return new FeuilleSalaire(e,new Cotisation(3.49,6.15,9.39,7.88),new ElementsSalaire(100,100,100,100,100));
}
// liste des employés
public List<Employe> findAllEmployes() {
if(listEmployes==null){
// on crée une liste de deux employés
listEmployes=new ArrayList<Employe>();
listEmployes.add(new Employe("254104940426058","Jouveinal","Marie","5 rue des oiseaux","St Corentin","49203",new Indemnite(2,2.1,2.1,3.1,15)));
listEmployes.add(new Employe("260124402111742","Laverti","Justine","La brûlerie","St Marcel","49014",new Indemnite(1,1.93,2,3,12)));
// dictionnaire des employes indexé par le n° SS
for(Employe e:listEmployes){
hashEmployes.put(e.getSS(),e);
}
}
// on rend la liste des employés
return listEmployes;
}
}
نترك للقارئ مهمة فك شفرة هذا الكود. تجدر الإشارة إلى الطريقة المستخدمة: حتى لا نضطر إلى تنفيذ الجزء EJB من التطبيق، نقوم بمحاكاة الطبقة [métier]. عندما يتم إعلان الطبقة [web] على أنها صحيحة، يمكننا عندئذ استبدالها بالطبقة الحقيقية [métier].
10.4. النموذج [index.xhtml] ونموذجه [Form.java]
نقوم الآن بإنشاء الصفحة XHTML للنموذج بالإضافة إلى نموذجه.
قراءات موصى بها في [ref3]:
- المثال رقم 3 (mv-jsf2-03) لقائمة العلامات التي يمكن استخدامها في النموذج
- المثال رقم 4 (mv-jsf2-04) لقوائم المنسدلة التي يتم ملؤها بواسطة النموذج
- المثال رقم 6 (mv-jsf2-06) للتحقق من صحة الإدخالات
- المثال رقم 7 (mv-jsf2-07) لإدارة الزر [Raz]
10.4.1. الخطوة 1
السؤال: قم بإنشاء النموذج [index.xhtml] ونموذجه [Form.java] اللازمين للحصول على الصفحة التالية:
![]() |
مكونات الإدخال هي كما يلي:
id | النوع JSF | القالب | الدور | |
comboEmployes | <h:selectOneMenu> | سلسلة comboEmployesValue List<Employe> getEmployes() | تحتوي على قائمة الموظفين بالصيغة "الاسم الأول الاسم الأخير". | |
heuresTravaillees | <h:inputText> | سلسلة heuresTravaillées | عدد ساعات العمل - العدد الفعلي | |
joursTravailles | <h:inputText> | سلسلة joursTravaillés | عدد أيام العمل - عدد صحيح | |
btnSalaire | <h:commandButton> | يبدأ حساب الراتب | ||
btnRaz | <h:commandButton> | إعادة النموذج إلى حالته الأصلية |
- ستعرض الطريقة getEmployes قائمة بالموظفين التي ستحصل عليها من الطبقة [métier]. ستكون الكائنات المعروضة بواسطة القائمة المنسدلة ذات السمة itemValue، ورقم الموظف SS، والسمة itemLabel، وهي سلسلة تتكون من الاسم الأول واسم العائلة للموظف.
- لن يتم ربط الزرين [Salaire] و [Raz] بأي معالجات أحداث في الوقت الحالي.
- سيتم التحقق من صحة الإدخالات.

اختبر هذه النسخة. تأكد بشكل خاص من أن أخطاء الإدخال يتم الإبلاغ عنها بشكل صحيح.
ملاحظة: من المهم ألا تحتوي سمات id لمكونات الصفحة على أحرف مشددة. مع Glassfish 3.1.2، يؤدي ذلك إلى تعطل التطبيق.
10.4.2. الخطوة 2
السؤال: أكمل النموذج [index.xhtml] ونموذجه [Form.java] للحصول على الصفحة التالية بمجرد النقر على الزر [Salaire]:
![]() |
سيتم ربط الزر [Salaire] بمدير الأحداث calculerSalaire الخاص بالنموذج. ستستخدم هذه الطريقة الطريقة calculerFeuilleSalaire من الطبقة [métier]. سيتم إعداد كشف الراتب هذا للموظف المحدد في [1].
في النموذج، سيتم تمثيل كشف الراتب بالحقل الخاص التالي:
private FeuilleSalaire feuilleSalaire;
الذي يتضمن الطريقتين get و set.
للحصول على المعلومات الموجودة في هذا الكائن، يمكن كتابة تعبيرات مثل التالية في الصفحة JSF:
<h:outputText value="#{form.feuilleSalaire.employe.nom}"/>
سيتم تقييم قيمة السمة value على النحو التالي:
[form].getFeuilleSalaire().getEmploye().getNom() حيث يمثل [form] مثيلًا لفئة [Form.java]. يمكن للقارئ التحقق من أن الطرق get المستخدمة هنا موجودة بالفعل في الفئات [Form] و [FeuilleSalaire] و [Employe] على التوالي. وإذا لم يكن الأمر كذلك، فسيتم إصدار استثناء عند تقييم التعبير.
اختبر هذه النسخة الجديدة.
10.4.3. الخطوة 3
السؤال: أكمل النموذج [index.xhtml] ونموذجه [Form.java] للحصول على المعلومات الإضافية التالية:
![]() |
سنتبع نفس الإجراء السابق. هناك صعوبة في رمز العملة الأوروبية الموجود في [1] على سبيل المثال. في إطار تطبيق دولي، من الأفضل الحصول على تنسيق العرض وعلامة العملة المستخدمة في locale (en، de، fr، ...). ويمكن الحصول على ذلك بالطريقة التالية:
<h:outputFormat value="{0,number,currency}">
<f:param value="#{form.feuilleSalaire.employe.indemnite.entretienJour}"/>
</h:outputFormat>
كان من الممكن كتابة:
<h:outputText value="#{form.feuilleSalaire.employe.indemnite.entretienJour} є">
ولكن مع الإعدادات المحلية en_GB (الإنجليزية GB) سيستمر العرض باليورو في حين أنه ينبغي استخدام الجنيه الإسترليني £. تسمح العلامة <h:outputFormat> بعرض المعلومات وفقًا لـ locale للصفحة JSF المعروضة:
- السطر 1: يعرض المعلمة {0} وهي رقم (number) يمثل مبلغًا ماليًا (currency)
- السطر 2: تمنح العلامة <f:param> قيمة للمعلمة {0}. ومنحى العلامة الثانية <f:param> قيمة للمعلمة {1} وهكذا دواليك.
10.4.4. الخطوة 4
قراءات موصى بها: المثال رقم 7 (mv-jsf2-07) في [ref3].
السؤال: أكمل النموذج [index.xhtml] ونموذجه [Form.java] لإدارة الزر [Raz].
يعيد الزر [Raz] النموذج إلى الحالة التي كان عليها عند طلبه لأول مرة بواسطة GET. هناك العديد من الصعوبات هنا. تم شرح بعضها في [ref3].
النموذج الذي يعرضه الزر [Raz] ليس النموذج بأكمله، بل فقط الجزء saisie منه:

يمكن الحصول على هذه النتيجة باستخدام علامة <f:subview> بالطريقة التالية:
<f:subview id="viewInfos" rendered="#{form.viewInfosIsRendered}">
... la partie du formulaire qu'on veut pouvoir ne pas afficher
</f:subview>
تحيط علامة <f:subview> بجميع أجزاء النموذج التي يمكن عرضها أو إخفاؤها. يمكن عرض أو إخفاء أي مكون باستخدام السمة rendered. إذا كانت القيمة rendered="true"، يتم عرض المكون، وإذا كانت القيمة rendered="false"، لا يتم عرضه. إذا أخذت السمة rendered قيمتها في النموذج، فيمكن التحكم في عرض المكون برمجياً.
فيما سبق، سيتم التحكم في عرض طريقة العرض viewInfos باستخدام الحقل التالي:
private boolean viewInfosIsRendered;
مصحوبًا بأساليبه get و set. ستقوم الطرق التي تدير النقرات على الأزرار [Salaire] و [Raz] بتحديث هذا القيمة المنطقية وفقًا لما إذا كان يجب عرض العرض viewInfos أم لا.

















