Skip to content

9. تطبيق المثال 05: rdvmedecins-pfm-ejb

دعونا نذكر هيكل التطبيق المثال 01 JSF / EJB الذي تم تطويره لخادم Glassfish:

لن نغير أي شيء في هذه البنية باستثناء طبقة الويب التي سيتم تنفيذها هنا باستخدام JSF و Primefaces و Primefaces mobile. وسيكون المتصفح المستهدف هو متصفح الهاتف المحمول.

لقد قمنا بتطوير تطبيقين باستخدام Glassfish:

  • التطبيق 01 استخدم JSF / EJB وكان له واجهة بسيطة إلى حد ما،
  • التطبيق 03 كان يستخدم PF / EJB وكان له واجهة غنية.

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

9.1. طرق العرض

لتوضيح الفكرة، نقدم بعض مشاهد التطبيق الذي تم تشغيله في محاكي iPhone 4:

  • في [1]، الصفحة الرئيسية. تجدر الإشارة إلى أنه كان من الضروري هذه المرة ذكر اسم الجهاز (يمكن أيضًا ذكر عنوانه IP)، لأنه مع الجهاز localhost، لم يتم عرض أي شيء،
  • في [2]، قائمة الأطباء،
  • في [3]، التاريخ المطلوب للموعد،
  • في [4]، زر طلب جدول اليوم،
  • في [5]، تعرض طريقة العرض الجديدة الفترات الزمنية المتاحة للطبيب،
  • في [6]، سلسلة من الأزرار للتنقل في التقويم،
  • في [7]، رسالة لتذكير الطبيب واليوم،
  • في [8]، فترة زمنية للحجز. نقوم بذلك،
  • في [9]، عرض اختيار العميل،
  • في [10]، رسالة تذكيرية بالطبيب واليوم والفترة الزمنية الخاصة بالموعد،
  • في [11]، قائمة العملاء،
  • في [12]، زر التأكيد،
  • في [13]، يعيدنا التأكيد إلى التقويم،
  • في [14]، أصبح الموعد المحجوز متاحًا الآن. سنقوم الآن بإلغاء الحجز،
  • في [15]، نبقى في نفس الشاشة،
  • ولكن في [16]، تم حذف الموعد،

من الممكن في الصفحة الرئيسية تغيير اللغة [17]، [18]، [19]:

أخيرًا، تم توفير عرض للأخطاء:

9.2. مشروع Netbeans

مشروع Netbeans هو التالي:

  
  • [mv-rdvmedecins-ejb-dao-jpa]: مشروع EJB للطبقات [DAO] و [JPA] من المثال 01،
  • [mv-rdvmedecins-ejb-metier]: مشروع EJB للطبقة [métier] من المثال 01،
  • [mv-rdvmedecins-pfmobile]: مشروع الطبقة [web] / Primefaces mobile – جديد،
  • [mv-rdvmedecins-pfmobile-app-ear]: مشروع المؤسسة لنشر التطبيق على خادم Glassfish – جديد.

9.3. مشروع المؤسسة

يستخدم مشروع المؤسسة فقط لنشر الوحدات الثلاث [mv-rdvmedecins-ejb-dao-jpa]، [mv-rdvmedecins-ejb-metier]، [mv-rdvmedecins-pfmobile] على خادم Glassfish. مشروع Netbeans هو التالي:

لا يوجد المشروع إلا لهذه التبعيات الثلاث [1] المحددة في الملف [pom.xml] على النحو التالي:


<?xml version="1.0" encoding="UTF-8"?>
<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-rdvmedecins-pfmobile-app-ear</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>ear</packaging>

  <name>mv-rdvmedecins-pfmobile-app-ear</name>

...
    <dependencies>
        <dependency>
            <groupId>${project.groupId}</groupId>
            <artifactId>mv-rdvmedecins-ejb-dao-jpa</artifactId>
            <version>${project.version}</version>
            <type>ejb</type>
        </dependency>
        <dependency>
            <groupId>${project.groupId}</groupId>
            <artifactId>mv-rdvmedecins-ejb-metier</artifactId>
            <version>${project.version}</version>
            <type>ejb</type>
        </dependency>
        <dependency>
            <groupId>${project.groupId}</groupId>
            <artifactId>mv-rdvmedecins-pfmobile</artifactId>
            <version>${project.version}</version>
            <type>war</type>
        </dependency>
    </dependencies>
</project>
  • الأسطر 6-9: عنصر Maven الخاص بمشروع المؤسسة،
  • الأسطر 14-33: التبعيات الثلاثة للمشروع. لاحظ نوع هذه التبعيات (الأسطر 19 و25 و31).

لتشغيل تطبيق الويب، سيتعين تشغيل مشروع المؤسسة هذا.

9.4. مشروع الويب Primefaces mobile

مشروع الويب Primefaces mobile هو التالي:

  • في [1]، صفحات المشروع. الصفحة [index.xhtml] هي الصفحة الوحيدة في المشروع. وهي تتضمن خمسة عروض [vue1.xhtml]، [vue2.xhtml]، [vue3.xhtml]، [vueErreurs.xhtml] و [config.xhtml
  • في [2]، حبوب Java. حبة [Application] ذات نطاق application، وحبة [Form] ذات نطاق session. تغلف الفئة [Erreur] خطأً،
  • في [3]، ملفات الرسائل الخاصة بالتدويل،
  • في [4]، التبعيات. يعتمد مشروع الويب على مشروع EJB من الطبقة [DAO]، ومشروع EJB من الطبقة [métier] و Primefaces mobile للطبقة [web].

9.5. تكوين المشروع

تكوين المشروع هو نفس تكوين مشاريع Primefaces أو JSF التي درسناها. ندرج ملفات التكوين دون إعادة شرحها.

 

[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>
  <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>
  <error-page>
    <error-code>500</error-code>
    <location>/faces/exception.xhtml</location>
  </error-page>
  <error-page>
    <exception-type>Exception</exception-type>
    <location>/faces/exception.xhtml</location>
  </error-page>

</web-app>

يُلاحظ في السطر 26 أن الصفحة [index.xhtml] هي الصفحة الرئيسية للتطبيق.

[faces-config.xml]: تكوين التطبيق JSF


<?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>
    <default-render-kit-id>PRIMEFACES_MOBILE</default-render-kit-id>
  </application>
</faces-config>

[beans.xml]: فارغة ولكنها ضرورية للتعليق @Named


<?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>

[messages_fr.properties]: ملف الرسائل باللغة الفرنسية


# صفحة
page.titre=Les M\u00e9decins Associ\u00e9s
format.date=dd/MM/yyyy
format.date_detail=dd/MM/yyyy

# عرض1
vue1.header=Les M\u00e9decins Associ\u00e9s - R\u00e9servations

# نموذج 1
form1.titre=R\u00e9servations
form1.medecin=M\u00e9decin
form1.jour=Jour (jj/mm/aaaa)
form1.date.requise=La date est n\u00e9cessaire
form1.date.invalide=La date est invalide
form1.date.invalide_detail=La date est invalide
form1.agenda=Agenda
form1.options=Options

# نموذج 2
form2.titre={0} {1} {2}<br/>{3}
form2.titre_detail={0} {1} {2}<br/>{3}
form2.retour=Retour
form2.supprimer=Supprimer
form2.reserver=R\u00e9server
form2.precedent=Jour pr\u00e9c\u00e9dent
form2.suivant=Jour suivant
form2.today=Aujourd'hui

# نموذج 3
form3.client=Client
form3.valider=Valider
form3.titre={0} {1} {2}<br/>{3}<br/>{4,number,#00}h:{5,number,#00}-{6,number,#00}h:{7,number,#00}
form3.titre_detail={0} {1} {2}<br/>{3}<br/>{4,number,#00}h:{5,number,#00}-{6,number,#00}h:{7,number,#00}
form3.retour=Retour

# خطأ
erreur.titre=Une erreur s'est produite.

# التكوين
config.retour=Retour
config.titre=Configuration
config.langue=Langue
config.langue.francais=Fran\u00e7ais
config.langue.anglais=Anglais
config.valider=Valider

#استثناء
exception.titre=Application indisponible. Veuillez recommencer ult\u00e9rieurement.

[messages_en.properties]: ملف الرسائل باللغة الإنجليزية


# صفحة
page.titre=The Associated Doctors
format.date=dd/MM/yyyy
format.date_detail=dd/MM/yyyy

# عرض 1
vue1.header=The Associated Doctors - Reservations

# نموذج 1
form1.titre=Reservations
form1.medecin=Doctor
form1.jour=day (dd/mm/yyyy)
form1.date.requise=The date is necessary
form1.date.invalide=Invalid date
form1.date.invalide_detail=invalid date
form1.agenda=Diary
form1.options=Options

# نموذج 2
form2.titre={0} {1} {2}<br/>{3}
form2.titre_detail={0} {1} {2}<br/>{3}
form2.retour=Back
form2.supprimer=Delete
form2.reserver=Reserve
form2.precedent=Previous Day
form2.suivant=Next Day
form2.today=Today

# نموذج 3
form3.client=Patient
form3.valider=Validate
form3.titre={0} {1} {2}<br/>{3}<br/>{4,number,#00}h:{5,number,#00}-{6,number,#00}h:{7,number,#00}
form3.titre_detail={0} {1} {2}<br/>{3}<br/>{4,number,#00}h:{5,number,#00}-{6,number,#00}h:{7,number,#00}
form3.retour=Back

# خطأ
erreur.titre=Some error happened

# التكوين
config.retour=Back
config.titre=Configuration
config.langue=Language
config.langue.francais=French
config.langue.anglais=English
config.valider=Validate

#استثناء
exception.titre=Application not available. Please try again later.

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

يعرض المشروع دائمًا نفس الصفحة، وهي الصفحة التالية [index.xhtml]:


<f:view xmlns="http://www.w3.org/1999/xhtml"
        xmlns:f="http://java.sun.com/jsf/core"
        xmlns:h="http://java.sun.com/jsf/html"
        xmlns:ui="http://java.sun.com/jsf/facelets"
        xmlns:p="http://primefaces.org/ui"
        xmlns:pm="http://primefaces.org/mobile"
        contentType="text/html"
        locale="#{form.locale}">

  <pm:page title="#{msg['page.titre']}">
    <pm:view id="vue1">
      <ui:fragment rendered="#{form.form1Rendered}">
        <ui:include src="vue1.xhtml"/>
      </ui:fragment>
      <ui:fragment rendered="#{form.erreurInit}">
        <ui:include src="vueErreurs.xhtml"/>
      </ui:fragment>
    </pm:view>
    <pm:view id="vue2">
      <ui:fragment rendered="#{form.form2Rendered}">
        <ui:include src="vue2.xhtml"/>
      </ui:fragment>
    </pm:view>
    <pm:view id="vue3">
      <ui:fragment rendered="#{form.form3Rendered}">
        <ui:include src="vue3.xhtml"/>
      </ui:fragment>
    </pm:view>
    <pm:view id="vueErreurs">
      <ui:fragment rendered="#{form.erreurRendered}">
        <ui:include src="vueErreurs.xhtml"/>
      </ui:fragment>
    </pm:view>
    <pm:view id="config">
      <ui:include src="config.xhtml"/>
    </pm:view>
  </pm:page>    
</f:view>
  • السطر 8: الصفحة مدعمة باللغات (السمة locale
  • السطر 10: تحتوي الصفحة على خمس طرق عرض: vue1 السطر 11، vue2 السطر 19، vue3 السطر 24، vueErreurs السطر 29، config السطر 34. في وقت معين، تكون إحدى طرق العرض هذه هي الوحيدة المرئية. عند بدء تشغيل التطبيق، يتم عرض طريقة العرض vue1. هنا، واجهنا الصعوبة التالية: إذا سارت عملية تهيئة التطبيق بشكل جيد، يجب عرض [vue1.xhtml]، وإلا يجب عرض [vueErreurs.xhtml]. تم حل المشكلة من خلال ضمان إدارة محتوى العرض vue1 بواسطة النموذج الذي يحدد قيم المتغيرات المنطقية [Form].form1rendered (السطر 12) و [Form].erreurInit (السطر 15) لتثبيت محتوى vue1 (السطر 11)،

في المحاكي، العرض [vue1.xhtml] له العرض [1]، والعرض [vue2.xhtml] يظهر كـ [2]، والعرض [vue3.xhtml] يظهر كـ [3]:

العرض [vueErreurs.xhtml] والعرض النهائي [4]، العرض [config.xhtml] والعرض النهائي [5]:

9.7. مكونات المشروع

تمت بالفعل تقديم فئة الحزمة [utils]: فئة [Messages] هي فئة تسهل تدويل رسائل التطبيق. وقد تمت دراستها في الفقرة 2.8.5.7.

9.7.1. البيان Application

البيان [Application.java] هو بيان من نطاق application. نتذكر أن هذا النوع من البيان يستخدم لتخزين البيانات للقراءة فقط والمتاحة لجميع مستخدمي التطبيق. هذا البيان هو التالي:


package beans;

import javax.ejb.EJB;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Named;
import rdvmedecins.metier.service.IMetierLocal;

@Named(value = "application")
@ApplicationScoped
public class Application {

  // طبقة الأعمال
  @EJB
  private IMetierLocal metier;

  public Application() {
  }
  
  // مستقبلات

  public IMetierLocal getMetier() {
    return metier;
  }
  
}
  • السطر 8: نسمي البين "application
  • السطر 9: نطاقه هو application،
  • السطران 13-14: سيتم حقن مرجع على الواجهة المحلية للطبقة [métier] بواسطة الحاوية EJB الخاصة بخادم التطبيق. لنتذكر بنية التطبيق:

سيتم تنفيذ التطبيق PFM و EJB و [Metier] في نفس JVM (آلة Java الافتراضية). لذلك ستستخدم الطبقة [PFM] الواجهة المحلية لـ EJB. هذا كل شيء. لا يحتوي bean [Application] على أي شيء آخر. للوصول إلى الطبقة [métier]، ستبحث البينات الأخرى عنها في هذا البين.

9.7.2. البيان [Erreur]

الفئة [Erreur] هي كما يلي:


package beans;

public class Erreur {
  
  public Erreur() {
  }
  
  // حقل
  private String classe;
  private String message;

  // منشئ
  public Erreur(String classe, String message){
    this.setClasse(classe);
    this.message=message;
  }
  
  // مُستردات ومُعيّنات
...  
}
  • السطر 9، اسم فئة الاستثناء في حالة حدوث استثناء،
  • السطر 10: رسالة خطأ.

9.7.3. البيان [Form]

ورمزه هو كما يلي:


package beans;

...

@Named(value = "form")
@SessionScoped
public class Form implements Serializable {

  public Form() {
  }
  // فول التطبيق
  @Inject
  private Application application;
  private IMetierLocal metier;
  // ذاكرة التخزين المؤقتة للجلسة
  private List<Medecin> medecins;
  private List<Client> clients;
  private Map<Long, Medecin> hMedecins = new HashMap<Long, Medecin>();
  private Map<Long, Client> hClients = new HashMap<Long, Client>();
  // النموذج
  private Long idMedecin;
  private Date jour = new Date();
  private String strJour;
  private Boolean form1Rendered;
  private Boolean form2Rendered;
  private Boolean form3Rendered;
  private Boolean erreurRendered;
  private String form2Titre;
  private String form3Titre;
  private AgendaMedecinJour agendaMedecinJour;
  private Long idCreneauChoisi;
  private Medecin medecin;
  private Long idClient;
  private CreneauMedecinJour creneauChoisi;
  private List<Erreur> erreurs;
  private Boolean erreurInit = false;
  private String action;
  private String locale = "fr";
  private String msgErreurDate = "";
  private SimpleDateFormat dateFormatter;
  private Boolean erreurDate;
  
  @PostConstruct
  private void init() {
    System.out.println("init");
    // لا توجد أخطاء في البداية
    erreurInit = false;
    // تنسيق التواريخ
    dateFormatter = new SimpleDateFormat(Messages.getMessage(null, "format.date", null).getSummary());
    dateFormatter.setLenient(false);
    // اليوم الحالي
    strJour = dateFormatter.format(jour);
    // استرداد طبقة الأعمال
    metier = application.getMetier();
    // نضع الأطباء والعملاء في ذاكرة التخزين المؤقت
    try {
      medecins = metier.getAllMedecins();
      clients = metier.getAllClients();
    } catch (Throwable th) {
      // تسجيل الخطأ
      erreurInit = true;
      prepareVueErreur(th);
      return;
    }
    // التحقق من القوائم
    if (medecins.size() == 0) {
      // تدوين الخطأ
      erreurInit = true;
      erreurs = new ArrayList<Erreur>();
      erreurs.add(new Erreur("", "La liste des médecins est vide"));
    }
    if (clients.size() == 0) {
      // تدوين الخطأ
      erreurInit = true;
      erreurs = new ArrayList<Erreur>();
      erreurs.add(new Erreur("", "La liste des clients est vide"));
    }
    // خطأ؟
    if (erreurInit) {
      // يتم عرض قائمة الأخطاء
      setForms(false, false, false, true);
      return;
    }

    // القواميس
    for (Medecin m : medecins) {
      hMedecins.put(m.getId(), m);
    }
    for (Client c : clients) {
      hClients.put(c.getId(), c);
    }
    // الطريقة 1
    setForms(true, false, false, false);
  }

  // عرض العرض
  private void setForms(Boolean form1Rendered, Boolean form2Rendered, Boolean form3Rendered, Boolean erreurRendered) {
    this.form1Rendered = form1Rendered;
    this.form2Rendered = form2Rendered;
    this.form3Rendered = form3Rendered;
    this.erreurRendered = erreurRendered;
  }

  // التحضير vueErreur
  private void prepareVueErreur(Throwable th) {
    // يتم إنشاء قائمة الأخطاء
    erreurs = new ArrayList<Erreur>();
    erreurs.add(new Erreur(th.getClass().getName(), th.getMessage()));
    while (th.getCause() != null) {
      th = th.getCause();
      erreurs.add(new Erreur(th.getClass().getName(), th.getMessage()));
    }
// يتم عرض عرض الأخطاء
    setForms(false, false, false, true);
  }

  // مُستردات ومُعيّنات
  ..
}
  • الأسطر 5-7: الفئة [Form] هي bean باسم form ونطاق session. نذكر أن الفئة يجب أن تكون قابلة للتسلسل،
  • السطران 12-13: يحتوي bean النموذج على مرجع إلى bean التطبيق. وسيتم حقن هذا المرجع بواسطة حاوية السيرفلت التي يتم فيها تشغيل التطبيق (وجود العلامة @Inject).
  • السطور 24-27: تتحكم في عرض العروض vue1 (السطر 24)، vue2 (السطر 25)، vue3 (السطر 26)، vueErreurs (السطر 27)،
  • الأسطر 43-44: يتم تنفيذ الطريقة init مباشرة بعد إنشاء مثيل للفئة (وجود التعليق التوضيحي @PostConstruct
  • السطران 49-50: يديران تنسيق التواريخ. لا يوفر Primefaces mobile تقويمًا. علاوة على ذلك، لا يمكن استخدام أدوات التحقق JSF في صفحة PFM. لذا سيتعين علينا إدارة إدخال تاريخ التقويم يدويًا،
  • السطر 49: يحدد تنسيق التواريخ. يتم أخذ هذا التنسيق من الملفات الدولية:

format.date=dd/MM/yyyy
format.date_detail=dd/MM/yyyy
  • السطر 50: نحدد أنه لا يجب أن يكون "laxiste" فيما يتعلق بالتواريخ. إذا لم نفعل ذلك، فإن إدخالًا مثل 32/12/2011، وهو إدخال غير صحيح، يُعتبر تاريخًا صالحًا 01/01/2012،
  • السطر 54: يتم استرداد مرجع على الطبقة [métier] من bean [Application
  • السطران 57-58: نطلب من الطبقة [métier] قائمة الأطباء والعملاء،
  • السطور 66-91: إذا سارت الأمور على ما يرام، يتم إنشاء قواميس الأطباء والعملاء. ويتم فهرستها حسب أرقامها. بعد ذلك، سيتم عرض العرض [vue1.xhtml] (السطر 93)،
  • السطر 59: في حالة حدوث خطأ، يتم إنشاء نموذج الصفحة [vueErreurs.xhtml]. هذا النموذج هو قائمة الأخطاء الواردة في السطر 35،
  • الأسطر 105-115: تقوم الطريقة [prepareVueErreur] بإنشاء قائمة الأخطاء المراد عرضها. ثم تعرض الصفحة [index.xhtml] العرض [vueErreurs.xhtml] (السطر 114).

الصفحة [erreur.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:p="http://primefaces.org/ui"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:pm="http://primefaces.org/mobile"
      xmlns:ui="http://java.sun.com/jsf/facelets">

  <!-- عرض الأخطاء -->
  <pm:header title="#{msg['page.titre']}" swatch="b">
    <f:facet name="left">
      <p:button icon="home" value=" " href="#vue1?reverse=true" />
    </f:facet>
  </pm:header>
  <pm:content>
    <div align="center">
      <h1><h:outputText value="#{msg['erreur.titre']}" style="color: blue"/></h1>
    </div>

    <p:dataList value="#{form.erreurs}" var="erreur">
      <b>#{erreur.classe}</b> : <i>#{erreur.message}</i>
    </p:dataList>
  </pm:content>
</html>

وهي تستخدم علامة <p:dataList> (الأسطر 21-23) لعرض قائمة الأخطاء. يتيح الزر الموجود في السطر 13 العودة إلى العرض vue1.

  • يتم إنشاء الزر [1] بواسطة السطر 13. يحدد السمة icon أيقونة الزر. يعيد الزر إلى العرض vue1 (السمة href
  • يتم إنشاء العنوان [2] بواسطة الأسطر 11-15،
  • العنوان [3] تم إنشاؤه بواسطة السطر 18،
  • النص [4] يتم إنشاؤه بواسطة النموذج #{erreur.classe} في السطر 22،
  • يتم إنتاج النص [5] بواسطة النموذج #{erreur.message} في السطر 22.

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

9.8. عرض الصفحة الرئيسية

إذا سارت الأمور على ما يرام، فإن العرض الأول الذي يظهر هو [vue1.xhtml]. وهذا يعطي العرض التالي:

رمز العرض [vue1.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:p="http://primefaces.org/ui"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:pm="http://primefaces.org/mobile"
      xmlns:ui="http://java.sun.com/jsf/facelets">

  <!-- عرض 1 -->
  <pm:header title="#{msg['page.titre']}" swatch="b">
    <f:facet name="left">
      <p:button icon="gear" value=" "  href="#config" />
    </f:facet>
  </pm:header>
  <pm:content>
    <h:form id="form1">
      <div align="center">
        <h1><h:outputText value="#{msg['form1.titre']}" style="color: blue"/></h1>
      </div>
      <pm:field>
        <h:outputLabel value="#{msg['form1.medecin']}" for="choixMedecin"/>
        <h:selectOneMenu id="choixMedecin" value="#{form.idMedecin}">  
          <f:selectItems value="#{form.medecins}" var="medecin" itemLabel="#{medecin.titre} #{medecin.prenom} #{medecin.nom}" itemValue="#{medecin.id}"/>  
        </h:selectOneMenu>              
      </pm:field>
      <pm:field>
        <h:outputLabel value="#{msg['form1.jour']}" for="jour"/>
        <p:inputText id="jour" value="#{form.strJour}"/>
        <ui:fragment rendered="#{form.erreurDate}">
          <p:spacer width="50px"/>
          <h:outputText id="msgErreurDate" value="#{form.msgErreurDate}" style="color: red"/>
        </ui:fragment>
      </pm:field>
      <p:commandButton value="#{msg['form1.agenda']}" update=":form1, :vue2, :vueErreurs" action="#{form.getAgenda}" />
    </h:form>
  </pm:content>
</html>
  • الأسطر 11-15: تنتج العنوان [1]،
  • السطر 13: ينتج الزر [2]. يؤدي النقر عليه إلى عرض طريقة العرض config (السمة href
  • السطر 19: ينتج العنوان [3]،
  • الأسطر 21-26: تنتج قائمة الأطباء المنسدلة [4]،
  • الأسطر 27-34: تنتج إدخال التاريخ [5]. يتم هذا الإدخال باستخدام علامة <p:inputText> بدون أداة التحقق من الصحة. سيتم التحقق من صحة التاريخ على جانب الخادم. إذا كان التاريخ غير صحيح، فسيضع الخادم رسالة خطأ يتم عرضها في الأسطر 30-34،
  • السطر 35: الزر الذي يتحقق من صحة النموذج. يقوم بتحديث ثلاث مناطق: form1 (النموذج في vue1vue2 و vueErreurs. في الواقع، إذا كان التاريخ غير صالح، فيجب تحديث form1. إذا كان التاريخ صحيحًا، فيجب تحديث vue2. وأخيرًا، إذا حدثت استثناء (مثل انقطاع الاتصال بقاعدة البيانات)، فيجب عرض vueErreurs. قد يميل المرء إلى وضع vue1 بدلاً من form1 (يتم تحديث العرض بالكامل). في هذه الحالة، يحدث خطأ في التطبيق.

هذه العرضة مدعومة بالنموذج التالي:


@Named(value = "form")
@SessionScoped
public class Form implements Serializable {

  public Form() {
  }

  // فاصوليا التطبيق
  @Inject
  private Application application;
  private IMetierLocal metier;
  // ذاكرة التخزين المؤقت للجلسة
  private List<Medecin> medecins;
  private List<Client> clients;
  // القالب
  private Long idMedecin;
  private Date jour = new Date();
  private String strJour;
  private Boolean form1Rendered;
  private Boolean form2Rendered;
  private Boolean form3Rendered;
  private Boolean erreurRendered;
  private String msgErreurDate = "";
  private Boolean erreurDate;
  
    // قائمة الأطباء
  public List<Medecin> getMedecins() {
    return medecins;
  }

  // التقويم
  public String getAgenda() {
...
    }
  }
  • يتم تزويد حقل السطر 16 بقراءة وكتابة قيمة القائمة الموجودة في السطر 23 من الصفحة. عند العرض الأولي للصفحة، يتم تحديد القيمة المختارة في القائمة المنسدلة،
  • وتقوم الطريقة الموجودة في الأسطر 27-29 بإنشاء عناصر قائمة الأطباء المنسدلة (السطر 24 من العرض). سيكون لكل خيار تم إنشاؤه تسمية (itemLabel) تتضمن اللقب واسم العائلة والاسم الأول للطبيب، وقيمة (itemValue) تتضمن معرف الطبيب،
  • ويقوم الحقل في السطر 18 بتغذية حقل الإدخال في السطر 29 من الصفحة بالقراءة/الكتابة،
  • السطور 32-34: تدير الطريقة getAgenda النقر على الزر [Agenda] في السطر 35 من الصفحة. ورمزها هو التالي:

// التقويم
  public String getAgenda() {
    try {
      // التحقق من اليوم
      jour = dateFormatter.parse(strJour);
      // لا توجد أخطاء
      erreurDate=false;
      msgErreurDate = "";
      // يتم إنشاء التقويم
      return getAgenda(jour);
    } catch (ParseException ex) {
      // رسالة خطأ
      erreurDate=true;
      msgErreurDate = Messages.getMessage(null, "form1.date.invalide", null).getSummary();
      // عرض 1      
      setForms(true, false, false, false);
      return "pm:vue1";
    }
  }
  • تبدأ الطريقة بالتحقق من صحة التاريخ الذي أدخله المستخدم،
  • السطر 5: التاريخ هو parsée وفقًا لتنسيق التاريخ الذي تم تهيئته بواسطة الطريقة init عند إنشاء مثيل النموذج،
  • السطر 11: في حالة حدوث استثناء، يتم تعيين خطأ (السطر 13)، ويتم إنشاء رسالة خطأ دولية (السطر 14)، ويتم إعداد العرض vue1 (السطر 16) وعرض العرض vue1 (السطر 17)،
  • السطر 10: إذا كان التاريخ صحيحًا، يتم تنفيذ الطريقة التالية:

  // التقويم
  public String getAgenda(Date jour) {
    // لم يتم اختيار أي موعد حتى الآن
    creneauChoisi = null;
    try {
      // يتم استرداد الطبيب المختار
      medecin = hMedecins.get(idMedecin);
      // عنوان النموذج 2
      form2Titre = Messages.getMessage(null, "form2.titre", new Object[]{medecin.getTitre(), medecin.getPrenom(), medecin.getNom(), new SimpleDateFormat("dd MMM yyyy").format(jour)}).getSummary();
      // جدول مواعيد الطبيب ليوم معين
      agendaMedecinJour = metier.getAgendaMedecinJour(medecin, jour);
      // عرض العرض 2
      setForms(false, true, false, false);
      return "pm:vue2";
    } catch (Throwable th) {
      System.out.println(th);
      // عرض الأخطاء
      prepareVueErreur(th);
      return "pm:vueErreurs";
    }
}

نجد هنا رمزًا سبق أن صادفناه عدة مرات. في السطر 9، يتم إنشاء رسالة دولية للعرض vue2:


form2.titre={0} {1} {2}<br/>{3}
form2.titre_detail={0} {1} {2}<br/>{3}

يُلاحظ أننا أدرجنا XHTML في الرسالة. وسيتم عرضها على النحو التالي:

9.9. عرض جدول مواعيد الطبيب

يتم عرض جدول مواعيد الطبيب من خلال العرض [vue2.xhtml]:

رمز العرض [vue2.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:p="http://primefaces.org/ui"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:pm="http://primefaces.org/mobile"
      xmlns:ui="http://java.sun.com/jsf/facelets">

  <!-- العرض 2 -->
  <pm:header title="#{msg['page.titre']}" swatch="b"/>
  <pm:content>
    <h:form id="form2">
      <div align="center">
        <pm:buttonGroup orientation="horizontal">
          <p:commandButton inline="true" icon="back" value=" " action="#{form.showVue1}" update=":vue1"/>
          <p:commandButton inline="true" icon="minus" value=" " action="#{form.getPreviousAgenda}" update=":form2"/>
          <p:commandButton inline="true" icon="home" value=" " action="#{form.getTodayAgenda}" update=":form2"/>
          <p:commandButton inline="true" icon="plus" value=" " action="#{form.getNextAgenda}" update=":form2"/>
        </pm:buttonGroup>
        <h3><h:outputText value="#{form.form2Titre}" style="color: blue" escape="false"/></h3>
      </div>

      <p:dataList id="creneaux" type="inset" value="#{form.agendaMedecinJour.creneauxMedecinJour}" var="creneauMedecinJour">
        <p:column>
          <div align="center">
            <h2>
              <h:outputFormat value="{0,number,#00}h:{1,number,#00} - {2,number,#00}h:{3,number,#00}">
                <f:param value="#{creneauMedecinJour.creneau.hdebut}" />
                <f:param value="#{creneauMedecinJour.creneau.mdebut}" />
                <f:param value="#{creneauMedecinJour.creneau.hfin}" />
                <f:param value="#{creneauMedecinJour.creneau.mfin}" />
              </h:outputFormat>
              <ui:fragment rendered="#{creneauMedecinJour.rv!=null}">
                <br/>
                <h:outputText value="#{creneauMedecinJour.rv.client.titre} #{creneauMedecinJour.rv.client.prenom} #{creneauMedecinJour.rv.client.nom}" style="color: blue"/>
              </ui:fragment>
            </h2>
          </div>
          <div align="center">
            <ui:fragment rendered="#{creneauMedecinJour.rv!=null}">
              <p:commandButton inline="true" action="#{form.action}" value="#{msg['form2.supprimer']}" icon="minus" update=":form2, :vue3, :vueErreurs">
                <f:setPropertyActionListener value="#{creneauMedecinJour.creneau.id}" target="#{form.idCreneauChoisi}"/>
              </p:commandButton>
            </ui:fragment>
            <ui:fragment rendered="#{creneauMedecinJour.rv==null}">
              <p:commandButton inline="true" action="#{form.action}" value="#{msg['form2.reserver']}" icon="plus" update=":form2, :vue3, :vueErreurs">
                <f:setPropertyActionListener value="#{creneauMedecinJour.creneau.id}" target="#{form.idCreneauChoisi}"/>
              </p:commandButton>
            </ui:fragment>
          </div>
        </p:column>
      </p:dataList>
    </h:form>
  </pm:content>
</html>
  • السطر 11: ينتج العنوان [1]،
  • السطور 15-20: تنتج مجموعة الأزرار [2]،
  • السطر 21: ينتج العنوان [3]. تجدر الإشارة إلى قيمة السمة escape. فهي التي تسمح بتفسير الرمز XHTML الذي وضعناه في form2Titre،
  • السطر 24: يتم عرض الفترات الزمنية باستخدام dataList،
  • الأسطر 28-33: تنتج عنوان الفترة الزمنية [4]،
  • الأسطر 34-37: تعرض جزءًا إذا كان هناك موعد في الفترة الزمنية،
  • السطر 36: يعرض هوية العميل الذي حدد الموعد،
  • الأسطر 41-45: تعرض الزر [Supprimer] إذا كان هناك موعد،
  • الأسطر 46-50: تعرض الزر [Réserver] إذا لم يكن هناك موعد.

يتم تغذية هذه العرض بشكل أساسي من النموذج التالي:


private AgendaMedecinJour agendaMedecinJour;

الذي يغذي dataList في السطر 24. تم إنشاء هذا الحقل بواسطة الطريقة getAgenda، عند الانتقال من العرض vue1 إلى العرض vue2.

9.10. حذف موعد

يتم حذف موعد وفقًا للتسلسل التالي:

العرض المعني بهذا الإجراء هو التالي:


<ui:fragment rendered="#{creneauMedecinJour.rv!=null}">
              <p:commandButton inline="true" action="#{form.action}" value="#{msg['form2.supprimer']}" icon="minus" update=":form2, :vueErreurs">
                <f:setPropertyActionListener value="#{creneauMedecinJour.creneau.id}" target="#{form.idCreneauChoisi}"/>
              </p:commandButton>
            </ui:fragment>
  • السطر 2: الزر [Supprimer] مرتبط بالطريقة [Form].action (السمة action
  • السطر 3: سيتم إرسال معرف الفتحة التي تم تحديدها إلى النموذج [Form].idCreneauChoisi،
  • السطر 2: سيقوم استدعاء AJAX بتحديث مناطق form2 (نموذج العرض 2) والعرض vueErreurs. هناك حالتان: إذا سارت الأمور على ما يرام، فسيتم إعادة عرض العرضوإلا فسيتم عرض العرض vueErreurs.

الطريقة [action] هي كما يلي:


  // إجراء على RV
  public String action() {
    // البحث عن الموعد في التقويم
    int i = 0;
    Boolean trouvé = false;
    while (!trouvé && i < agendaMedecinJour.getCreneauxMedecinJour().length) {
      if (agendaMedecinJour.getCreneauxMedecinJour()[i].getCreneau().getId() == idCreneauChoisi) {
        trouvé = true;
      } else {
        i++;
      }
    }
    // هل تم العثور عليه؟
    if (!trouvé) {
      // هذا غريب - نعيد عرض النموذج 2
      setForms(false, true, false, false);
      return "pm:vue2";
    } else {
      creneauChoisi = agendaMedecinJour.getCreneauxMedecinJour()[i];
    }
    // تم العثور عليه
    // حسب الإجراء المطلوب
    if (creneauChoisi.getRv() == null) {
      return reserver();
    } else {
      return supprimer();
    }
  }

  // حجز
  public String reserver() {
 ...
  }

  public String supprimer() {
    try {
      // حذف موعد
      metier.supprimerRv(creneauChoisi.getRv());
      // يتم تحديث الأجندة
      agendaMedecinJour = metier.getAgendaMedecinJour(medecin, jour);
      // عرض النموذج 2
      setForms(false, true, false, false);
      return "pm:vue2";
    } catch (Throwable th) {
      // عرض الأخطاء
      prepareVueErreur(th);
      return "pm:vueErreurs";
    }
}
  • الأسطر 3-12: نبحث عن الفترة الزمنية التي تلقينا معرفها (السطر 7)،
  • إذا لم يتم العثور عليه، وهو أمر غير طبيعي، يتم إعادة عرض «view2» (السطران 16-17)،
  • السطر 19: إذا تم العثور عليه، يتم حفظ الكائن [CreneauMedecinJour] المقابل. وهو الذي يتيح لنا الوصول إلى الموعد المراد حذفه،
  • السطر 26: نقوم بحذفه،
  • السطور 35-48: تعرض طريقة الحذف العرض vue2 إذا تم الحذف بنجاح (السطور 42-43) أو العرض vueErreurs إذا حدثت مشكلة (السطور 46-47).

9.11. تحديد موعد

يتم حجز الموعد وفقًا للتسلسل التالي:

ننتقل من العرض vue2 إلى العرض vue3. الرمز المتعلق بهذا الإجراء هو التالي:


<ui:fragment rendered="#{creneauMedecinJour.rv==null}">
              <p:commandButton inline="true" action="#{form.action}" value="#{msg['form2.reserver']}" icon="plus" update=":vue3, :vueErreurs">
                <f:setPropertyActionListener value="#{creneauMedecinJour.creneau.id}" target="#{form.idCreneauChoisi}"/>
              </p:commandButton>
            </ui:fragment>
  • السطر 2: الزر [Réserver] مرتبط بالطريقة [Form].action (السمة action)، أي نفس الطريقة المستخدمة للزر [Supprimer]. يقوم الاستدعاء AJAX بتحديث العرضين vue3 و vueErreurs حسب وجود أخطاء أم لا أثناء معالجة الاستدعاء.
  • السطر 3: كما هو الحال بالنسبة للزر [Supprimer]، يتم تمرير معرف الفترة الزمنية إلى النموذج.

القالب الذي يعالج هذا الإجراء هو التالي:


// إجراء على RV
  public String action() {
    ...
    // حسب الإجراء المطلوب
    if (creneauChoisi.getRv() == null) {
      return reserver();
    } else {
      return supprimer();
    }
  }

  // الحجز
  public String reserver() {
    try {
      // عنوان النموذج 3
      form3Titre = Messages.getMessage(null, "form3.titre", new Object[]{medecin.getTitre(), medecin.getPrenom(), medecin.getNom(), new SimpleDateFormat("dd MMM yyyy").format(jour),
                creneauChoisi.getCreneau().getHdebut(), creneauChoisi.getCreneau().getMdebut(), creneauChoisi.getCreneau().getHfin(), creneauChoisi.getCreneau().getMfin()}).getSummary();
      // يتم عرض النموذج 3
      setForms(false, false, true, false);
      return "pm:vue3";
    } catch (Throwable th) {
      // عرض الأخطاء
      prepareVueErreur(th);
      return "pm:vueErreurs";
    }
  }
  • الأسطر 2-10: ستحدد الطريقة action المرجع creneauChoisi للكائن [CreneauMedecinJour] الذي يخضع للحجز ثم تستدعي الطريقة reserver،
  • السطر 16: يتم إنشاء رسالة دولية. وهي كما يلي:
form3.titre={0} {1} {2}<br/>{3}<br/>{4,number,#00}h:{5,number,#00}-{6,number,#00}h:{7,number,#00}
form3.titre_detail={0} {1} {2}<br/>{3}<br/>{4,number,#00}h:{5,number,#00}-{6,number,#00}h:{7,number,#00}

سيكون هذا عنوان العرض vue3. كما هو الحال بالنسبة للعرض vue2، يتضمن هذا العنوان XML. كما يتضمن معلمات منسقة لعرض أوقات الفترة الزمنية،

  • السطران 19-20: يتم عرض العرض vue3،
  • السطران 23-24: يتم عرض العرض vueErreurs في حالة مواجهة مشاكل.

9.12. التحقق من صحة موعد

يتم التحقق من صحة الموعد وفقًا للتسلسل التالي:

في [1]، يتم عرض vue3 وفي [2]، يتم عرض vue2 بعد إضافة موعد.

الرمز [vue3.xhtml] لـ vue3 هو التالي:


<?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:p="http://primefaces.org/ui"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:pm="http://primefaces.org/mobile"
      xmlns:ui="http://java.sun.com/jsf/facelets">

  <!-- المنظر 3 -->
  <pm:header title="#{msg['page.titre']}" swatch="b"/>
  <pm:content>
    <h:form id="form3">
      <p:commandButton inline="true" value=" " icon="back" action="#{form.showVue2}" update=":vue2"/>
      <div align="center">
        <h3><h:outputText value="#{form.form3Titre}" style="color: blue" escape="false"/></h3>
      </div>
      <pm:field>
        <h:outputLabel value="#{msg['form3.client']}" for="choixClient"/>
        <h:selectOneMenu id="choixClient" value="#{form.idClient}">
          <f:selectItems value="#{form.clients}" var="client" itemLabel="#{client.titre} #{client.prenom} #{client.nom}" itemValue="#{client.id}"/>
        </h:selectOneMenu>
      </pm:field>
      <div align="center">
        <p:commandButton inline="true" value="#{msg['form3.valider']}" action="#{form.validerRv}" update=":vue2, :vueErreurs" icon="check"/>
      </div>
    </h:form>
  </pm:content>
</html>
  • السطر 16: يُنتج عنوان العرض [3]. تجدر الإشارة إلى قيمة السمة escape التي تسمح بتفسير الأحرف XHTML في العنوان،
  • الأسطر 18-23: تنتج قائمة العملاء المنسدلة [4]،
  • السطر 25: ينتج الزر [Valider] [5]. ترتبط الطريقة [Form].validerRv بهذا الزر:

// التحقق من الموعد
  public String validerRv() {
    try {
      // يتم استرداد مثيل للفترات الزمنية المختارة
      Creneau creneau = metier.getCreneauById(idCreneauChoisi);
      // يتم إضافة الموعد
      metier.ajouterRv(jour, creneau, hClients.get(idClient));
      // يتم تحديث الأجندة
      agendaMedecinJour = metier.getAgendaMedecinJour(medecin, jour);
      // يتم عرض form2
      setForms(false, true, false, false);
      return "pm:vue2";
    } catch (Throwable th) {
      // عرض الأخطاء
      prepareVueErreur(th);
      return "pm:vueErreurs";
    }
  }

تم استخدام هذا الرمز بالفعل في الإصدار 01. سنلاحظ فقط عرض المشاهدات:

  • الطريقة vue2 (السطور 11-12) إذا سارت الأمور على ما يرام،
  • الطريقة vueErreurs (السطران 15-16) في حالة عدم نجاح العملية.

9.13. إلغاء موعد

وهذا يتوافق مع التسلسل التالي:

الزر [1] في العرض [vue3.xhtml] هو التالي:


      <p:commandButton inline="true" value=" " icon="back" action="#{form.showVue2}" update=":vue2"/>

وبالتالي يتم استدعاء الطريقة [Form].showVue2. وهي تكتفي بعرض view2:


  public String showVue2() {
    // عرض 2
    setForms(false, true, false, false);
    return "pm:vue2?reverse=true";
}

9.14. التنقل في التقويم

في vue2، تتيح الأزرار التنقل في التقويم:

اليوم السابق:

اليوم التالي:

اليوم:

لا يظهر في لقطات الشاشة أعلاه، يتم تحديث التقويم ويعرض مواعيد اليوم الجديد المختار.

علامات الأزرار الثلاثة المعنية هي التالية في [vue2.xhtml]:


<pm:buttonGroup orientation="horizontal">
          <p:commandButton inline="true" icon="back" value=" " action="#{form.showVue1}" update=":vue1"/>
          <p:commandButton inline="true" icon="minus" value=" " action="#{form.getPreviousAgenda}" update=":form2"/>
          <p:commandButton inline="true" icon="home" value=" " action="#{form.getTodayAgenda}" update=":form2"/>
          <p:commandButton inline="true" icon="plus" value=" " action="#{form.getNextAgenda}" update=":form2"/>
        </pm:buttonGroup>

الطرق [Form].getPreviousAgenda، [Form].getNextAgenda، [Form].today تمت دراستها في المثال 03.

9.15. تغيير لغة العرض

يتم إدارة تغيير اللغة بواسطة زر في الصفحة الرئيسية:

رمز الزر هو التالي:


      <p:button icon="gear" value=" "  href="#config" />

وبالتالي، فإنه ينقل إلى عرض الإعدادات [2]. عرض [config.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:p="http://primefaces.org/ui"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:pm="http://primefaces.org/mobile"
      xmlns:ui="http://java.sun.com/jsf/facelets">

  <!-- عرض 1 -->
  <pm:header title="#{msg['page.titre']}" swatch="b">
    <f:facet name="left">
      <p:button icon="back" value=" " href="#vue1?reverse=true" />
    </f:facet>
  </pm:header>
  <pm:content>
    <h:form id="frmConfig">
      <div align="center">
        <h3><h:outputText value="#{msg['config.titre']}" style="color: blue"/></h3>
      </div>
      <pm:field>
        <h:outputLabel value="#{msg['config.langue']}" for="langue"/>
        <h:selectOneRadio id="langue" value="#{form.locale}">
          <f:selectItem itemLabel="#{msg['config.langue.francais']}" itemValue="fr"/>
          <f:selectItem itemLabel="#{msg['config.langue.anglais']}" itemValue="en" />
        </h:selectOneRadio>
      </pm:field>
      <p:commandButton value="#{msg['config.valider']}" action="#{form.configurer}" update=":vue1"/>
    </h:form>
  </pm:content>
</html>
  • السطر 11: يعرض [3]،
  • السطر 13: يعرض الزر [4]. يتيح هذا الزر العودة إلى العرض vue1،
  • السطر 17: نموذج العرض،
  • السطر 19: يعرض عنوان العرض [5]،
  • الأسطر 21-27: تعرض أزرار الاختيار. سيتم إرسال قيمة (itemValue) زر الاختيار المحدد إلى النموذج [Form].locale (السمة value في السطر 23)،
  • السطر 28: يعرض الزر [Valider]. يقوم الاستدعاء AJAX بتحديث العرض vue1 (السمة update). الطريقة المستدعاة هي [Form].configurer:

public String configurer(){
    // بعد التكوين - إعادة عرض العرض 1
    redirect();
    return null;
  }
  
  private void redirect() {
    // إعادة توجيه العميل إلى السيرفلت
    ExternalContext ctx = FacesContext.getCurrentInstance().getExternalContext();
    try {
      ctx.redirect(ctx.getRequestContextPath());
    } catch (IOException ex) {
      Logger.getLogger(Form.class.getName()).log(Level.SEVERE, null, ex);
    }
  }

تكتفي الطريقة configurer (السطر 1) بإعادة توجيه متصفح الهاتف المحمول إلى URL الخاص بالتطبيق. وبالتالي، سيتم تحميل الصفحة [index.xhtml]:


<f:view xmlns="http://www.w3.org/1999/xhtml"
        xmlns:f="http://java.sun.com/jsf/core"
        xmlns:h="http://java.sun.com/jsf/html"
        xmlns:ui="http://java.sun.com/jsf/facelets"
        xmlns:p="http://primefaces.org/ui"
        xmlns:pm="http://primefaces.org/mobile"
        contentType="text/html"
        locale="#{form.locale}">

  <pm:page title="#{msg['page.titre']}">
    <pm:view id="vue1">
      ...
    </pm:view>
    ...
  </pm:page>    
</f:view>
  • السطر 8: ستستخدم طريقة العرض اللغة التي تم تغييرها للتو (السمة locale) وستعرض vue1 (السطر 11).

9.16. الخلاصة

لنتذكر بنية التطبيق الذي قمنا بإنشائه للتو:

تطلب الانتقال إلى واجهة للأجهزة المحمولة إعادة كتابة الصفحات XHTML. أما النموذج، فلم يتغير كثيرًا. أما الطبقات السفلية [métier] و [DAO] و [JPA]، فلم تتغير على الإطلاق.

9.17. اختبارات Eclipse

كما فعلنا مع الإصدارات السابقة من التطبيق النموذجي، نعرض كيفية اختبار هذا الإصدار 05 باستخدام Eclipse. أولاً، نقوم باستيراد مشاريع Maven الخاصة بالمثال 05 [1] إلى Eclipse:

  • [mv-rdvmedecins-ejb-dao-jpa]: الطبقات [DAO] و [JPA
  • [mv-rdvmedecins-ejb-metier]: الطبقة [métier
  • [mv-rdvmedecins-pfmobile]: الطبقة [web] التي تم تنفيذها بواسطة Primefaces mobile،
  • [mv-rdvmedecins-pfmobile-app]: الأصل لمشروع المؤسسة [mv-rdvmedecins-pfmobile-app-ear]. عند استيراد المشروع الأصلي، يتم استيراد المشروع الفرعي تلقائيًا،
  • في [2]، يتم تنفيذ مشروع المؤسسة [mv-rdvmedecins-pfmobile-app-ear
  • في [3]، يتم اختيار خادم Glassfish،
  • في [4]، في علامة التبويب [Servers]، تم نشر التطبيق. لا يتم تشغيله تلقائيًا. يجب طلب URL [http://localhost:8080/mv-rdvmedecins-pfmobile/] في متصفح أو محاكي هاتف محمول [5]: