Skip to content

12. الإصدار 7 - تطبيق الويب PAM متعدد العروض / متعدد الصفحات

نعود هنا إلى البنية الأولية حيث تمت محاكاة الطبقة [métier]. نحن نعلم الآن أنه يمكن استبدالها بسهولة بالطبقة الحقيقية [métier]. تسهل الطبقة المحاكاة [métier] إجراء الاختبارات.

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

  • الخدمة الصغيرة [Faces Servlet] هي وحدة التحكم العامة التي يوفرها JSF. يتم توسيع نطاق وحدة التحكم هذه بواسطة معالجات الأحداث الخاصة بالتطبيق. كانت معالجات الأحداث التي تمت مناقشتها حتى الآن عبارة عن طرق للفئات التي تعمل كنماذج للصفحات JSF
  • تقوم صفحات JSF بإرسال الاستجابات إلى متصفح العميل. هذه هي طرق عرض التطبيق.
  • تحتوي الصفحات JSF على عناصر ديناميكية تسمى نموذج الصفحة. يُذكر أن النموذج، بالنسبة لبعض المؤلفين، يشمل الكيانات التي يتعامل معها التطبيق، مثل الفئات FeuilleSalaire أو Employe. ولتمييز هذين النموذجين، يمكننا التحدث عن نموذج التطبيق ونموذج الصفحة JSF.

في بنية JSF، قد يكون الانتقال من صفحة JSFi إلى صفحة JSFj مشكلة.

  • تم عرض الصفحة JSFi. من هذه الصفحة، يقوم المستخدم بتشغيل POST بواسطة حدث ما [1]
  • في JSF، سيتم معالجة هذا POST بشكل عام بواسطة طريقة C من نموذج Mi للصفحة JSFi. يمكن القول أن الطريقة C هي وحدة تحكم ثانوية.
  • إذا كان يجب عرض الصفحة JSFj في نهاية هذه الطريقة، فيجب على وحدة التحكم C:
  1. تحديث [2c] النموذج Mj للصفحة JSFj
  2. إرجاع [2a] إلى وحدة التحكم الرئيسية، وهو مفتاح التنقل الذي سيسمح بعرض الصفحة JSFj

تتطلب الخطوة 1 أن يكون للنموذج Mi للصفحة JSFi مرجع إلى النموذج Mj للصفحة JSFj. وهذا يعقد الأمور قليلاً، حيث يجعل النماذج Mi تعتمد على بعضها البعض. في الواقع، يجب أن يعرف مدير C للنموذج Mi الذي يقوم بتحديث النموذج Mj هذا الأخير. إذا اضطررنا إلى تغيير النموذج Mj، فسنضطر عندئذٍ إلى تغيير مدير C الخاص بالنموذج Mi.

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

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

12.1. طرق عرض التطبيق

ستكون العروض المختلفة المعروضة للمستخدم كما يلي:

  • عرض [VueSaisies] الذي يعرض نموذج المحاكاة

  • عرض [VueSimulation] المستخدم لعرض النتيجة التفصيلية للمحاكاة:

Image

Image

  • الطريقة [VueSimulations] التي تعرض قائمة بالمحاكاة التي أجراها العميل

Image

  • عرض [VueSimulationsVides] الذي يشير إلى أن العميل ليس لديه محاكاة أو لم يعد لديه محاكاة:

Image

  • عرض [VueErreur] الذي يشير إلى وجود خطأ واحد أو أكثر:

Image

12.2. مشروع Netbeans للطبقة [web]

سيكون مشروع Netbeans لهذه النسخة هو مشروع Maven التالي:

  • في [1]، ملفات التكوين،
  • في [2]، الصفحات JSF،
  • في [3]، ورقة الأنماط وصورة الخلفية للعروض،
  • في [4]، فئات الطبقة [web]،
  • في [5]، الطبقات السفلية للتطبيق،
  • في [6]، ملف الرسائل لتدويل التطبيق،
  • في [7]، تبعيات المشروع.

سنستعرض بعض هذه العناصر.

12.2.1. ملفات التكوين

ملفات [web.xml] و [faces-config.xml] مطابقة لتلك الموجودة في المشروع السابق باستثناء تفصيل واحد في [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">
  ...
  <welcome-file-list>
    <welcome-file>faces/saisie.xhtml</welcome-file>
  </welcome-file-list>
  ...
</web-app>
  • الأسطر 4-6: الصفحة الرئيسية هي الصفحة [saisie.xhtml]

12.2.2. ورقة الأنماط

ملف [styles.css] هو التالي:


.simulationsHeader {
   text-align: center;
   font-style: italic;
   color: Snow;
   background: Teal;
}

.simuNum {
   height: 25px;
   text-align: center;
   background: MediumTurquoise;
}
.simuNom {
   text-align: left;
   background: PowderBlue;
}
.simuPrenom {
   width: 6em;
   text-align: left;
   color: Black;
   background: MediumTurquoise;
}
.simuHT {
   width: 3em;
   text-align: center;
   color: Black;
   background: PowderBlue;
}
.simuJT {
   width: 3em;
   text-align: center;
   color: Black;
   background: MediumTurquoise;
}
.simuSalaireBase {
   width: 9em;
   text-align: center;
   color: Black;
   background: PowderBlue;
}
.simuIndemnites {
   width: 3em;
   text-align: center;
   color: Black;
   background: MediumTurquoise;
}
.simuCotisationsSociales {
   width: 6em;
   text-align: center;
   background: PowderBlue;
}

.simuSalaireNet {
   width: 10em;
   text-align: center;
   background: MediumTurquoise;
}

.erreursHeaders {
   background: Teal;
   background-color: #ff6633;
   color: Snow;
   font-style: italic;
   text-align: center

}

.erreurClasse {
   background: MediumTurquoise;
   background-color: #ffcc66;
   height: 25px;
   text-align: center
}

.erreurMessage {
   background: PowderBlue;
   background-color: #ffcc99;
   text-align: left
}

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

عرض المحاكاة


          <h:dataTable value="#{form.simulations}" var="simulation"
                       headerClass="simulationsHeaders" columnClasses="simuNum,simuNom,simuPrenom,simuHT,simuJT,simuSalaireBase,simuIndemnites,simuCotisationsSociales,simuSalaireNet">

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

Image

عرض الخطأ


          <h:dataTable value="#{form.erreurs}" var="erreur"
                                 headerClass="erreursHeaders" columnClasses="erreurClasse,erreurMessage">

Image

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

ملف الرسائل [messages_fr.properties] هو التالي:


form.titre=Simulateur de calcul de paie
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
form.menu.faireSimulation=| Faire la simulation
form.menu.effacerSimulation=| Effacer la simulation
form.menu.enregistrerSimulation=| Enregistrer la simulation
form.menu.retourSimulateur=| Retour au simulateur
form.menu.voirSimulations=| Voir les simulations
form.menu.terminerSession=| Terminer la session
simulations.headers.nom=Nom
simulations.headers.nom=Nom
simulations.headers.prenom=Pr\u00e9nom
simulations.headers.heuresTravaillees=Heures travaill\u00e9es
simulations.headers.joursTravailles=Jours Travaill\u00e9s
simulations.headers.salaireBase=Salaire de base
simulations.headers.indemnites=Indemnit\u00e9s
simulations.headers.cotisationsSociales=Cotisations sociales
simulations.headers.salaireNet=SalaireNet
simulations.headers.numero=N\u00b0
erreur.titre=Une erreur s'est produite.
erreur.exceptions=Cha\u00eene des exceptions
exception.type=Type de l'exception
exception.message=Message associ\u00e9

12.2.4. الطبقة [métier]

يتم تعديل الطبقة [métier] المحاكاة على النحو التالي:


package metier;

...
public class Metier implements ImetierLocal, Serializable {
  
  // liste des employes
  private Map<String,Employe> hashEmployes=new HashMap<String,Employe>();
  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é
    Employe e=hashEmployes.get(SS);
    // on rend une exception si l'employé n'existe pas
    if(e==null){
      throw new PamException(String.format("L'employé de n° SS [%s] n'existe pas",SS),1);
    }
    // on rend une feuille de salaire fictive
    return new FeuilleSalaire(e,new Cotisation(3.49,6.15,9.39,7.88),e.getIndemnite(),new ElementsSalaire(100,100,100,100,100));
  }
  
  // liste des employés
  public List<Employe> findAllEmployes() {
    if(listEmployes==null){
      // on crée une liste de trois 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
      for(Employe e:listEmployes){
        hashEmployes.put(e.getSS(),e);
      }
      // on ajoute un employé qui n'existera pas dans le dictionnaire
      listEmployes.add(new Employe("X","Y","Z","La brûlerie","St Marcel","49014",new Indemnite(1,1.93,2,3,12)));
    }
    // on rend la liste des employés
    return listEmployes;
  }
}
  • السطر 8: قائمة الموظفين
  • السطر 7: نفس القائمة في شكل قاموس مفهرس برقم الموظفين SS
  • الأسطر 24-39: الطريقة findAllEmployes التي تعرض قائمة الموظفين. تنشئ هذه الطريقة قائمة ثابتة وتشير إليها عبر الحقل employés في السطر 8.
  • الأسطر 27-33: يتم إنشاء قائمة وقاموس لموظفين اثنين
  • السطر 35: يتم إضافة موظف إلى قائمة الموظفين (السطر 8) ولكن ليس إلى القاموس hashEmployes (السطر 7). وذلك لكي يظهر في قائمة الموظفين المنسدلة ولكن لا يتم التعرف عليه بعد ذلك بواسطة الطريقة calculerFeuilleSalaire (السطر 14) حتى تقوم هذه الطريقة بإطلاق استثناء (السطر 17).
  • السطور 11-21: الطريقة calculerFeuilleSalaire
  • السطر 14: يتم البحث عن الموظف في القاموس hashEmployes عبر رقمه SS. إذا لم يتم العثور عليه، يتم إصدار استثناء (الأسطر 16-18). وبذلك سيكون لدينا استثناء للموظف رقم SS X الذي تمت إضافته في السطر 35 إلى قائمة الموظفين ولكن لم يتم إضافته إلى القاموس hashEmployes.
  • في السطر 20، يتم إنشاء كشف راتب وهمي وعرضه.

12.3. حبوب التطبيق

سيكون هناك ثلاث حبوب ذات نطاقات مختلفة:

  

12.3.1. حبة ApplicationData

سيكون نطاق bean ApplicationData هو application:


package web.beans.application;

import java.io.Serializable;
import java.util.logging.Logger;
import javax.annotation.PostConstruct;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Named;
import metier.IMetierLocal;
import metier.Metier;

@Named
@ApplicationScoped
public class ApplicationData implements Serializable {

  // couche métier
  private IMetierLocal metier = new Metier();
  // logger
  private boolean logsEnabled = true;
  private final Logger logger = Logger.getLogger("pam");

  public ApplicationData() {
  }

  @PostConstruct
  public void init() {
    // log
    if (isLogsEnabled()) {
      logger.info("ApplicationData");
    }
  }

  // getters et setters
...
}
  • السطر 11: التعليق التوضيحي @Named يجعل الفئة bean مُدارًا. تجدر الإشارة إلى أنه على عكس المشروع السابق، لم يتم استخدام التعليق التوضيحي @ManagedBean. والسبب في ذلك هو أن مرجع هذه الفئة يجب أن يتم حقنه في فئة أخرى باستخدام التعليق التوضيحي @Inject، وأن هذا الأخير لا يحقن سوى الفئات التي تحمل التعليق التوضيحي @Named.
  • السطر 12: تعمل العلامة @ApplicationScoped على تحويل الفئة إلى كائن ذي نطاق تطبيق. تجدر الإشارة إلى أن فئة العلامة هي [javax.enterprise.context.ApplicationScoped] (السطر 6) وليست [javax.faces.bean.ApplicationScoped] كما هو الحال في البينات في المشروع السابق.

يستخدم bean ApplicationData لغرضين:

  • السطر 16: الاحتفاظ بمرجع على الطبقة [métier
  • السطران 18-19: تعريف مسجل سجلات يمكن استخدامه من قبل الحبوب الأخرى لتسجيل السجلات على وحدة التحكم في Glassfish.

12.3.2. البيان SessionData

سيكون نطاق عمل bean SessionData هو الجلسة:


package web.beans.session;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.PostConstruct;
import javax.enterprise.context.SessionScoped;
import javax.inject.Inject;
import javax.inject.Named;
import web.beans.application.ApplicationData;
import web.entities.Simulation;

@Named
@SessionScoped
public class SessionData implements Serializable {

  // application
  @Inject
  private ApplicationData applicationData;
  // simulations
  private List<Simulation> simulations = new ArrayList<Simulation>();
  private int numDerniereSimulation = 0;
  private Simulation simulation;
  // menus
  private boolean menuFaireSimulationIsRendered = true;
  private boolean menuEffacerSimulationIsRendered = true;
  private boolean menuEnregistrerSimulationIsRendered;
  private boolean menuVoirSimulationsIsRendered;
  private boolean menuRetourSimulateurIsRendered;
  private boolean menuTerminerSessionIsRendered = true;
  // locale
  private String locale="fr_FR";
  
  // constructeur
  public SessionData() {
  }

  @PostConstruct
  public void init() {
    // log
    if (applicationData.isLogsEnabled()) {
      applicationData.getLogger().info("SessionData");
    }
  }

  // gestion des menus
  public void setMenu(boolean menuFaireSimulationIsRendered, boolean menuEnregistrerSimulationIsRendered, boolean menuEffacerSimulationIsRendered, boolean menuVoirSimulationsIsRendered, boolean menuRetourSimulateurIsRendered, boolean menuTerminerSessionIsRendered) {
    this.setMenuFaireSimulationIsRendered(menuFaireSimulationIsRendered);
    this.setMenuEnregistrerSimulationIsRendered(menuEnregistrerSimulationIsRendered);
    this.setMenuVoirSimulationsIsRendered(menuVoirSimulationsIsRendered);
    this.setMenuEffacerSimulationIsRendered(menuEffacerSimulationIsRendered);
    this.setMenuRetourSimulateurIsRendered(menuRetourSimulateurIsRendered);
    this.setMenuTerminerSessionIsRendered(menuTerminerSessionIsRendered);
  }

  // getters et setters
  ...
}
  • السطر 13: الفئة SessionData هي bean مُدار (@Named) يمكن حقنه في beans مُدارة أخرى،
  • السطر 14: نطاقه هو الجلسة (@SessionScoped
  • السطران 18-19: يتم حقن مرجع إلى bean ApplicationData فيه (@Inject
  • الأسطر 21-32: بيانات التطبيق التي يجب الحفاظ عليها عبر الجلسات.
  • السطر 21: قائمة المحاكاة التي أجراها المستخدم،
  • السطر 22: رقم آخر محاكاة تم تسجيلها،
  • السطر 23: آخر محاكاة تم إجراؤها،
  • الأسطر 25-30: خيارات القائمة،
  • السطر 32: الإعدادات المحلية للتطبيق.

الأسطر 39-44، يتم تنفيذ الطريقة init بعد إنشاء مثيل للفئة (@PostConstruct). هنا، يتم استخدامها فقط لترك أثر لتنفيذها. يجب أن نتمكن من التحقق من أنها لا تُنفَّذ سوى مرة واحدة لكل مستخدم لأن نطاق الفئة هو نطاق الجلسة. السطر 42، تستخدم الطريقة أداة التسجيل المحددة في الفئة ApplicationData. ولهذا السبب كان علينا حقن مرجع إلى هذا البين (السطور 18-19).

12.3.3. البيان Form

نطاق bean Form هو requête:


package web.beans.request;

import java.util.ArrayList;
import java.util.List;
import javax.enterprise.context.RequestScoped;
import javax.faces.context.FacesContext;
import javax.inject.Inject;
import javax.inject.Named;
import javax.servlet.http.HttpServletRequest;
import jpa.Employe;
import metier.FeuilleSalaire;
import web.beans.application.ApplicationData;
import web.beans.session.*;
import web.entities.Erreur;
import web.entities.Simulation;

@Named
@RequestScoped
public class Form {

  public Form() {
  }
  // autres beans
  @Inject
  private ApplicationData applicationData;
  @Inject
  private SessionData sessionData;
  // le modèle des vues
  private String comboEmployesValue = "";
  private String heuresTravaillées = "";
  private String joursTravaillés = "";
  private Integer numSimulationToDelete;
  private List<Erreur> erreurs = new ArrayList<Erreur>();
  private FeuilleSalaire feuilleSalaire;

  
  // liste des employés
  public List<Employe> getEmployes(){
    return applicationData.getMetier().findAllEmployes();
  }
  
  // action du menu
  public String faireSimulation() {
    ...
  }

  public String enregistrerSimulation() {
  ...
  }

  public String effacerSimulation() {
  ...
  }

  public String voirSimulations() {
  ...
  }

  public String retourSimulateur() {
   ...
  }

  public String terminerSession() {
  ...
  }

  public String retirerSimulation() {
 ...
  }

  private void razFormulaire() {
 ...
  }

// getters et setters
...
}
  • السطر 17، الفئة هي bean مُدار (@Named
  • السطر 18، نطاق الطلب (@RequestScoped
  • السطران 24-25: حقن مرجع على bean ذي نطاق التطبيق ApplicationData،
  • السطران 26-27: حقن مرجع إلى bean ذي نطاق الجلسة SessionData.

12.4. صفحات التطبيق

  

12.4.1. [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">

  <f:view locale="#{sessionData.locale}">
    <h:head>
      <title><h:outputText value="#{msg['form.titre']}"/></title>
      <h:outputStylesheet library="css" name="styles.css"/>
    </h:head>
    <script type="text/javascript">
      function raz(){
        // on change les valeurs postées
        document.forms['formulaire'].elements['formulaire:comboEmployes'].value="0";
        document.forms['formulaire'].elements['formulaire:heuresTravaillees'].value="0";
        document.forms['formulaire'].elements['formulaire:joursTravailles'].value="0";
      }
    </script>
    <h:body style="background-image: url('${request.contextPath}/resources/images/standard.jpg');">
      <h:form id="formulaire">
        <!-- entete -->
        <ui:include src="entete.xhtml" />
        <!-- contenu -->
        <ui:insert name="part1" >
          Gestion des assistantes maternelles
        </ui:insert>
        <ui:insert name="part2"/>
      </h:form>
    </h:body>
  </f:view>
</html>

تتكون كل عرض من العناصر التالية:

  • رأس يتم عرضه بواسطة الجزء [entete.xhtml] (السطر 24)،
  • نص رئيسي يتكون من جزأين يسميان part1 (الأسطر 26-28) و part2 (السطر 29)،
  • السطر 8: يضمن السمة locale تدويل الصفحات. هنا سيكون هناك واحد فقط: fr_FR،
  • الأسطر 14-19: كود جافا سكريبت.

عرض الصفحة [layout.xhtml] هو كما يلي:

  • في [1]، العنوان [entete.xhtml
  • في [2]، الجزء part1.

12.4.2. العنوان [entete.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"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:ui="http://java.sun.com/jsf/facelets">

  <ui:composition>
    <!-- entete -->
    <h:panelGrid columns="2">
      <h:panelGroup>
        <h2><h:outputText value="#{msg['form.titre']}"/></h2>
      </h:panelGroup>
      <h:panelGroup>
        <h:panelGrid columns="1">
          <h:commandLink id="cmdFaireSimulation" value="#{msg['form.menu.faireSimulation']}" action="#{form.faireSimulation}" rendered="#{sessionData.menuFaireSimulationIsRendered}"/>
          <h:commandLink id="cmdEffacerSimulation"  onclick="raz()" value="#{msg['form.menu.effacerSimulation']}" action="#{form.effacerSimulation}" rendered="#{sessionData.menuEffacerSimulationIsRendered}"/>
          <h:commandLink id="cmdEnregistrerSimulation" immediate="true" value="#{msg['form.menu.enregistrerSimulation']}" action="#{form.enregistrerSimulation}" rendered="#{sessionData.menuEnregistrerSimulationIsRendered}"/>
          <h:commandLink id="cmdVoirSimulations" immediate="true" value="#{msg['form.menu.voirSimulations']}" action="#{form.voirSimulations}" rendered="#{sessionData.menuVoirSimulationsIsRendered}"/>
          <h:commandLink id="cmdRetourSimulateur" immediate="true" value="#{msg['form.menu.retourSimulateur']}" action="#{form.retourSimulateur}" rendered="#{sessionData.menuRetourSimulateurIsRendered}"/>
          <h:commandLink id="cmdTerminerSession" immediate="true" value="#{msg['form.menu.terminerSession']}" action="#{form.terminerSession}" rendered="#{sessionData.menuTerminerSessionIsRendered}"/>
        </h:panelGrid>
      </h:panelGroup>
    </h:panelGrid>
    <hr/>
  </ui:composition>
</html>
  • السطر 12: عنوان التطبيق،
  • الأسطر 16-21: الروابط الستة المطابقة للإجراءات الستة التي يمكن للمستخدم القيام بها. يتم التحكم في هذه الروابط (السمة rendered) بواسطة قيم منطقية من bean SessionData،
  • السطر 17: يؤدي النقر على الرابط [Effacer la simulation] إلى تنفيذ دالة جافا سكريبت raz. وقد تم تعريف هذه الدالة في النموذج [layout.xhtml
  • السطر 18: السمة immediate=true تجعل صحة البيانات لا يتم التحقق منها قبل تنفيذ الطريقة [Form].enregistrerSimulation. وهذا مقصود. قد نرغب في حفظ آخر محاكاة تم إجراؤها قبل دقيقة واحدة حتى لو كانت البيانات التي تم إدخالها منذ ذلك الحين في النموذج (ولكن لم يتم التحقق من صحتها بعد) غير صالحة. وينطبق الأمر نفسه على الإجراءات [Voir les simulations] و [Effacer la simulation] و [Retour au simulateur] و [Terminer la session].

12.5. حالات استخدام التطبيق

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

الصفحة الرئيسية هي الصفحة التالية [saisie.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="part1">
      <ui:include src="saisie2.xhtml"/>
    </ui:define>
  </ui:composition>
</html>
  • السطر 8: يتم عرضها داخل الصفحة [layout.xhtml
  • السطر 9: بدلاً من المقطع المسمى part1. في هذا المقطع، يتم عرض الصفحة [saisie2.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:panelGrid columns="3">
    <h:outputText value="#{msg['form.comboEmployes.libellé']}"/>
    <h:outputText value="#{msg['form.heuresTravaillées.libellé']}"/>
    <h:outputText value="#{msg['form.joursTravaillés.libellé']}"/>
    <h:selectOneMenu id="comboEmployes" value="#{form.comboEmployesValue}">
      <f:selectItems .../>
    </h:selectOneMenu>
    <h:inputText id="heuresTravaillees" value="#{form.heuresTravaillées}" required="true" requiredMessage="#{msg['form.heuresTravaillées.required']}" validatorMessage="#{msg['form.heuresTravaillées.validation']}">
      <f:validateDoubleRange minimum="0" maximum="300"/>
    </h:inputText>
    <h:inputText id="joursTravailles" value="#{form.joursTravaillés}" required="true" requiredMessage="#{msg['form.joursTravaillés.required']}" validatorMessage="#{msg['form.joursTravaillés.validation']}">
      <f:validateLongRange minimum="0" maximum="31"/>
    </h:inputText>
    <h:panelGroup></h:panelGroup>
    <h:message for="heuresTravaillees" styleClass="error"/>
    <h:message for="joursTravailles" styleClass="error"/>
  </h:panelGrid>
  <hr/>
</html>

يعرض هذا الرمز العرض التالي:

 

السؤال: أكمل السطر 13 من الكود XHTML. يتم توفير قائمة عناصر قائمة التحرير والسرد الخاصة بالموظفين بواسطة طريقة في bean [Form]. اكتب هذه الطريقة. ستكون الخاصية itemValue للعناصر المعروضة في القائمة المنسدلة مساوية لرقم الموظف SS، وستكون الخاصية itemLabel عبارة عن سلسلة تتكون من الاسم الأول واسم العائلة للموظف.



السؤال: كيف يجب تهيئة bean [SessionData] بحيث يكون قائمة العناوين هي تلك الموضحة أعلاه عند الطلب الأولي GET المقدم إلى النموذج؟


12.5.2. الإجراء [faireSimulation]

الرمز JSF للإجراء [faireSimulation] هو التالي:


<h:commandLink id="cmdFaireSimulation" value="#{msg['form.menu.faireSimulation']}" action="#{form.faireSimulation}" rendered="#{sessionData.menuFaireSimulationIsRendered}"/>

تقوم العملية [faireSimulation] بحساب كشف الراتب:

Image

من الصفحة السابقة، نحصل على النتيجة التالية:

يتم عرض المحاكاة مع الصفحة التالية [simulation.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="part1">
      <ui:include src="saisie2.xhtml"/>
    </ui:define>
    <ui:define name="part2">
      <h:outputText value="#{msg['form.infos.employé']}" styleClass="titreInfos"/>
      <br/><br/>
      <h:panelGrid columns="3" rowClasses="libelle,info">
        <h:outputText value="#{msg['form.employe.nom']}"/>
        <h:outputText value="#{msg['form.employe.prénom']}"/>
        <h:outputText value="#{msg['form.employe.adresse']}"/>
        <h:outputText value="#{form.feuilleSalaire.employe.nom}"/>
        <h:outputText value="#{form.feuilleSalaire.employe.prenom}"/>
        <h:outputText value="#{form.feuilleSalaire.employe.adresse}"/>
      </h:panelGrid>
      <h:panelGrid columns="3" rowClasses="libelle,info">
        <h:outputText value="#{msg['form.employe.ville']}"/>
        <h:outputText value="#{msg['form.employe.codePostal']}"/>
        <h:outputText value="#{msg['form.employe.indice']}"/>
        <h:outputText value="#{form.feuilleSalaire.employe.ville}"/>
        <h:outputText value="#{form.feuilleSalaire.employe.codePostal}"/>
        <h:outputText value="#{form.feuilleSalaire.employe.indemnite.indice}"/>
      </h:panelGrid>
      <br/>
      <h:outputText value="#{msg['form.infos.cotisations']}" styleClass="titreInfos"/>
      <br/><br/>
      <h:panelGrid columns="4" rowClasses="libelle,info">
        <h:outputText value="#{msg['form.cotisations.csgrds']}"/>
        <h:outputText value="#{msg['form.cotisations.csgd']}"/>
        <h:outputText value="#{msg['form.cotisations.retraite']}"/>
        <h:outputText value="#{msg['form.cotisations.secu']}"/>
        <h:outputText value="#{form.feuilleSalaire.cotisation.csgrds} %"/>
        <h:outputText value="#{form.feuilleSalaire.cotisation.csgd} %"/>
        <h:outputText value="#{form.feuilleSalaire.cotisation.retraite} %"/>
        <h:outputText value="#{form.feuilleSalaire.cotisation.secu} %"/>
      </h:panelGrid>
      <br/>
      <h:outputText value="#{msg['form.infos.indemnites']}" styleClass="titreInfos"/>
      <br/><br/>
      <h:panelGrid columns="4" rowClasses="libelle,info">
        <h:outputText value="#{msg['form.indemnites.salaireHoraire']}"/>
        <h:outputText value="#{msg['form.indemnites.entretienJour']}"/>
        <h:outputText value="#{msg['form.indemnites.repasJour']}"/>
        <h:outputText value="#{msg['form.indemnites.congésPayés']}"/>
        <h:outputFormat value="{0,number,currency}">
          <f:param value="#{form.feuilleSalaire.employe.indemnite.baseHeure}"/>
        </h:outputFormat>
        <h:outputFormat value="{0,number,currency}">
          <f:param value="#{form.feuilleSalaire.employe.indemnite.entretienJour}"/>
        </h:outputFormat>
        <h:outputFormat value="{0,number,currency}">
          <f:param value="#{form.feuilleSalaire.employe.indemnite.repasJour}"/>
        </h:outputFormat>
        <h:outputText value="#{form.feuilleSalaire.employe.indemnite.indemnitesCP} %"/>
      </h:panelGrid>
      <br/>
      <h:outputText value="#{msg['form.infos.salaire']}" styleClass="titreInfos"/>
      <br/><br/>
      <h:panelGrid columns="4" rowClasses="libelle,info">
        <h:outputText value="#{msg['form.salaire.base']}"/>
        <h:outputText value="#{msg['form.salaire.cotisationsSociales']}"/>
        <h:outputText value="#{msg['form.salaire.entretien']}"/>
        <h:outputText value="#{msg['form.salaire.repas']}"/>
        <h:outputFormat value="{0,number,currency}">
          <f:param value="#{form.feuilleSalaire.elementsSalaire.salaireBase}"/>
        </h:outputFormat>
        <h:outputFormat value="{0,number,currency}">
          <f:param value="#{form.feuilleSalaire.elementsSalaire.cotisationsSociales}"/>
        </h:outputFormat>
        <h:outputFormat value="{0,number,currency}">
          <f:param value="#{form.feuilleSalaire.elementsSalaire.indemnitesEntretien}"/>
        </h:outputFormat>
        <h:outputFormat value="{0,number,currency}">
          <f:param value="#{form.feuilleSalaire.elementsSalaire.indemnitesRepas}"/>
        </h:outputFormat>
      </h:panelGrid>
      <br/>
      <h:panelGrid columns="3" columnClasses="libelle,col2,info">
        <h:outputText value="#{msg['form.salaire.net']}"/>
        <h:panelGroup></h:panelGroup>
        <h:outputFormat value="{0,number,currency}">
          <f:param value="#{form.feuilleSalaire.elementsSalaire.salaireNet}"/>
        </h:outputFormat>
      </h:panelGrid>
    </ui:define>
  </ui:composition>
</html>
  • السطر 8، يتم إدراج الصفحة [simulation.xhtml] في الصفحة [layout.xhtml
  • الأسطر 9-11: يتم عرض منطقة الإدخالات في الجزء part1 من صفحة التخطيط،
  • الأسطر 12-91: يتم عرض المحاكاة في الجزء part2 من تخطيط الصفحة.

السؤال: اكتب الطريقة [faireSimulation] للفئة [Form]. سيتم حفظ المحاكاة في bean SessionData.


12.5.3. إدارة الأخطاء

نريد أن نتمكن من إدارة الاستثناءات التي قد تحدث أثناء حساب المحاكاة بشكل صحيح. لهذا الغرض، سيستخدم كود الطريقة [faireSimulation] عبارة try / catch:


  // action du menu
  public String faireSimulation(){
    try{
      // on calcule la feuille de salaire
      feuilleSalaire= ...
      // on affiche la simulation
      ...
      // on met à jour le menu
      ...
       // on rend la vue simulation
      return "simulation";
    }catch(Throwable th){
      // on vide la liste des erreurs précédentes
      ...
      // on crée la nouvelle liste des erreurs
      ...
      // on affiche la vue vueErreur
      ...
      // on met à jour le menu
      ...
       // on affiche la vue erreur
      return "erreurs";
    }
}

قائمة الأخطاء التي تم إنشاؤها في السطر 15 هي كما يلي:


  // le modèle des vues
  ...
  private List<Erreur> erreurs=new ArrayList<Erreur>();
...

يتم تعريف فئة Erreur على النحو التالي:


package web.entities;

public class Erreur {
  
  public Erreur() {
  }
  
  // champ
  private String classe;
  private String message;

  // constructeur
  public Erreur(String classe, String message){
    this.setClasse(classe);
    this.message=message;
  }
  
  // getters et setters
...  
}

ستكون الأخطاء استثناءات يتم تخزين اسم فئتها في الحقل classe والرسالة في الحقل message.

تتكون قائمة الأخطاء التي تم إنشاؤها في الطريقة [faireSimulation] من:

  • الاستثناء الأولي th من النوع Throwable الذي حدث،
  • وسببها th.getCause() إن وجد،
  • سبب السبب h.getCause().getCause() إن وجد.
  • ...

فيما يلي مثال على قائمة الأخطاء:

Image

في المثال أعلاه، لا يوجد الموظف [Z Y] في قاموس الموظفين المستخدم من قبل الطبقة [métier] المحاكاة. في هذه الحالة، تطلق الطبقة [métier] المحاكاة استثناءً (السطر 6 أدناه):


  public FeuilleSalaire calculerFeuilleSalaire(String SS, double nbHeuresTravaillées, int nbJoursTravaillés) {
    // on récupère l'employé
    Employe e=hashEmployes.get(SS);
    // on rend une exception si l'employé n'existe pas
    if(e==null){
      throw new PamException(String.format("L'employé de n° SS [%s] n'existe pas",SS),1);
    }
...
}

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

Image

يتم عرض هذا العرض من خلال الصفحة التالية [erreurs.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="part1">
      <h3><h:outputText value="#{msg['erreur.titre']}"/></h3>
      <h:dataTable value="#{form.erreurs}" var="erreur"
                   headerClass="erreursHeaders" columnClasses="erreurClasse,erreurMessage">
        <f:facet name="header">
          <h:outputText value="#{msg['erreur.exceptions']}"/>
        </f:facet>
        <h:column>
          <f:facet name="header">
            <h:outputText value="#{msg['exception.type']}"/>
          </f:facet>
          <h:outputText value="#{erreur.classe}"/>
        </h:column>
        <h:column>
          <f:facet name="header">
            <h:outputText value="#{msg['exception.message']}"/>
          </f:facet>
          <h:outputText value="#{erreur.message}"/>
        </h:column>
      </h:dataTable>
    </ui:define>
  </ui:composition>
</html>

السؤال: أكمل الطريقة [faireSimulation] بحيث تعرض طريقة العرض [vueErreur] عند حدوث استثناء.


12.5.4. الإجراء [effacerSimulation]

تسمح الإجراء [effacerSimulation] للمستخدم بالوصول إلى نموذج فارغ:

الرمز JSF للرابط [effacerSimulation] هو التالي:


<h:commandLink id="cmdEffacerSimulation"  onclick="raz()" value="#{msg['form.menu.effacerSimulation']}" action="#{form.effacerSimulation}" rendered="#{sessionData.menuEffacerSimulationIsRendered}"/>

يؤدي النقر على الرابط [EffacerSimulation] أولاً إلى استدعاء دالة جافا سكريبت raz(). يتم تعريف هذه الطريقة في الصفحة [layout.xhtml]:


    <script type="text/javascript">
      function raz(){
        // on change les valeurs postées
        document.forms['formulaire'].elements['formulaire:comboEmployes'].value="0";
        document.forms['formulaire'].elements['formulaire:heuresTravaillees'].value="0";
        document.forms['formulaire'].elements['formulaire:joursTravailles'].value="0";
      }
</script>

تقوم الأسطر 4-6 بتغيير القيم المرسلة. تجدر الإشارة إلى أن

  • القيم المرسلة هي قيم صالحة، c.a.d. وأنها ستجتاز اختبارات التحقق من صحة حقول الإدخال heuresTravaillees و joursTravailles.
  • لا تقوم الدالة raz بإرسال النموذج. في الواقع، سيتم إرساله عبر الرابط cmdEffacerSimulation. سيتم تنفيذ post بعد تشغيل دالة جافا سكريبت raz.

أثناء تنفيذ post، ستتبع القيم المرسلة مسارًا عاديًا: التحقق من الصحة ثم التعيين إلى حقول النموذج. وهذه الحقول هي التالية في الفئة [Form]:


  // le modèle des vues
  private String comboEmployesValue;
  private String heuresTravaillées;
  private String joursTravaillés;
  ...

ستتلقى هذه الحقول الثلاثة القيم الثلاث التي تم إرسالها {"0","0","0"}. بمجرد إجراء هذا التعيين، سيتم تنفيذ الطريقة effacerSimulation.


السؤال: اكتب الطريقة [effacerSimulation] للفئة [Form]. يجب التأكد من:

  • يتم عرض منطقة الإدخال فقط،

  • يتم وضع القائمة المنسدلة على العنصر الأول،

  • تعرض مناطق الإدخال heuresTravaillees و joursTravailles سلاسل فارغة.


12.5.5. الإجراء [enregistrerSimulation]

الرمز JSF للرابط [enregistrerSimulation] هو التالي:


<h:commandLink id="cmdEnregistrerSimulation" immediate="true" value="#{msg['form.menu.enregistrerSimulation']}" action="#{form.enregistrerSimulation}" rendered="#{sessionData.menuEnregistrerSimulationIsRendered}"/>

تسمح الإجراء [enregistrerSimulation] المرتبط بالرابط بتسجيل المحاكاة الحالية في قائمة المحاكاة الموجودة في الفئة [SessionData]:


private List<Simulation> simulations=new ArrayList<Simulation>();

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


package web.entities;

import metier.FeuilleSalaire;

public class Simulation {
  
  public Simulation() {
  }

  // champs d'une simulation
  private Integer num;
  private FeuilleSalaire feuilleSalaire;
  private String heuresTravaillées;
  private String joursTravaillés;
  
  // constructeur
  public Simulation(Integer num,String heuresTravaillées, String joursTravaillés, FeuilleSalaire feuilleSalaire){
    this.setNum(num);
    this.setFeuilleSalaire(feuilleSalaire);
    this.setHeuresTravaillées(heuresTravaillées);
    this.setJoursTravaillés(joursTravaillés);
  }
  
  public double getIndemnites(){
    return feuilleSalaire.getElementsSalaire().getIndemnitesEntretien()+ feuilleSalaire.getElementsSalaire().getIndemnitesRepas();
  }

  // getters et setters
...
}

تسمح هذه الفئة بتخزين محاكاة قام بها المستخدم:

  • السطر 11: رقم المحاكاة،
  • السطر 12: كشف الراتب الذي تم حسابه،
  • السطر 13: عدد ساعات العمل،
  • السطر 14: عدد أيام العمل.

فيما يلي مثال على التسجيل:

Image

من الصفحة السابقة، نحصل على النتيجة التالية:

Image

رقم المحاكاة هو رقم يزداد مع كل تسجيل جديد. وهو ينتمي إلى bean SessionData:


  // simulations
  private List<Simulation> simulations = new ArrayList<Simulation>();
  private int numDerniereSimulation = 0;
private Simulation simulation;
  • السطر 2: رقم آخر محاكاة تم إجراؤها.

يمكن أن تعمل الطريقة [enregistrerSimulation] على النحو التالي:

  • استرداد رقم آخر محاكاة في bean [SessionData] وزيادته،
  • إضافة المحاكاة الجديدة إلى قائمة المحاكاة التي تحتفظ بها الفئة [SessionData
  • عرض جدول المحاكاة:

يتم عرض جدول المحاكاة بواسطة الصفحة [simulations.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="part1">
      <!-- tableau des simulations -->
      <h:dataTable value="#{sessionData.simulations}" var="simulation"
                   headerClass="simulationsHeaders" columnClasses="simuNum,simuNom,simuPrenom,simuHT,simuJT,simuSalaireBase,simuIndemnites,simuCotisationsSociales,simuSalaireNet">
        <h:column>
          <f:facet name="header">
            <h:outputText value="#{msg['simulations.headers.numero']}"/>
          </f:facet>
          <h:outputText value="#{simulation.num}"/>
        </h:column>
        <h:column>
          <f:facet name="header">
            <h:outputText value="#{msg['simulations.headers.nom']}"/>
          </f:facet>
          <h:outputText value="#{simulation.feuilleSalaire.employe.nom}"/>
        </h:column>
        <h:column>
          <f:facet name="header">
            <h:outputText value="#{msg['simulations.headers.prenom']}"/>
          </f:facet>
          <h:outputText value="#{simulation.feuilleSalaire.employe.prenom}"/>
        </h:column>
        <h:column>
          <f:facet name="header">
            <h:outputText value="#{msg['simulations.headers.heuresTravaillees']}"/>
          </f:facet>
          <h:outputText value="#{simulation.heuresTravaillées}"/>
        </h:column>
        <h:column>
          <f:facet name="header">
            <h:outputText value="#{msg['simulations.headers.joursTravailles']}"/>
          </f:facet>
          <h:outputText value="#{simulation.joursTravaillés}"/>
        </h:column>
        <h:column>
          <f:facet name="header">
            <h:outputText value="#{msg['simulations.headers.salaireBase']}"/>
          </f:facet>
          <h:outputText value="#{simulation.feuilleSalaire.elementsSalaire.salaireBase}"/>
        </h:column>
        <h:column>
          <f:facet name="header">
            <h:outputText value="#{msg['simulations.headers.indemnites']}"/>
          </f:facet>
          <h:outputText value="#{simulation.indemnites}"/>
        </h:column>
        <h:column>
          <f:facet name="header">
            <h:outputText value="#{msg['simulations.headers.cotisationsSociales']}"/>
          </f:facet>
          <h:outputText value="#{simulation.feuilleSalaire.elementsSalaire.cotisationsSociales}"/>
        </h:column>
        <h:column>
          <f:facet name="header">
            <h:outputText value="#{msg['simulations.headers.salaireNet']}"/>
          </f:facet>
          <h:outputText value="#{simulation.feuilleSalaire.elementsSalaire.salaireNet}"/>
        </h:column>
        <h:column>
          <h:commandLink value="Retirer" action="#{form.retirerSimulation}">
            <f:setPropertyActionListener target="#{form.numSimulationToDelete}" value="#{simulation.num}"/>
          </h:commandLink>
        </h:column>
      </h:dataTable>
    </ui:define>
  </ui:composition>
</html>
  • السطر 8، يتم إدراج الصفحة [simulations.xhtml] داخل الصفحة [layout.xhtml
  • السطر 9، بدلاً من الجزء المسمى part1،
  • السطر 11، تستخدم العلامة <h:dataTable> الحقل #{sessionData.simulations} كمصدر للبيانات، c.a.d. الحقل التالي:

  // simulations
private List<Simulation> simulations;
  • السمة var="simulation" تحدد اسم المتغير الذي يمثل المحاكاة الحالية داخل العلامة <h:datatable>

  • السمة headerClass="simulationsHeaders" تحدد نمط عناوين أعمدة الجدول.

  • السمة columnClasses="...." تحدد نمط كل عمود من أعمدة الجدول

دعونا نلقي نظرة على أحد أعمدة الجدول ونرى كيف تم إنشاؤه:

الرمز JSF للعمود Nom هو التالي:


            <h:column>
              <f:facet name="header">
                <h:outputText value="#{msg['simulations.headers.nom']}"/>
              </f:facet>
              <h:outputText value="#{simulation.feuilleSalaire.employe.nom}"/>
</h:column>
  • الأسطر 2-4: العلامة <f:facet name="header"> تحدد عنوان العمود
  • السطر 5: اسم الموظف مكتوب:
    • تشير المحاكاة إلى السمة var لعلامة <h:dataTable ...>:

<h:dataTable value="#{sessionData.simulations}" var="simulation" ...>

تشير المحاكاة إلى المحاكاة الحالية لقائمة المحاكاة: أولاً الأولى، ثم الثانية، ...

  • (تابع)
    • يشير simulation.feuilleSalaire إلى الحقل feuilleSalaire في المحاكاة الحالية
    • يشير simulation.feuilleSalaire.employe إلى حقل الموظف في الحقل feuilleSalaire
    • يشير simulation.feuilleSalaire.employe.nom إلى الحقل "الاسم" في الحقل "الموظف"

يتم تكرار نفس التقنية لجميع أعمدة الجدول. هناك صعوبة في عمود "Indemnités" الذي يتم إنشاؤه باستخدام الرمز التالي:


            <h:column>
              <f:facet name="header">
                <h:outputText value="#{msg['simulations.headers.indemnites']}"/>
              </f:facet>
              <h:outputText value="#{simulation.indemnites}"/>
</h:column>

في السطر 5، يتم عرض قيمة simulation.indemnites. ولكن فئة Simulation لا تحتوي على حقل indemnites. يجب أن نتذكر هنا أن حقل indemnites لا يُستخدم مباشرةً بل عبر الطريقة simulation.getIndemnites(). لذا يكفي أن تكون هذه الطريقة موجودة. أما الحقل indemnites فيمكن ألا يكون موجودًا. يجب أن تعرض الطريقة getIndemnites إجمالي بدلات الموظف. وهذا يتطلب حسابًا وسيطًا لأن هذا الإجمالي غير متاح مباشرةً في كشف الراتب. الطريقة getIndemnites موضحة في الفقرة 12.5.5.


السؤال: اكتب الطريقة [enregistrerSimulation] للفئة [Form].


12.5.6. الإجراء [retourSimulateur]

الرمز JSF للرابط [retourSimulateur] هو كما يلي:


<h:commandLink id="cmdRetourSimulateur" immediate="true" value="#{msg['form.menu.retourSimulateur']}" action="#{form.retourSimulateur}" rendered="#{sessionData.menuRetourSimulateurIsRendered}"/>

تسمح الإجراء [retourSimulateur] المرتبط بالرابط للمستخدم بالعودة من العرض [vueSimulations] إلى العرض [vueSaisies]:

Image

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

Image


السؤال: اكتب الطريقة [retourSimulateur] للفئة [Form]. يجب أن يكون نموذج الإدخال المعروض فارغًا كما هو موضح أعلاه.


12.5.7. الإجراء [voirSimulations]

الرمز JSF للرابط [voirSimulations] هو التالي:


<h:commandLink id="cmdVoirSimulations" immediate="true" value="#{msg['form.menu.voirSimulations']}" action="#{form.voirSimulations}" rendered="#{sessionData.menuVoirSimulationsIsRendered}"/>

تسمح الإجراء [voirSimulations] المرتبط بالرابط للمستخدم بالحصول على جدول المحاكاة، بغض النظر عن حالة إدخالاته:

Image

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

Image


السؤال: اكتب الطريقة [voirSimulations] للفئة [Form].


سنحرص على أنه إذا كانت قائمة المحاكاة فارغة، فإن العرض المعروض سيكون [vueSimulationsVides]:

Image

لعرض العرض أعلاه، سنستخدم الصفحة التالية [simulationsVides.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="part1">
      <h2>Votre liste de simulations est vide.</h2>
    </ui:define>
  </ui:composition>
</html>

12.5.8. الإجراء [retirerSimulation]

يمكن للمستخدم إزالة المحاكاة من قائمته:

Image

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

Image

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

Image

الرمز JSF من العمود [Retirer] في جدول المحاكاة هو التالي:


          <h:dataTable value="#{form.simulations}" var="simulation"
                       headerClass="simulationsHeaders" columnClasses="simuNum,simuNom,simuPrenom,simuHT,simuJT,simuSalaireBase,simuIndemnites,simuCotisationsSociales,simuSalaireNet">
...
            <h:column>
              <h:commandLink value="Retirer" action="#{form.retirerSimulation}">
                <f:setPropertyActionListener target="#{form.numSimulationToDelete}" value="#{simulation.num}"/>
              </h:commandLink>
            </h:column>
          </h:dataTable>
  • السطر 5: الرابط [Retirer] مرتبط بالطريقة [retirerSimulation] من الفئة [Form]. تحتاج هذه الطريقة إلى معرفة رقم المحاكاة المراد إزالتها. يتم توفير هذا الرقم لها بواسطة العلامة <f:setPropertyActionListener> في السطر 8. تحتوي هذه العلامة على سمتين هما target و value: تشير السمة target إلى حقل في النموذج سيتم تعيين قيمة السمة value إليه. هنا، سيتم تعيين رقم المحاكاة المراد إزالتها #{simulation.num} إلى الحقل numSimulationToDelete من الفئة [Form]:

  // le modèle des vues
  ...
  private Integer numSimulationToDelete;

عندما يتم تنفيذ الطريقة [retirerSimulation] للفئة [Form]، ستتمكن من استخدام القيمة التي تم تخزينها مسبقًا في الحقل numSimulationToDelete.


السؤال: اكتب الطريقة [retirerSimulation] للفئة [Form].


12.5.9. الإجراء [terminerSession]

الرمز JSF للرابط [Terminer la session] هو التالي:


<h:commandLink id="cmdTerminerSession" immediate="true" value="#{msg['form.menu.terminerSession']}" action="#{form.terminerSession}" rendered="#{sessionData.menuTerminerSessionIsRendered}"/>

تسمح الإجراء [terminerSession] المرتبط بالرابط للمستخدم بإنهاء جلسته والعودة إلى نموذج الإدخال الفارغ:

Image

Image

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


السؤال: اكتب الطريقة [terminerSession] للفئة [Form].


12.6. دمج طبقة الويب في بنية ثلاثية الطبقات JSF / EJB

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

نستبدل الطبقة [métier] المحاكاة بطبقات [métier, DAO, jpa] التي تم تنفيذها بواسطة EJB في الفقرة 7.1:


تدريب عملي: قم بدمج الطبقات JSF و EJB باتباع المنهجية الموضحة في الفقرة 11.