Skip to content

19. دراسة حالة – الإصدار 1

19.1. الطبقة [metier] المحاكاة

لنعد إلى بنية التطبيق الذي نقوم بإنشائه:

لدينا أرشيف الطبقات [metier, dao, jpa] وقد عرضنا عناصر هذه الطبقات التي يجب أن تكون الطبقة [web] على علم بها. نحن جاهزون لكتابة هذه الطبقة باستخدام إطار عمل Struts.

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

سنقوم بتطوير الطبقة [web] مع الطبقة [métier] المحاكاة. ستصبح الاختبارات أسهل في التنفيذ نظرًا لعدم وجود قاعدة بيانات في البنية. بفضل Spring واستخدام الواجهات، فإن استبدال الطبقة [metier] المُحاكاة لاحقًا بالبنية الحقيقية [metier, dao, jpa] لن يكون له أي تأثير على كود الطبقة [web / struts2]. ويمكن استخدام الطبقة [web / struts2] التي سنقوم بتطويرها الآن كما هي.

الطبقة [metier] المحاكاة التي سنستخدمها هي التالية:


package metier;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import jpa.Cotisation;
import jpa.Employe;
import jpa.Indemnite;

public class MetierSimule implements IMetier {

  // قائمة الموظفين
  private Map<String, Employe> hashEmployes = new HashMap<String, Employe>();
  private List<Employe> listEmployes;

  // الحصول على كشف الراتب
  public FeuilleSalaire calculerFeuilleSalaire(String SS,
          double nbHeuresTravaillées, int nbJoursTravaillés) {
    // استرداد الموظف رقم SS
    Employe e = hashEmployes.get(SS);
    // يتم إصدار كشف راتب وهمي
    return new FeuilleSalaire(e, new Cotisation(3.49, 6.15, 9.39, 7.88), new ElementsSalaire(100, 100, 100, 100, 100));
  }

  // قائمة الموظفين
  public List<Employe> findAllEmployes() {
    if (listEmployes == null) {
      // يتم إنشاء قائمة تضم موظفين اثنين
      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)));
      // قاموس الموظفين
      for (Employe e : listEmployes) {
        hashEmployes.put(e.getSS(), e);
      }
    }
    // عرض قائمة الموظفين
    return listEmployes;
  }
}
  • السطر 11: الفئة [MetierSimule] تُنفِّذ الواجهة [IMetier] التي تُنفِّذها الطبقة الحقيقية [metier].
  • السطر 14: قاموس للموظفين مفهرس حسب أرقامهم INSEE
  • السطر 15: قائمة الموظفين
  • الأسطر 27-39: تنفيذ الطريقة findAllEmployes للواجهة [IMetier].
  • الأسطر 30-33: إنشاء قائمة تضم موظفين اثنين
  • الأسطر 34-36: إنشاء قاموس الموظفين المفهرس برقم INSEE
  • الأسطر 18-24: تنفيذ الطريقة calculerSalaire الخاصة بالواجهة [IMetier]. هنا يتم إرجاع كشف راتب وهمي.

19.2. مشروع NetBeans

مشروع NetBeans هو كما يلي:

  • في [1]:
  • [applicationContext.xml] هو ملف تكوين Spring
  • [tiles.xml] هو ملف تكوين إطار عمل يُسمى Tiles.
  • [web.xml] هو ملف تكوين تطبيق الويب
  • في [2]: العروض المختلفة للتطبيق
  • في [3]:
  • [messages.properties]: ملف الرسائل
  • [struts.xml]: ملف تكوين Struts
  • في [4]: أكواد المصدر للتطبيق. توجد إجراءات Struts في الحزمة [web.actions].
  • في [5]: الطبقة [metier] المحاكاة
  • في [6]: الأرشيفات المستخدمة. توجد أرشيفات الأدوات المختلفة المستخدمة: Spring، Tiles، Struts 2، المكون الإضافي للتكامل بين Struts 2 وSpring، المكون الإضافي للتكامل بين Struts 2 وTiles.
  • في [7]: أرشيف الطبقة [metier, dao, jpa] الحقيقية. وهي تتيح لنا الوصول إلى كيانات JPA، والواجهة [IMetier]، والفئات [FeuilleSalaire] و[ElementsSalaire]. فجميع هذه العناصر تُستخدم بالفعل من قِبل فئتنا [MetierSimule].

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

يتم تكوين المشروع من خلال ملفات مختلفة:

  • [web.xml] الذي يقوم بتكوين تطبيق الويب
  • [struts.xml] الذي يقوم بتكوين إطار العمل Struts
  • [applicationContext.xml] الذي يقوم بتكوين إطار العمل Spring
  • [tiles.xml] الذي يقوم بتكوين إطار العمل Tiles

19.3.1. تكوين تطبيق الويب

الملف [web.xml] هو كما يلي:


<?xml version="1.0" encoding="UTF-8"?>
<web-app id="pam_struts_01" version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
  <display-name>Pam</display-name>
    <!-- المربعات -->
  <context-param>
    <param-name> org.apache.tiles.impl.BasicTilesContainer.DEFINITIONS_CONFIG </param-name>
    <param-value>/WEB-INF/tiles.xml</param-value>
  </context-param>
  <listener>
    <listener-class>org.apache.struts2.tiles.StrutsTilesListener</listener-class>
  </listener>
    <!-- Struts 2 -->
  <filter>
    <filter-name>struts2</filter-name>
    <filter-class>org.apache.struts2.dispatcher.FilterDispatcher</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>struts2</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
     <!-- Spring -->
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>
</web-app>
  • الأسطر 13-20: تهيئ مرشح Struts 2 – سبق ذكرها
  • الأسطر 22-24: تهيئ مستمع Spring – سبق ذكرها
  • الأسطر 9-11: تُستخدم لتكوين مستمع Tiles. سيتم إنشاء مثيل للفئة [org.apache.struts2.tiles.StrutsTilesListener] عند بدء تشغيل تطبيق الويب. وستقوم عندئذٍ باستخدام ملف التكوين الخاص بها. ويتم تعريف هذا الملف في الأسطر 5-8. وبالتالي، فإن ملف تكوين Tiles هو الملف [WEB-INF/tiles.xml].

وفي النهاية، عند بدء تشغيل تطبيق Struts، يتم إنشاء ثلاث فئات:

  • واحدة لمرشح Struts 2. وهي التي تضمن الحرف C في MVC.
  • والثانية لمستمع Spring. سيستخدم Spring الملف [applicationContext.xml] لإنشاء مثيلات لطبقات [métier, dao, jpa] الخاصة بالتطبيق. كما سيقوم Spring، كما في المثال السابق، بإنشاء مثيل لفئة [Config] التي ستحتوي على بيانات نطاق التطبيق (Application). وأخيرًا، سيقوم Spring بحقن مرجع إلى هذه المثيل الوحيد [Config] في كل إجراء Struts يحتاج إليه.
  • وآخر للمستمع Tiles. سيتولى هذا الإطار إدارة العروض. سنعود إلى هذا الموضوع قريبًا.

19.3.2. تكوين إطار عمل Struts

يتم تكوين إطار عمل Struts بواسطة الملف [struts.xml] التالي:


<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
    "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
    "http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>
  <!-- التدويل -->
  <constant name="struts.custom.i18n.resources" value="messages" />
  <!-- تكامل Spring -->
  <constant name="struts.objectFactory.spring.autoWire" value="name" />

  
  <!-- إجراءات Struts /Tiles -->
  <package name="default" namespace="/" extends="tiles-default">
    <!-- الإجراء الافتراضي -->
    <default-action-ref name="index" />
    <action name="index">
      <result type="redirectAction">
        <param name="actionName">Formulaire</param>
        <param name="namespace">/</param>
      </result>
    </action>
    <!-- إجراء النموذج -->
    <action name="Formulaire" class="web.actions.Formulaire" method="input">
      <result name="success" type="tiles">saisie</result>
      <result name="exception" type="tiles">exception</result>
    </action>
    <!-- إجراء FaireSimulation -->
    <action name="FaireSimulation" class="web.actions.Formulaire" method="calculSalaire">
      <result name="success" type="tiles">simulation</result>
      <result name="exception" type="tiles">exception</result>
      <result name="input" type="tiles">saisie</result>
    </action>
    <!-- إجراء EnregistrerSimulation -->
    <action name="EnregistrerSimulation" class="web.actions.Enregistrer" method="execute">
      <result name="error" type="tiles">erreur</result>
      <result name="simulations" type="tiles">simulations</result>
    </action>
   <!-- الإجراء RetourFormulaire -->
    <action name="RetourFormulaire" >
      <result type="redirectAction">
        <param name="actionName">Formulaire</param>
        <param name="namespace">/</param>
      </result>
    </action>
   <!-- إجراء VoirSimulations -->
    <action name="VoirSimulations" class="web.actions.Voir">
      <result name="success" type="tiles">simulations</result>
    </action>
   <!-- إجراء RetirerSimulation -->
    <action name="SupprimerSimulation" class="web.actions.Supprimer" method="execute">
      <result name="erreur" type="tiles">erreur</result>
      <result name="simulations" type="tiles">simulations</result>
    </action>
   <!-- إجراء TerminerSession -->
    <action name="TerminerSession" class="web.actions.Terminer" method="execute">
      <result name="success" type="redirectAction">
        <param name="actionName">Formulaire</param>
        <param name="namespace">/</param>
      </result>
    </action>
  </package>

</struts>

سنعلق على الإجراءات المختلفة في Struts تدريجيًا أثناء دراستنا لها. في الوقت الحالي، يمكن ملاحظة النقاط التالية:

  • السطر 8: يحدد ملف الرسائل
  • السطر 10: يحدد طريقة حقن مكونات Spring في إجراءات Struts. يتم الحقن بناءً على اسم المكون. يجب أن يحمل الحقل في إجراء Struts الذي يجب تهيئته بواسطة Spring اسم المكون المراد حقنه.
  • السطر 25: يحدد العرض الذي سيتم عرضه لمفتاح التنقل success الخاص بعملية [Formulaire]. نلاحظ أن النتيجة <result> تحتوي على سمة type='tiles' التي لا نعرفها. كنا نعرف النوع redirect الذي يسمح بإعادة توجيه العميل إلى عرض. هنا، يتم إدارة العرض من النوع tiles بواسطة إطار العمل Tiles. يتم تعريف النوع tiles في الملف [struts-plugin.xml] الموجود في الأرشيف [struts2-tiles-plugin-2.2.3.1.jar]:

<struts>
    <package name="tiles-default" extends="struts-default">
        <result-types>
            <result-type name="tiles" class="org.apache.struts2.views.tiles.TilesResult"/>
        </result-types>
    </package>
</struts>
  • الأسطر 3-5: تعريف نوع النتيجة tiles.
  • السطر 2: تم تعريف هذا النوع في الحزمة [tiles-default] التي توسع نطاق الحزمة [struts-default].
  • السطر 14: يحدد الحزمة [default] التي ستحتوي على جميع إجراءات التطبيق. للاستفادة من تعريف نوع العرض tiles، توسع الحزمة [tiles-default].

19.3.3. تكوين إطار عمل Spring

يتم تكوين إطار عمل Spring بواسطة الملف [WEB-INF/applicationContext.xml] التالي:


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd">
  
  <!-- طبقات التطبيقات -->
 
<!-- الويب -->
  <bean id="config" class="web.Config" init-method="init">
    <property name="metier" ref="metier"/>
  </bean>
  <!-- الطبقة الوظيفية -->
  <bean id="metier" class="metier.MetierSimule"/>

</beans>
  • السطر 13: الطبقة [metier] المُحاكاة التي تم إنشاء مثيل لها بواسطة الفئة [metier.MetierSimule]
  • الأسطر 9-11: تُكوّن حبة (bean) تُسمى config. وكما في المثال الذي تمت دراسته سابقًا، ستُستخدم هذه الحبة لتغليف معلومات النطاق Application. والفئة المرتبطة بهذه الحبة هي الفئة [Config] التالية:

package web;

import java.util.List;
import jpa.Employe;
import metier.IMetier;

public class Config {

  // طبقة الأعمال التي تم تهيئتها بواسطة Spring
  private IMetier metier;
  // قائمة الموظفين
  private List<Employe> employes;
  // الأخطاء
  private Exception initException;

  // منشئ
  public Config() {
  }

  // طريقة Spring لتهيئة الكائن
  public void init() {
    // يتم طلب قائمة الموظفين
    try {
      employes = metier.findAllEmployes();
    } catch (Exception ex) {
      initException = ex;
    }
  }

  // الطرق المخصصة للاسترداد والتعيين
  ...
}

لنعد إلى تكوين البين config:


  <bean id="config" class="web.Config" init-method="init">
    <property name="metier" ref="metier"/>
  </bean>
  <!-- الطبقة اللوجستية -->
  <bean id="metier" class="metier.Metier">
    ...  
</bean>

في السطر 2، يمكننا أن نرى أن البين metier الموجود في السطر 5، يتم حقنه (ref) في الحقل المسمى metier (name) للكائن [Config]. يُعد «البيان» metier مرجعًا في الطبقة [metier]:

للتفاعل مع الطبقة [metier]، ستحتاج جميع إجراءات Struts في الطبقة [web] إلى مرجع إلى هذه الطبقة. يمكن القول إن الإشارة إلى الطبقة [metier] هي بيانات ذات نطاق Application. وستحتاج إليها جميع الطلبات المقدمة من جميع المستخدمين. ولهذا السبب، نضع هذه الإشارة في الكائن [Config]. من ناحية أخرى، في السطر 1، تحتوي تهيئة الكائن config على سمة init-method. تشير هذه السمة إلى الأسلوب الخاص بالكائن الذي يجب تنفيذه بعد إنشاء مثيل للكائن. هنا، نحدد أنه بعد إنشاء مثيل للفئة [web.Config]، يجب تنفيذ طريقتها init. وهذه الطريقة هي كما يلي:


// طبقة الأعمال التي تم تهيئتها بواسطة Spring
  private IMetier metier;
  // قائمة الموظفين
  private List<Employe> employes;
  // الأخطاء
  private Exception initException;

  // منشئ
  public Config() {
  }

  // طريقة Spring لتهيئة الكائن
  public void init() {
    // يتم طلب قائمة الموظفين
    try {
      employes = metier.findAllEmployes();
    } catch (Exception ex) {
      initException = ex;
    }
  }

عند تنفيذ الطريقة init، يكون الحقل metier الخاص بالفئة قد تم إنشاء مثيل له بواسطة Spring. وبالتالي، فإن الطريقة init يمكنها الوصول إلى طبقة الأعمال الخاصة بالواجهة [IMetier] (السطر 2):


package metier;

import java.util.List;
import jpa.Employe;

public interface IMetier {
  // الحصول على كشف الراتب
  FeuilleSalaire calculerFeuilleSalaire(String SS, double nbHeuresTravaillées, int nbJoursTravaillés );
  // قائمة الموظفين
  List<Employe> findAllEmployes();
}
  • السطر 8: تتيح هذه الطريقة حساب راتب الموظف
  • السطر 10: تتيح هذه الطريقة الحصول على قائمة الموظفين

نلاحظ أن الطريقة init تطلب قائمة الموظفين من الطبقة [métier]. يتم تسجيل هذه القائمة في الحقل الموجود في السطر 4. وفي حالة حدوث استثناء، يتم تخزينه في الحقل الموجود في السطر 6.

وختامًا، يحتوي الكائن الفريد [Config] على:

  • مرجعًا إلى الطبقة [métier]
  • قائمة الموظفين

19.4. إنشاء طرق عرض Tiles

كما رأينا في ملف تكوين Struts، سيتم إنشاء طرق العرض بواسطة إطار العمل Tiles. لن نشرح سوى ما هو ضروري للغاية لكتابة تطبيقنا.

يتيح Tiles إنشاء طرق عرض انطلاقًا من صفحة رئيسية. هذه الصفحة، التي تسمى هنا [MasterPage.jsp]، ستكون تجميعًا للأجزاء JSP التالية:

Entete.jsp
 
Saisie.jsp
 
Simulation.jsp
 
Simulations.jsp
 
Exception.jsp
 
Erreur.jsp
 

تم تعريف أجزاء JSP هذه في مشروع NetBeans:

Image

يتيح لنا إطار العمل Tiles تحديد الأجزاء التي سيتم إدراجها في الصفحة الرئيسية.

الصفحة الرئيسية [MasterPage.jsp] هي كما يلي:


<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<%@ taglib uri="http://tiles.apache.org/tags-tiles" prefix="tiles" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
  "http://www.w3.org/TR/html4/loose.dtd">

<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <link href="styles.css" rel="stylesheet" type="text/css"/>
    <title>
      <tiles:insertAttribute name="titre" ignore="true" />
    </title>
    <s:head/>
  </head>
  <body background="<s:url value="/ressources/standard.jpg"/>">
    <tiles:insertAttribute name="entete" />
    <hr/>
    <tiles:insertAttribute name="saisie" />
    <tiles:insertAttribute name="simulation" />
    <tiles:insertAttribute name="exception" />
    <tiles:insertAttribute name="erreur" />
    <tiles:insertAttribute name="simulations" />
  </body>
</html>

الصفحة الرئيسية عبارة عن حاوية لأجزاء JSP. وهي هنا عبارة عن تجميع لستة أجزاء، وهي تلك الموجودة في الأسطر من 17 إلى 23. عند الإنشاء، قد يتراوح عدد الأجزاء المجمعة في الصفحة الرئيسية بين صفر و6 أجزاء. يتم التحكم في عملية الإنشاء هذه بواسطة الملف [WEB-INF/tiles.xml] الذي يحدد طرق العرض Tiles:


<?xml version="1.0" encoding="UTF-8" ?>
 
<!DOCTYPE tiles-definitions PUBLIC "-//Apache Software Foundation//DTD Tiles Configuration 2.0//EN"
 "http://tiles.apache.org/dtds/tiles-config_2_0.dtd">
 
<tiles-definitions>
 
<!-- الصفحة الرئيسية -->
  <definition name="masterPage" template="/MasterPage.jsp">
    <put-attribute name="entete" value="/Entete.jsp"/>
    <put-attribute name="titre"  value="Pam"/>
    <put-attribute name="saisie" value=""/>     
    <put-attribute name="simulation" value=""/>
    <put-attribute name="simulations" value=""/>
    <put-attribute name="exception" value=""/>     
    <put-attribute name="erreur" value=""/>     
  </definition>
 
<!-- طريقة عرض الإدخال -->
  <definition name="saisie" extends="masterPage">
    <put-attribute name="saisie"   value="/Saisie.jsp"/>     
  </definition>

  <!-- عرض المحاكاة -->
 <definition name="simulation" extends="saisie">
    <put-attribute name="simulation"   value="/Simulation.jsp"/>     
  </definition>

<!-- عرض عمليات المحاكاة -->
  <definition name="simulations" extends="masterPage">
    <put-attribute name="simulations"   value="/Simulations.jsp"/>     
  </definition>
  
<!-- عرض الاستثناء -->
  <definition name="exception" extends="masterPage">
    <put-attribute name="exception"   value="/Exception.jsp"/>     
  </definition>
 
<!-- عرض الخطأ -->
 <definition name="erreur" extends="masterPage">
    <put-attribute name="erreur"   value="/Erreur.jsp"/>     
  </definition>
</tiles-definitions>
  • يحدد الملف أعلاه ستة عروض Tiles تسمى: masterPage (السطر 9)، الإدخال (السطر 20)، المحاكاة (السطر 25)، المحاكاة (السطر 30)، الاستثناء (السطر 35)، الخطأ (السطر 40).
  • الأسطر 9-17: تحدد عرضًا يسمى masterPage (name) ومرتبطًا بالصفحة الرئيسية [MasterPage.jsp] (template). لقد رأينا أن صفحة JSP هذه تحدد ستة عروض فرعية. يجب أن تشير العرض Tiles المرتبط بالصفحة الرئيسية إلى جزء JSP المرتبط بكل عرض من العروض الفرعية الستة. ونلاحظ أن بعض العروض الفرعية تتلقى السلسلة الفارغة (value) كقيمة. لن يتم تضمين هذه العروض الفرعية في الصفحة الرئيسية [MasterPage.jsp]. وبالتالي، فإن العرض Tiles المسمى masterPage يتكون من الجزء الفرعي الوحيد [Entete.jsp].
  • الأسطر 20-22: تُعرّف عرضًا يُسمى saisie (name) ويُوسّع (extends) العرض المسمى masterPage الذي تمت إشارته سابقًا. وهذا يعني أنها تتضمن جميع تعريفات العرض masterPage. وتعريفها مكافئ لما يلي:

<definition name="saisie" template="/MasterPage.jsp">
    <put-attribute name="entete" value="/Entete.jsp"/>
    <put-attribute name="titre"  value="Pam"/>
    <put-attribute name="saisie" value=""/>     
    <put-attribute name="simulation" value=""/>
    <put-attribute name="simulations" value=""/>
    <put-attribute name="exception" value=""/>     
    <put-attribute name="erreur" value=""/>
    <put-attribute name="saisie" value="/Saisie.jsp"/>     
  </definition>

ونلاحظ أنها مرتبطة بصفحة JSP [MasterPage.jsp]، وبالتالي يجب أن تحدد العروض الفرعية الستة لتلك الصفحة. ونلاحظ أن التعريف الوارد في السطر 9 يلغي التعريف الوارد في السطر 4. وبالتالي، فإن العرض Tiles المسمى saisie يتكون من أجزاء JSP [Entete.jsp, Saisie.jsp]

وإذا واصلنا هذا المنطق، نحصل على الجدول التالي:

عرض Tiles
صفحات JSP
masterPage
Entete.jsp
saisie
Entete.jsp, Saisie.jsp
simulation
Entete.jsp, Saisie.jsp, Simulation.jsp
simulations
Entete.jsp, Simulations.jsp
exception
Entete.jsp, Exception.jsp
erreur
Entete.jsp, Erreur.jsp

19.5. ملفات الرسائل

تم توطين التطبيق. توجد الرسائل في الملفين [messages.properties] و [Formulaire.properties].

ملف [messages.properties] هو كما يلي:


Pam.titre=Calcul du salaire des assistantes maternelles
Pam.Erreurs.titre=Les erreurs suivantes se sont produites :
Pam.Erreurs.classe=Exception
Pam.Erreurs.message=Message
Pam.Erreur.libelle=L''erreur suivante s''est produite
Pam.Saisie.Heures.libell\u00e9=Heures travaill\u00e9es
Pam.Saisie.Jours.libell\u00e9=Jours travaill\u00e9s
Pam.Saisie.employ\u00e9=Employ\u00e9
Pam.BtnSalaire.libell\u00e9=Salaire
Pam.BtnEffacer.libell\u00e9=Effacer
Simulation.Infos.employe=Informations Employ\u00e9
Simulation.Employe.nom=Nom
Simulation.Employe.prenom=Pr\u00e9nom
Simulation.Employe.adresse=Adresse
Simulation.Employe.indice=Indice
Simulation.Employe.ville=Ville
Simulation.Employe.codePostal=Code Postal
Simulation.Infos.cotisations=Cotisations Sociales
Simulation.Cotisations.csgrds=CsgRds
Simulation.Cotisations.csgrds=Csgd
Simulation.Cotisations.retraite=Retraite
Simulation.Cotisations.secu=S\u00e9cu
Form.Infos.indemnites=Indemnit\u00e9s
Simulation.Indemnites.salaireHoraire=Salaire horaire
Simulation.Indemnites.entretienJour=Entretien/Jour
Simulation.Indemnites.repasJour=Repas/Jour
Simulation.Indemnites.cong\u00e9sPay\u00e9s=Cong\u00e9s pay\u00e9s
Simulation.Infos.Salaire=Salaire
Simulation.Salaire.salaireBase=Salaire de base
Simulation.Salaire.cotisationsSociales=Cotisations sociales
Simulation.Salaire.entretien=Indemnit\u00e9s d''entretien
Simulation.Salaire.repas=Indemnit\u00e9s de repas
Simulation.salaireNet=Salaire net
# التنسيقات
Format.heure = {0,time}
Format.nombre = {0,number,#0.0##}
Format.pourcent = {0,number,##0.00' %'}
Format.monnaie={0,number,##0.00' \u20ac'}
# قائمة عمليات المحاكاة
Pam.Simulations.titre=Liste des simulations
Pam.Simulations.num=Num\u00e9ro
Pam.Simulations.nom=Nom
Pam.Simulations.prenom=Pr\u00e9nom
Pam.Simulations.heures=Heures
Pam.Simulations.jours=Jours
Pam.Simulations.salairebase=Salaire de base
Pam.Simulations.indemnites=Indemnites
Pam.Simulations.cotisationsociales=Cotisations
Pam.Simulations.salairenet=Salaire
Pam.SimulationsVides.titre=La liste des simulations est vide
# القائمة
Menu.FaireSimulation=Faire la simulation
Menu.EffacerSimulation=Effacer la simulation
Menu.VoirSimulations=Voir les simulations
Menu.RetourFormulaire=Retour au formulaire de navigation
Menu.EnregistrerSimulation=Enregistrer la simulation
Menu.TerminerSession=Terminer la session
# رسالة الخطأ
Erreur.sessionexpiree=La session a expir\u00e9
Erreur.numSimulation=N\u00b0 de simulation incorrect
# خطأ في التحويل
xwork.default.invalid.fieldvalue=Valeur invalide pour le champ "{0}".

ملف [Formulaire.properties] هو التالي:


# لجعل القيم المكررة بالتنسيق المحلي
double.format={0,number,#0.00##}
# رسالة خطأ
joursTravaill\u00e9s.error=Tapez un nombre entier compris entre 1 et 31
heuresTravaill\u00e9es.error=Tapez un nombre r\u00e9el entre 0 et 300

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

تستخدم طرق العرض Tiles ورقة الأنماط التالية: [styles.css]:


.libelle{
  background-color: #ccffff;
  font-family: 'Times New Roman',Times,serif;
  font-size: 14px;
  font-weight: bold;;
  padding-right: 5px;
  padding-left: 5px;
  padding-bottom: 5px;
  padding-top: 5px;
}


.info{
  background-color: #99cc00;;
  padding-right: 5px;
  padding-left: 5px;
  padding-bottom: 5px;
  padding-top: 5px;
}

.titreInfos{
  background-color: #ffcc00
}

19.7. الطريقة الأولية

لدراسة التطبيق، سنعرضه انطلاقًا من الإجراءات المختلفة التي يقوم بها المستخدم. وسندرس في كل مرة الإجراء Struts الذي ينفذ هذا الإجراء، والعرض Tiles الذي يتم إرساله كرد.

في [struts.xml]، لدينا الإجراءات التالية:


  <!-- الإجراء الافتراضي -->
    <default-action-ref name="index" />
    <action name="index">
      <result type="redirectAction">
        <param name="actionName">Formulaire!input</param>
        <param name="namespace">/</param>
      </result>
    </action>
    <!-- إجراء النموذج -->
    <action name="Formulaire" class="web.actions.Formulaire">
      <result name="success" type="tiles">saisie</result>
      <result name="exception" type="tiles">exception</result>
      <result name="input" type="tiles">saisie</result>
      <result name="simulation" type="tiles">simulation</result>
</action>

  • الأسطر 2-8: الإجراء الافتراضي للتطبيق هو [Formulaire!input].

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


package web.actions;

...
public class Formulaire extends ActionSupport implements Preparable, SessionAware {

  // التكوين الذي تم تهيئته بواسطة Spring
  private Config config;
  // قائمة الموظفين
  private List<Employe> employes;
  // قائمة الأخطاء
  private List<Erreur> erreurs;
  // كشوف الرواتب
  private FeuilleSalaire feuilleSalaire;
  // البيانات المدخلة
  private String comboEmployesValue;
  private Double heuresTravaillees;
  private Integer joursTravailles;
  // الجلسة
  private Map<String, Object> session;
  // القائمة
  private Menu menu;

  @Override
  public void prepare() throws Exception {
    ...
  }

  @Override
  public String input() {
  ....
  }

  // حساب الراتب
  public String calculSalaire() {
 ...
    }
  }

  @Override
  public void validate() {
  ...
  }

  @Override
  public void setSession(Map<String, Object> map) {
    session = map;
  }

  // وظائف الاسترجاع والتعيين
  ...
}
  • السطر 4: تنفذ الإجراء [Formulaire] واجهة Preparable. تحتوي هذه الواجهة على طريقة واحدة فقط، وهي الطريقة prepare الموجودة في السطر 24. يتم تنفيذ هذه الطريقة مرة واحدة قبل أي طريقة أخرى في الإجراء. وتُستخدم عادةً لتهيئة نموذج الإجراء.

يتكون نموذج الإجراء من الأسطر 6-21:

  • السطر 7: يتم تهيئة الحقل config بواسطة Spring كما تم شرحه. ويتيح هذا الحقل الوصول إلى البيانات ذات النطاق على مستوى التطبيق:
  • مرجع على الطبقة [métier]
  • إشارة إلى قائمة الموظفين.
  • إشارة إلى الاستثناء الذي قد يكون حدث أثناء إنشاء مثيل الكائن [Config]
  • السطر 9: قائمة بالموظفين. ستُستخدم هذه القائمة لتغذية قائمة الاختيار المنسدلة الخاصة بالموظفين في الجزء [Saisie.jsp].
  • السطر 11: قائمة بالأخطاء. ستُستخدم هذه القائمة لتغذية الجزء [Erreur.jsp].
  • السطر 21: قائمة خيارات قائمة المقطع [Entete.jsp]

في [1]، يتم التحكم في روابط القائمة المعروضة بواسطة الحقل menu الخاص بالإجراء [Formulaire].

يتم تنفيذ الأسلوب prepare قبل الأسلوب input. وهو كما يلي:


  @Override
  public void prepare() throws Exception {
    // خطأ في التكوين؟
    Exception initException = config.getInitException();
    if (initException != null) {
      erreurs = new ArrayList<Erreur>();
      Throwable th = initException;
      while (th != null) {
        erreurs.add(new Erreur(th.getClass().getName(), th.getMessage()));
        th = th.getCause();
      }
    } else {
      employes = config.getEmployes();
    }
}
  • السطر 4: يتم استرداد الاستثناء في الكائن [Config] الذي تم إنشاء مثيل له بواسطة Spring
  • السطر 5: في حالة حدوث استثناء أثناء إنشاء مثيل الكائن [Config]، يتم تهيئة قائمة الأخطاء في السطر 11. الفئة [Erreur] هي كما يلي:

package web.entities;

import java.io.Serializable;

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

  // منشئ
  public Erreur(String classe, String message){
    this.setClasse(classe);
    this.message=message;
  }
  
  // دالات الاسترجاع والتعيين
...
}

تُستخدم الفئة لتخزين مكدس الاستثناءات:

  • السطر 11: فئة الاستثناء
  • السطر 12: رسالة الاستثناء

لنعد إلى الطريقة prepare:

  • السطر 13: يتم تخزين قائمة الموظفين الخاصة بالكائن [Config] في الحقل employes الخاص بالإجراء.

بمجرد تنفيذ الطريقة prepare، سيتم تنفيذ الطريقة input بدورها. وهي كما يلي:


  @Override
  public String input() {
    if (erreurs == null) {
      // القائمة
      menu = new Menu(true, false, false, true, false, true);
      return SUCCESS;
    } else {
      // القائمة
      menu = new Menu(false, false, false, false, false, false);
      return "exception";
    }
}

تقتصر الطريقة input على تحديد موضع قائمة خيارات القائمة المراد عرضها. الفئة [Menu] هي كما يلي:


package web.entities;

import java.io.Serializable;

public class Menu implements Serializable {
  // عناصر القائمة

  private boolean faireSimulation;
  private boolean effacerSimulation;
  private boolean enregistrerSimulation;
  private boolean voirSimulations;
  private boolean retourFormulaire;
  private boolean terminerSession;

  public Menu() {
  }

  public Menu(boolean faireSimulation, boolean effacerSimulation, boolean enregistrerSimulation, boolean voirSimulations, boolean retourFormulaire, boolean terminerSession) {
    this.faireSimulation = faireSimulation;
    this.effacerSimulation = effacerSimulation;
    this.enregistrerSimulation = enregistrerSimulation;
    this.voirSimulations = voirSimulations;
    this.retourFormulaire = retourFormulaire;
    this.terminerSession = terminerSession;
  }
  
  // وظائف الحصول والتعيين
...  
}
  • الأسطر 8-13: هناك 6 روابط محتملة في القائمة
  • الأسطر 18-25: يتيح منشئ الفئة تحديد الروابط التي يجب عرضها وتلك التي لا يجب عرضها.

يتم عرض روابط القائمة في [Entete.jsp]، وهو جزء JSP موجود في جميع طرق العرض Tiles. سيكون لكل إجراء حقل menu للتحكم في عرض قائمة [Entete.jsp].

لنعد إلى الطريقة input:


  @Override
  public String input() {
    if (erreurs == null) {
      // القائمة
      menu = new Menu(true, false, false, true, false, true);
      return SUCCESS;
    } else {
      // القائمة
      menu = new Menu(false, false, false, false, false, false);
      return "exception";
    }
}
  • الأسطر 3-6: إذا كانت قائمة الأخطاء فارغة، فسيتم عرض القائمة [Faire la simulation, Voir les simulations, Terminer la session] وإرجاع المفتاح input.
  • الأسطر 9-10: إذا كانت قائمة الأخطاء غير فارغة، فسيكون القائمة فارغًا وسيتم إرجاع المفتاح exception.

لنعد إلى تكوين الإجراء [Formulaire] في [struts.xml]:


    <!-- إجراء النموذج -->
    <action name="Formulaire" class="web.actions.Formulaire">
      <result name="success" type="tiles">saisie</result>
      <result name="exception" type="tiles">exception</result>
      <result name="input" type="tiles">saisie</result>
      <result name="simulation" type="tiles">simulation</result>
</action>
  • السطر 5: يعرض المفتاح input عرض «Tiles» المسمى saisie
  • السطر 4: يعرض المفتاح exception عرض "Tiles" المسمى exception

لنبدأ بعرض Tiles المسمى saisie. وهو يتألف من شرائح JSP [Entete.jsp] و [Saisie.jsp].

المقطع [Entete.jsp] هو كما يلي:

Image

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


<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>

<table>
  <tr>
    <td><h1><s:text name="Pam.titre"/></h1></td>
    <td>
      <s:if test="menu.faireSimulation">
        |<a href="javascript:doSimulation()"><s:text name="Menu.FaireSimulation"/></a><br/>
      </s:if>
      <s:if test="menu.effacerSimulation">
        |<a href="<s:url action="Formulaire!input"/>"><s:text name="Menu.EffacerSimulation"/></a><br/>
      </s:if>
      <s:if test="menu.voirSimulations">
        |<a href="<s:url action="VoirSimulations"/>"><s:text name="Menu.VoirSimulations"/></a><br/>
      </s:if>
      <s:if test="menu.retourFormulaire">
        |<a href="<s:url action="RetourFormulaire"/>"><s:text name="Menu.RetourFormulaire"/></a><br/>
      </s:if>
      <s:if test="menu.enregistrerSimulation">
        |<a href="<s:url action="EnregistrerSimulation"/>"><s:text name="Menu.EnregistrerSimulation"/></a><br/>
      </s:if>
      <s:if test="menu.terminerSession">
        |<a href="<s:url action="TerminerSession"/>"><s:text name="Menu.TerminerSession"/></a><br/>
      </s:if>
    </td>
  </tr>
</table>
  • الأسطر 8-25: عرض الروابط الستة في القائمة [إجراء المحاكاة (الأسطر 8-10)، مسح المحاكاة (الأسطر 11-13)، عرض المحاكاة (الأسطر 14-16)، العودة إلى النموذج (الأسطر 17-19)، حفظ المحاكاة (الأسطر 20-22)، إنهاء الجلسة (الأسطر 23-25).
  • الأسطر 8 و11 و14 و17 و20 و23: يتم التحكم في عرض الروابط من خلال الحقل menu الخاص بالإجراء الحالي.

تجدر الإشارة إلى أن المقتطف [Entete.jsp] يعرض جدول HTML (الأسطر 4-28) ولكنه ليس صفحة HTML كاملة. ولا ينبغي نسيان أن جميع طرق العرض الخاصة بالتطبيق يتم إدراجها في الصفحة الرئيسية التالية [MasterPage.jsp]:


<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<%@ taglib uri="http://tiles.apache.org/tags-tiles" prefix="tiles" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
  "http://www.w3.org/TR/html4/loose.dtd">

<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <link href="styles.css" rel="stylesheet" type="text/css"/>
    <title>
      <tiles:insertAttribute name="titre" ignore="true" />
    </title>
    <s:head/>
  </head>
  <body background="<s:url value="/ressources/standard.jpg"/>">
    <tiles:insertAttribute name="entete" />
    <hr/>
    <tiles:insertAttribute name="saisie" />
    <tiles:insertAttribute name="simulation" />
    <tiles:insertAttribute name="exception" />
    <tiles:insertAttribute name="erreur" />
    <tiles:insertAttribute name="simulations" />
  </body>
</html>

يتم إدراج المقطع [Entete.jsp] في السطر 17، داخل صفحة HTML عادية.

يتم إدراج المقطع [Saisie.jsp] في السطر 19. وهذه هي الواجهة التالية:

Image

رمز المقطع [Saisie.jsp] هو التالي:


<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<%@ taglib uri="http://tiles.apache.org/tags-tiles" prefix="tiles" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
  "http://www.w3.org/TR/html4/loose.dtd">


<script language="javascript" type="text/javascript">
  function doSimulation(){
    // إرسال النموذج
    document.forms['Saisie'].elements['action'].name='action:Formulaire!calculSalaire'
    document.forms['Saisie'].submit();
  }  
</script>

<!-- إدخال المعلومات -->
<s:form name="Saisie" id="Saisie">
  <s:select name="comboEmployesValue" list="employes" listKey="SS" listValue="prenom+' ' +nom" key="Pam.Saisie.employé"/>
  <s:textfield name="heuresTravaillees" key="Pam.Saisie.Heures.libellé" value="%{#parameters['heuresTravaillees']!=null ? #parameters['heuresTravaillees'] : heuresTravaillees==null ? '' : getText('double.format',{heuresTravaillees})}"/>
  <s:textfield name="joursTravailles" key="Pam.Saisie.Jours.libellé" value="%{#parameters['joursTravailles']!=null ? #parameters['joursTravailles'] : joursTravailles==null ? '' : joursTravailles}"/>
  <input type="hidden" name="action"/>
</s:form>
  • السطر 17: النموذج لا يحتوي على السمة action. بشكل افتراضي، لدينا action='Formulaire'.
  • السطر 18: عرض القائمة المنسدلة للموظفين. يتم توفير محتوى القائمة المنسدلة (السمة list) من الحقل employes الخاص بالإجراء الحالي. وسيكون السمة value الخاصة بالخيارات هو الرقم SS الخاص بالموظفين (السمة listKey). سيكون النص المعروض لكل خيار هو الاسم الأول واسم العائلة للموظف (السمة listValue). سيتم إدراج رقم الموظف SS المحدد في القائمة المنسدلة في الحقل [Formulaire].comboEmployesvalue (السمة name).
  • السطر 19: حقل إدخال ساعات العمل. القيمة المعروضة (السمة value) هي قيمة الحقل heuresTravaillees الخاص بالإجراء [Formulaire] بالصيغة التالية (Formulaire.properties):

double.format={0,number,#0.00##}

سيتم إدراج القيمة في الحقل [Formulaire].heuresTravaillees (السمة name).

  • السطر 20: حقل إدخال أيام العمل. القيمة المعروضة (السمة value) هي قيمة الحقل joursTravailles الخاص بالإجراء [Formulaire].

سيتم ترحيل القيمة إلى الحقل [Formulaire].joursTravailles (السمة name).

وفي النهاية، فإن عرض «Tiles» saisie الذي يظهر عند بدء التشغيل في حالة عدم وجود أخطاء هو كما يلي:

Image

لنعد إلى تكوين الإجراء [Formulaire]:


    <!-- إجراء النموذج -->
    <action name="Formulaire" class="web.actions.Formulaire">
      <result name="success" type="tiles">saisie</result>
      <result name="exception" type="tiles">exception</result>
      <result name="input" type="tiles">saisie</result>
      <result name="simulation" type="tiles">simulation</result>
</action>

لقد رأينا أن الإجراء [Formulaire].input يمكنه أيضًا إرجاع المفتاح exception من السطر 4. في هذه الحالة، يتم عرض عرض «Tiles» المسمى exception. ويتكون هذا العرض من المقتطفات [Entete.jsp] و [Exception.jsp]. لقد عرضنا سابقًا الجزء [Entete.jsp]. أما الجزء [Exception.jsp] فهو كما يلي:

Image

هذه هي صفحة البداية للإصدار 2 من التطبيق عندما لم يتم تشغيل نظام إدارة قواعد البيانات (SGBD). كود JSP للمقطع [Erreur.jsp] هو كما يلي:


<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>

<h2><s:text name="Pam.Erreurs.titre"/></h2>
<table>
  <tr class="titreInfos">
    <th><s:text name="Pam.Erreurs.classe"/></th>
    <th><s:text name="Pam.Erreurs.message"/></th>
  </tr>
  <s:iterator value="erreurs">
    <tr>
      <td class="libelle"><s:property value="classe"/></td>
      <td class="info"><s:property value="message"/></td>
    </tr>
  </s:iterator>
</table>
  • الأسطر 10-14: متكرر على المجموعة List<Erreur> erreurs الخاصة بالإجراء [Formulaire]. نتذكر أنه في حالة حدوث خطأ، تم تخزين مكدس من الاستثناءات فيها.

19.8. إجراء محاكاة

بمجرد الحصول على شاشة البدء، يمكن إجراء حساب الراتب عبر الرابط [Faire une simulation].

19.8.1. التحقق من صحة البيانات المدخلة

لنأخذ التسلسل التالي بعين الاعتبار:

  • في [1]، إدخال خاطئ
  • إلى [2]، وهي الرد المرسل.

لننظر إلى تكوين الإجراء [Formulaire] في [struts.xml]:


    <!-- إجراء النموذج -->
    <action name="Formulaire" class="web.actions.Formulaire">
      <result name="success" type="tiles">saisie</result>
      <result name="exception" type="tiles">exception</result>
      <result name="input" type="tiles">saisie</result>
      <result name="simulation" type="tiles">simulation</result>
</action>

من المعروف أنه في حالة حدوث خطأ في التحقق من الصحة، يقوم معترض التحقق من الصحة بإرجاع المفتاح input. وبالتالي، يتم إرجاع عرض Tiles saisie. تؤدي عملية التحقق من الصحة إلى إرفاق رسائل خطأ بالحقول التي تحتوي على أخطاء.

يتم التحقق من صحة الإجراء [Formulaire] بواسطة الملف [Formulaire-validation.xml] التالي:


<!DOCTYPE validators PUBLIC "-//OpenSymphony Group//XWork Validator 1.0.2//EN" "http://www.opensymphony.com/xwork/xwork-validator-1.0.2.dtd">


<validators>
  
  <field name="heuresTravaillees" >
    <field-validator type="required" short-circuit="true">
      <message key="heuresTravaillées.error"/>
    </field-validator>
    
    <field-validator type="conversion" short-circuit="true">
      <message key="heuresTravaillées.error"/>
    </field-validator>
    
    <field-validator type="double" short-circuit="true">
      <param name="minInclusive">0</param>
      <param name="maxInclusive">300</param>
      <message key="heuresTravaillées.error"/>
    </field-validator>
  </field>
  
  <field name="joursTravailles" >
    <field-validator type="required" short-circuit="true">
      <message key="joursTravaillés.error"/>
    </field-validator>
    
    <field-validator type="conversion" short-circuit="true">
      <message key="joursTravaillés.error"/>
    </field-validator>
    
    <field-validator type="int" short-circuit="true">
      <param name="min">0</param>
      <param name="max">31</param>
      <message key="joursTravaillés.error"/>
    </field-validator>
  </field>
  
</validators>
  • تتحقق الأسطر من 6 إلى 20 من أن الحقل heuresTravaillees هو عدد حقيقي يقع ضمن النطاق [0,300].
  • تتحقق الأسطر من 22 إلى 36 من أن الحقل joursTravailles هو عدد صحيح يقع ضمن النطاق [0,31].

لنعد إلى تكوين الإجراء [Formulaire] في [struts.xml]:


    <!-- إجراء النموذج -->
    <action name="Formulaire" class="web.actions.Formulaire">
      ...
      <result name="input" type="tiles">saisie</result>
</action>

نعلم أنه في حالة حدوث خطأ في التحقق من الصحة، يقوم معترض التحقق من الصحة بإرجاع المفتاح input. وبالتالي، يتم إرجاع عرض Tiles saisie.

تجدر الإشارة إلى أن هذه العرضة تتكون من الأجزاء [Entete.jsp] و [Saisie.jsp]، حيث يحتوي الجزء [Entete.jsp] على عنوان وخيارات للاختيار، بينما يحتوي الجزء [Saisie.jsp] على نموذج الإدخال. في حالة وجود أخطاء في الإدخال، فإن عملية التحقق من الصحة تجعل الحقول التي تحتوي على أخطاء مصحوبة برسائل خطأ، كما تعرض قيمها الخاطئة. ولا يشارك الجزء [Entete.jsp] بأي شكل من الأشكال في عملية التحقق من الصحة. لنلقِ نظرة على كوده:


<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>

<table>
  <tr>
    <td><h1><s:text name="Pam.titre"/></h1></td>
    <td>
      <s:if test="menu.faireSimulation">
        |<a href="javascript:doSimulation()"><s:text name="Menu.FaireSimulation"/></a><br/>
      </s:if>
      <s:if test="menu.effacerSimulation">
        |<a href="<s:url action="Formulaire!input"/>"><s:text name="Menu.EffacerSimulation"/></a><br/>
      </s:if>
      <s:if test="menu.voirSimulations">
        |<a href="<s:url action="VoirSimulations"/>"><s:text name="Menu.VoirSimulations"/></a><br/>
      </s:if>
      <s:if test="menu.retourFormulaire">
        |<a href="<s:url action="RetourFormulaire"/>"><s:text name="Menu.RetourFormulaire"/></a><br/>
      </s:if>
      <s:if test="menu.enregistrerSimulation">
        |<a href="<s:url action="EnregistrerSimulation"/>"><s:text name="Menu.EnregistrerSimulation"/></a><br/>
      </s:if>
      <s:if test="menu.terminerSession">
        |<a href="<s:url action="TerminerSession"/>"><s:text name="Menu.TerminerSession"/></a><br/>
      </s:if>
    </td>
  </tr>
</table>

يتم تكوين الروابط الستة بواسطة الحقل menu في النموذج (الأسطر 8 و11 و14 و17 و20 و23). عند حدوث خطأ، لا يتم تحديث هذا النموذج بواسطة الإجراء، وبالتالي تظهر صفحة بدون قائمة. لحل هذه المشكلة، تحتوي الفئة [Formulaire] على الطريقة التالية: validate:


package web.actions;

import com.opensymphony.xwork2.ActionSupport;
...
public class Formulaire extends ActionSupport implements Preparable, SessionAware {

  ...
  // القائمة
  private Menu menu;

  @Override
  public void prepare() throws Exception {
  ...
  }

  @Override
  public String input() {
  ...
  }

  // حساب الراتب
  public String calculSalaire() {
  ...
  }

  @Override
  public void validate() {
    // هل توجد أخطاء؟
    if (!getFieldErrors().isEmpty()) {
      // القائمة
      menu = new Menu(true, false, false, true, false, true);
    }
  }

  // متغيرات القراءة والكتابة
  ...
}
  • السطر 27: من المعروف أنه عند وجودها، يتم تنفيذ الأسلوب validate بواسطة عملية التحقق من الصحة. ونستغل هذه الفرصة لتحديث القائمة الموجودة في السطر 4 والتي تشكل جزءًا من نموذج المقطع [Entete.jsp].
  • الأسطر 29-32: في حالة وجود أخطاء في التحقق من الصحة، يتم ضبط القائمة لإعادة عرض عرض Tiles saisie. أما في حالة عدم وجود أخطاء في التحقق من الصحة، فلا يتم اتخاذ أي إجراء. وتتولى الطريقة calculSalaire مسؤولية إنشاء نموذج العرض المراد عرضه.

19.8.2. حساب الراتب

لنعد إلى كود JSP الخاص بالرأس:


<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>

<table>
  <tr>
    <td><h1><s:text name="Pam.titre"/></h1></td>
    <td>
      <s:if test="menu.faireSimulation">
        |<a href="javascript:doSimulation()"><s:text name="Menu.FaireSimulation"/></a><br/>
      </s:if>
      ...
    </td>
  </tr>
</table>
  • السطر 9: عندما ينقر المستخدم على الرابط [Faire la simulation]، يتم تنفيذ دالة جافا سكريبت doSimulation. وقد تم تعريف هذه الدالة في المقتطف [Saisie.jsp]:

<script language="javascript" type="text/javascript">
  function doSimulation(){
    // إرسال النموذج
    document.forms['Saisie'].elements['action'].name='action:Formulaire!calculSalaire'
    document.forms['Saisie'].submit();
  }
</script>

<!-- إدخال المعلومات -->
<s:form name="Saisie" id="Saisie">
  <s:select name="comboEmployesValue" list="employes" listKey="SS" listValue="prenom+' ' +nom" key="Pam.Saisie.employé"/>
  <s:textfield name="heuresTravaillees" key="Pam.Saisie.Heures.libellé" value="%{#parameters['heuresTravaillees']!=null ? #parameters['heuresTravaillees'] : heuresTravaillees==null ? '' : getText('double.format',{heuresTravaillees})}"/>
  <s:textfield name="joursTravailles" key="Pam.Saisie.Jours.libellé" value="%{#parameters['joursTravailles']!=null ? #parameters['joursTravailles'] : joursTravailles==null ? '' : joursTravailles}"/>
  <input type="hidden" name="action"/>
</s:form>
  • السطر 14: سيتم إرسال حقل مخفي باسم action إلى الإجراء [Formulaire]. سيسمح لنا هذا الحقل بتحديد الإجراء والطريقة اللذين يجب تنفيذهما في POST من النموذج. ربما نتذكر من الأمثلة السابقة أنه يمكن تحديد هذين العنصرين في معلمة باسم action:Action!méthode. ولا يهم قيمة هذه المعلمة، المهم هو وجودها.
  • الأسطر 2-6: دالة جافا سكريبت التي يتم تنفيذها عندما ينقر المستخدم على الرابط [Faire la simulation] في الجزء [Entete.jsp].
  • السطر 4: يتم تغيير السمة name للحقل المخفي action. ويتم التأكد من أن تكون بالصيغة action:Action!méthode التي يتوقعها Struts.
  • السطر 5: يتم إرسال النموذج المسمى Saisie الموجود في السطر 5. وبالتالي يتم إرسال سلسلة المعلمات التالية:
comboEmployesValue=SS1&heuresTravaillees=xx&joursTravailles=yy&action:Formulaire!calculSalaire

SS1: رقم INSEE للموظف المحدد في القائمة المنسدلة

heuresTravaillees: عدد ساعات العمل

joursTravailles: عدد أيام العمل

action:Formulaire!calculSalaire: سيتم إرسال العناصر المذكورة أعلاه إلى الإجراء [Formulaire] ثم سيتم تنفيذ الطريقة calculSalaire الخاصة بهذا الإجراء.

الطريقة [Formulaire].calculSalaire هي كما يلي:


// حساب الراتب
  public String calculSalaire() {
    try {
      // حساب الراتب
      feuilleSalaire = config.getMetier().calculerFeuilleSalaire(comboEmployesValue, heuresTravaillees, joursTravailles);
      // إدراج المحاكاة في الجلسة
      session.put("simulation", new Simulation(0, "" + heuresTravaillees, "" + joursTravailles, feuilleSalaire));
      // القائمة
      menu = new Menu(true, true, true, true, false, true);
      // انتهى
      return "simulation";
    } catch (Throwable th) {
      ...
    }
  }
  • السطر 5: يُطلب حساب كشف الراتب من الطبقة [métier]
  • السطر 7: يتم وضع كائن من النوع Simulation في جلسة عمل المستخدم. في الواقع، قد نحتاج إليه عند إجراء استعلام لاحق. الفئة [Simulation] هي كما يلي:

package web.entities;

import java.io.Serializable;
import metier.FeuilleSalaire;

public class Simulation implements Serializable{
  
  public Simulation() {
  }

  // حقول المحاكاة
  private Integer num;
  private FeuilleSalaire feuilleSalaire;
  private String heuresTravaillées;
  private String joursTravaillés;
  
  // منشئ
  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();
  }

  // أدوات الاسترجاع والتعيين
...
}
  • السطر 12: رقم المحاكاة. يتم زيادته مع كل محاكاة جديدة يتم تسجيلها.
  • السطر 13: كشف راتب الموظف
  • السطر 14: عدد ساعات العمل
  • السطر 15: عدد أيام العمل
  • السطر 25: تعرض الطريقة getIndemnites إجمالي تعويضات الموظف

سنرى أن الفئة [Simulation] هي نموذج لجزء [Simulations.jsp] الذي يعرض جميع عمليات المحاكاة التي تم إجراؤها.

العودة إلى الطريقة [Formulaire].calculSalaire:


// حساب الراتب
  public String calculSalaire() {
    try {
      // حساب الراتب
      feuilleSalaire = config.getMetier().calculerFeuilleSalaire(comboEmployesValue, heuresTravaillees, joursTravailles);
      // إدراج المحاكاة في الجلسة
      session.put("simulation", new Simulation(0, "" + heuresTravaillees, "" + joursTravailles, feuilleSalaire));
      // القائمة
      menu = new Menu(true, true, true, true, false, true);
      // انتهى
      return "simulation";
    } catch (Throwable th) {
 ...
  }
  • السطر 9: تحديث القائمة
  • السطر 11: إعادة توجيه مفتاح التنقل simulation.

العودة إلى تكوين الإجراء [Formulaire]:


    <!-- إجراء النموذج -->
    <action name="Formulaire" class="web.actions.Formulaire">
         ...
      <result name="simulation" type="tiles">simulation</result>
</action>

يُظهر السطر 4 أن مفتاح التنقل simulation يعرض طريقة العرض Tiles المسماة simulation. وتتكون هذه الطريقة من أجزاء JSP التالية: [Entete, Saisie, Simulation].

والعرض النهائي هو كما يلي:

  • في [1]، الجزء [Entete.jsp]
  • في [2]، الجزء [Saisie.jsp]
  • إلى [3]، والجزء [Simulation.jsp]. تجدر الإشارة إلى أن كشف الراتب المعروض هو كشف الراتب الوهمي الذي تم عرضه بواسطة الطبقة [metier].

وقد تم عرض المقطعين الأولين بالفعل. أما المقطع [Simulation.jsp] فهو كما يلي:


<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>

<hr/>
<!-- معلومات الموظف -->
<span class="titreInfos">
  <s:text  name="Simulation.Infos.employe"/>
</span>
<br/><br/>
<table>
  <!-- السطر 1 -->
  <tr>
    <th class="libelle">
      <s:text name="Simulation.Employe.nom"/>
    </th>
    <th class="libelle">
      <s:text name="Simulation.Employe.prenom"/>
    </th>
    <th class="libelle">
      <s:text name="Simulation.Employe.adresse"/>
    </th>
  </tr>
  <!-- السطر 2 -->
  <tr>
    <td class="info">   
      <s:property value="feuilleSalaire.employe.nom"/>      
    </td>
    <td class="info">
      <s:property value="feuilleSalaire.employe.prenom"/>
    </td>
    <td class="info">
      <s:property value="feuilleSalaire.employe.adresse"/>
    </td>
</table>
<table>
  <!-- السطر 1 -->
  <tr>
    <th class="libelle"><s:text name="Simulation.Employe.ville"/></th>
    <th class="libelle">
      <s:text name="Simulation.Employe.codePostal"/>
    </th>
    <th class="libelle">
      <s:text name="Simulation.Employe.indice"/>
    </th>
  </tr>
  <!-- السطر 2 -->
  <tr>
    <td class="info">
      <s:property value="feuilleSalaire.employe.ville"/>
    </td>
    <td class="info">
      <s:property value="feuilleSalaire.employe.codePostal"/>
    </td>
    <td class="info">
      <s:property value="feuilleSalaire.employe.indemnite.indice"/>
    </td>
</table>
<!-- معلومات الاشتراكات -->
<br/>
<span class="titreInfos">
  <s:text name="Simulation.Infos.cotisations"/>
</span>

<br/><br/>
<table>
  <!-- السطر 1 -->
  <tr>
    <th class="libelle">
      <s:text name="Simulation.Cotisations.csgrds"/>
    </th>
    <th class="libelle">
      <s:text name="Simulation.Cotisations.csgrds"/>
    </th>
    <th class="libelle">
      <s:text name="Simulation.Cotisations.retraite"/>
    </th>
    <th class="libelle">
      <s:text name="Simulation.Cotisations.secu"/>
    </th>
  </tr>
  <!-- السطر 2 -->
  <tr>
    <td class="info">
      <s:text name="Format.pourcent">
        <s:param value="feuilleSalaire.cotisation.csgrds"/>
      </s:text>
    </td>
    <td  class="info">
      <s:text name="Format.pourcent">
        <s:param value="feuilleSalaire.cotisation.csgd"/>
      </s:text>
    </td>
    <td class="info">
      <s:text name="Format.pourcent">
        <s:param value="feuilleSalaire.cotisation.retraite"/>
      </s:text>
    </td>
    <td class="info">
      <s:text name="Format.pourcent">
        <s:param value="feuilleSalaire.cotisation.secu"/>
      </s:text>
    </td>
</table>
<!-- معلومات التعويضات -->
<br/>
<span class="titreInfos">
  <s:text name="Form.Infos.indemnites"/>
</span>
<br/><br/>
<table>
  <!-- السطر 1 -->
  <tr>
    <th class="libelle">
      <s:text name="Simulation.Indemnites.salaireHoraire"/>
    </th>
    <th class="libelle">
      <s:text name="Simulation.Indemnites.entretienJour"/>
    </th>
    <th class="libelle">
      <s:text name="Simulation.Indemnites.repasJour"/>
    </th>
    <th class="libelle">
      <s:text name="Simulation.Indemnites.congésPayés"/>
    </th>
  </tr>
  <!-- السطر 2 -->
  <tr>
    <td class="info">
      <s:text name="Format.monnaie">
        <s:param value="feuilleSalaire.employe.indemnite.baseHeure"/>
      </s:text>
    </td>
    </td>
    <td class="info">
      <s:text name="Format.monnaie">
        <s:param value="feuilleSalaire.employe.indemnite.entretienJour"/>
      </s:text>
    </td>
    <td class="info">
      <s:text name="Format.monnaie">
        <s:param value="feuilleSalaire.employe.indemnite.repasJour"/>
      </s:text>
    </td>
    <td class="info">
      <s:text name="Format.monnaie">
        <s:param value="feuilleSalaire.employe.indemnite.indemnitesCP"/>
      </s:text>
    </td>
  </tr>
</table>
<!-- معلومات الراتب -->
<br/>
<span class="titreInfos">
  <s:text name="Simulation.Infos.Salaire"/>
</span>
<br/><br/>
<table>
  <!-- السطر 1 -->
  <tr>
    <th class="libelle">
      <s:text name="Simulation.Salaire.salaireBase"/>
    </th>
    <th class="libelle">
      <s:text name="Simulation.Salaire.cotisationsSociales"/>
    </th>
    <th class="libelle">
      <s:text name="Simulation.Salaire.entretien"/>
    </th>
    <th class="libelle">
      <s:text name="Simulation.Salaire.repas"/>
    </th>
  </tr>

  <!-- السطر 2 -->
  <tr>
    <td class="info">
      <s:text name="Format.monnaie">
        <s:param value="feuilleSalaire.elementsSalaire.salaireBase"/>
      </s:text>
    </td>
    <td class="info">
      <s:text name="Format.monnaie">
        <s:param value="feuilleSalaire.elementsSalaire.cotisationsSociales"/>
      </s:text>
    </td>
    <td class="info">
      <s:text name="Format.monnaie">
        <s:param value="feuilleSalaire.elementsSalaire.indemnitesEntretien"/>
      </s:text>
    </td>
    <td class="info">
      <s:text name="Format.monnaie">
        <s:param value="feuilleSalaire.elementsSalaire.indemnitesRepas"/>
      </s:text>
    </td>
  </tr>
</table>
<!-- الراتب الصافي-->
<br/>
<table>
  <tr>
    <td class="libelle">
      <s:text name="Simulation.salaireNet"/>
    <td></td>
    <td class="info">
      <s:text name="Format.monnaie">
        <s:param value="feuilleSalaire.elementsSalaire.salaireNet"/>
      </s:text>
    </td>
  </tr>
</table>

إنها طويلة... لكنها بسيطة من الناحية الوظيفية. يعرض هذا الجزء الخصائص المختلفة للحقل [Formulaire].feuilleSalaire الذي يمثل كشف راتب الموظف.

العودة إلى الأسلوب [Formulaire].calculSalaire:


// حساب الراتب
  public String calculSalaire() {
    try {
      ...
      return "simulation";
    } catch (Throwable th) {
      erreurs = new ArrayList<Erreur>();
      while (th != null) {
        erreurs.add(new Erreur(th.getClass().getName(), th.getMessage()));
        th = th.getCause();
      }
      // القائمة
      menu = new Menu(false, false, false, false, true, true);
      return "exception";
    }
  }

قد يحدث خطأ في حساب الراتب. ويحدث ذلك على وجه الخصوص في حالة انقطاع الاتصال بنظام إدارة قواعد البيانات (Sgbd). في هذه الحالة، يتم التعامل مع الاستثناء الذي يحدث. وقد واجهنا هذه الحالة بالفعل أثناء دراسة الطريقة [Formulaire].input.

  • الأسطر 7-11: يتم إنشاء قائمة بالكائنات Erreur من مكدس الاستثناءات
  • السطر 13: يتم تعيين القائمة
  • السطر 14: يتم إرجاع المفتاح exception.

سيؤدي المفتاح exception إلى عرض طريقة العرض Tiles exception:


    <!-- إجراء النموذج -->
    <action name="Formulaire" class="web.actions.Formulaire">
      <result name="exception" type="tiles">exception</result>
      ...
</action>

تم عرض طريقة العرض Tiles هذه من قبل. وتبدو كما يلي:

Image

19.9. حفظ محاكاة

بعد إجراء محاكاة، قد يرغب المستخدم في حفظها في الجلسة.

  • في [1]، يتم تسجيل المحاكاة
  • في [2]، يتم عرض الاستجابة التي تتضمن قائمة بالمحاكاة التي تم إجراؤها بالفعل مع إضافة المحاكاة الجديدة إليها

الرابط [Enregistrer la simulation] موجود في المقطع [Entete.jsp]:


<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>

<table>
  <tr>
    <td><h1><s:text name="Pam.titre"/></h1></td>
    <td>
      ...
      <s:if test="menu.enregistrerSimulation">
        |<a href="<s:url action="EnregistrerSimulation"/>"><s:text name="Menu.EnregistrerSimulation"/></a><br/>
      </s:if>
      ...
    </td>
  </tr>
</table>

ونلاحظ أن النقر على الرابط يؤدي إلى تنفيذ الإجراء [EnregistrerSimulation]. وقد تم تكوين هذا الإجراء في الملف [struts.xml] على النحو التالي:


   <!-- إجراء EnregistrerSimulation -->
    <action name="EnregistrerSimulation" class="web.actions.Enregistrer" method="execute">
      <result name="error" type="tiles">erreur</result>
      <result name="simulations" type="tiles">simulations</result>
</action>
  • السطر 1: الإجراء [EnregistrerSimulation] مرتبط بالفئة [Enregistrer] وطريقتها execute.

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


package web.actions;

...
public class Enregistrer extends ActionSupport implements SessionAware {

  // الجلسة
  private Map<String, Object> session;
  // القائمة
  private Menu menu;

  @Override
  public void setSession(Map<String, Object> session) {
    this.session = session;
  }

  // تنفيذ الإجراء
  public String execute() {
    // استرداد آخر محاكاة في الجلسة
    Simulation simulation = (Simulation) session.get("simulation");
    if (simulation == null) {
      return ERROR;
    }
    ...
  }

  // وظائف الاسترجاع والتعيين
  ...
}
  • السطر 4: نظرًا لأن الإجراء يحتاج إلى الوصول إلى الجلسة، فإنه ينفذ الواجهة SessionAware.
  • السطر 7: الجلسة
  • السطر 9: القائمة

عندما يتم إنشاء مثيل الإجراء [Enregistrer]، يتم تنفيذ أسلوبه execute. تجدر الإشارة إلى أن مهمته هي وضع آخر محاكاة في الجلسة. ستُضاف هذه المحاكاة إلى قائمة المحاكاة التي تم إجراؤها بالفعل، والتي يتم الاحتفاظ بها أيضًا في الجلسة.

  • السطر 19: يتم استرداد آخر محاكاة تم وضعها في الجلسة.
  • الأسطر 20-22: إذا لم يتم العثور عليها، فهذا يعني على الأرجح أن الجلسة قد انتهت صلاحيتها. في الواقع، لا تدوم هذه الجلسة سوى لفترة معينة يمكن تحديدها في الملف [web.xml] الذي يقوم بتكوين التطبيق.
  • السطر 21: يتم إرجاع المفتاح error.

العودة إلى تكوين الإجراء [EnregistrerSimulation]:


   <!-- الإجراء EnregistrerSimulation -->
    <action name="EnregistrerSimulation" class="web.actions.Enregistrer" method="execute">
      <result name="error" type="tiles">erreur</result>
      <result name="simulations" type="tiles">simulations</result>
</action>

نلاحظ أن المفتاح error (السطر 3) يؤدي إلى عرض طريقة العرض Tiles المسماة erreur. وتتكون هذه العروض من الأجزاء [Entete.jsp] و [Erreur.jsp] وتبدو كما يلي:

Image

الجزء [Erreur.jsp] هو التالي:


<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>

<h2><s:text name="Pam.Erreur.libelle"/></h2>
<h4><s:text name="Erreur.sessionexpiree"/></h4>

العودة إلى الطريقة [Enregistrer].execute:


// تنفيذ الإجراء
  public String execute() {
    // يتم استرداد آخر محاكاة في الجلسة
    Simulation simulation = (Simulation) session.get("simulation");
    if (simulation == null) {
      return ERROR;
    }
    // استرداد رقم آخر محاكاة
    Integer numDerniereSimulation = (Integer) session.get("numDerniereSimulation");
    if (numDerniereSimulation == null) {
      numDerniereSimulation = 0;
    }
    // زيادة الرقم
    numDerniereSimulation++;
    // إعادة إدخال الرقم الجديد في الجلسة
    session.put("numDerniereSimulation", numDerniereSimulation);
    // يتم استرداد قائمة عمليات المحاكاة
    List<Simulation> simulations = (List<Simulation>) session.get("simulations");
    if (simulations == null) {
      simulations = new ArrayList<Simulation>();
      session.put("simulations", simulations);
    }
    // نضيف إليها المحاكاة الحالية
    simulation.setNum(numDerniereSimulation);
    simulations.add(simulation);
    // يتم عرض قائمة عمليات المحاكاة
    menu = new Menu(false, false, false, false, true, true);
    return "simulations";
  }
  • الأسطر 9-16: يتم ترقيم المحاكاة المختلفة بدءًا من 1. يتم وضع آخر رقم مُخصص في الجلسة تحت المفتاح numDerniereSimulation. يتكون كود الأسطر 9-16 من استرداد هذا المفتاح وزيادة القيمة المرتبطة به.
  • الأسطر 18-22: يتم حفظ قائمة عمليات المحاكاة في الجلسة المرتبطة بالمفتاح simulations. وتتمثل الأسطر 18-22 في استرداد هذه القائمة إن وجدت أو إنشائها إن لم تكن موجودة.
  • الأسطر 24-25: بمجرد الحصول على قائمة عمليات المحاكاة، تُضاف إليها عملية المحاكاة الحالية (السطر 25). وقبل ذلك، تم تخصيص رقم لعملية المحاكاة الحالية (السطر 24).
  • السطر 27: يتم تحديد القائمة المراد عرضها
  • السطر 28: يتم تعيين مفتاح التنقل simulations.

العودة إلى تكوين الإجراء [EnregistrerSimulation] في [struts.xml]:


   <!-- الإجراء EnregistrerSimulation -->
    <action name="EnregistrerSimulation" class="web.actions.Enregistrer" method="execute">
      <result name="error" type="tiles">erreur</result>
      <result name="simulations" type="tiles">simulations</result>
</action>

السطر 4، يؤدي المفتاح simulations إلى عرض طريقة العرض Tiles المسماة simulations. تتكون طريقة العرض هذه من الأجزاء [Entete.jsp] و [Simulations.jsp]. الطريقة المعروضة هي كما يلي:

  • في [1]، الجزء [Entete.jsp] الذي أصبحنا نعرفه جيدًا الآن.
  • في [2]، المقطع [Simulations.jsp]

المقطع [Simulations.jsp] هو التالي:


<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>

<!-- قائمة المحاكاة فارغة -->
<s:if test="#session['simulations']==null || #session['simulations'].size()==0">
  <h2><s:text name="Pam.SimulationsVides.titre"/></h2>
</s:if>
<!-- قائمة عمليات المحاكاة غير فارغة -->
<s:if test="#session['simulations'].size()!=0">
  <h2><s:text name="Pam.Simulations.titre"/></h2>
  <table>
    <tr class="titreInfos">
      <th><s:text name="Pam.Simulations.num"/></th>
      <th><s:text name="Pam.Simulations.nom"/></th>
      <th><s:text name="Pam.Simulations.prenom"/></th>
      <th><s:text name="Pam.Simulations.heures"/></th>
      <th><s:text name="Pam.Simulations.jours"/></th>
      <th><s:text name="Pam.Simulations.salairebase"/></th>
      <th><s:text name="Pam.Simulations.indemnites"/></th>
      <th><s:text name="Pam.Simulations.cotisationsociales"/></th>
      <th><s:text name="Pam.Simulations.salairenet"/></th>
    </tr>
    <s:iterator value="#session['simulations']">
      <s:url action="SupprimerSimulation" var="url">
        <s:param name="id" value="num"/>
      </s:url>
      <tr>
        <td class="libelle"><s:property value="num"/></td>
        <td class="info"><s:property value="feuilleSalaire.employe.nom"/></td>
        <td class="info"><s:property value="feuilleSalaire.employe.prenom"/></td>
        <td class="info"><s:property value="heuresTravaillées"/></td>
        <td class="info"><s:property value="joursTravaillés"/></td>
        <td class="info">
          <s:text name="Format.monnaie">
            <s:param value="feuilleSalaire.elementsSalaire.salaireBase"/>
          </s:text>
        </td>
        <td class="info">
          <s:text name="Format.monnaie">
            <s:param value="indemnites"/>
          </s:text>
        </td>
        <td class="info">
          <s:text name="Format.monnaie">
            <s:param value="feuilleSalaire.elementsSalaire.cotisationsSociales"/>
          </s:text>
        </td>
        <td class="info">
          <s:text name="Format.monnaie">
            <s:param value="feuilleSalaire.elementsSalaire.salaireNet"/>
          </s:text>
        </td>
        <td class="info"><a href="<s:property value="#url"/>">حذف</a></td>
      </tr>
    </s:iterator>
  </table>
</s:if>
  • الأسطر 5-7: إذا لم تكن هناك محاكاة في الجلسة، فسيتم عرض المنظر التالي:

Image

  • الأسطر 13-21: عرض عناوين أعمدة الجدول

Image

  • الأسطر 23-55: تكرار على قائمة عمليات المحاكاة الموجودة في الجلسة
  • الأسطر 24-26: إنشاء عنوان URL باسم url (السمة id). رابط HTML الذي تم إنشاؤه بواسطة عنوان URL هذا هو التالي:
<a href="<a href="view-source:http://localhost:8084/pam/SupprimerSimulation.action?id=1">/pam/SupprimerSimulation.action?id=1</a>">Retirer</a>

نلاحظ أن الرابط يستهدف الإجراء [SupprimerSimulation] مع المعلمة id التي تمثل رقم المحاكاة المراد حذفها من القائمة.

  • الأسطر 28-54: في كل تكرار على قائمة عمليات المحاكاة، يتم عرض خصائص عملية المحاكاة الحالية.

Image

19.10. حذف محاكاة

قد يرغب المستخدم في إزالة محاكاة من قائمة المحاكاة:

  • في [1]، يتم حذف المحاكاة رقم 1
  • في [2]، تم حذف المحاكاة رقم 1

الرابط [Retirer] موجود في المقطع [Simulations.jsp] الذي درسناه سابقًا:


<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>

<!-- قائمة محاكاة فارغة -->
<s:if test="#session['simulations']==null || #session['simulations'].size()==0">
  <h2><s:text name="Pam.SimulationsVides.titre"/></h2>
</s:if>
<!-- قائمة محاكاة غير فارغة -->
<s:if test="#session['simulations'].size()!=0">
  <h2><s:text name="Pam.Simulations.titre"/></h2>
  <table>
    <tr class="titreInfos">
      ...
    </tr>
    <s:iterator value="#session['simulations']">
      <s:url action="SupprimerSimulation" var="url">
        <s:param name="id" value="num"/>
      </s:url>
      <tr>
        ...
        <td class="info">
          <s:text name="Format.monnaie">
            <s:param value="feuilleSalaire.elementsSalaire.salaireNet"/>
          </s:text>
        </td>
        <td class="info"><a href="<s:property value="#url"/>">إزالة</a></td>
      </tr>
    </s:iterator>
  </table>
</s:if>
  • الأسطر 16-18: تولد رابط HTML
<a href="<a href="view-source:http://localhost:8084/pam-01/SupprimerSimulation.action?id=2">/pam-01/SupprimerSimulation.action?id=</a>num">Retirer</a>

حيث num هو رقم المحاكاة المراد حذفها.

يتم تعريف الإجراء [SupprimerSimulation] على النحو التالي في الملف [struts.xml]:


<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
    "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
    "http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>
  <!-- التدويل -->
  <constant name="struts.custom.i18n.resources" value="messages" />
  <!-- تكامل Spring -->
  <constant name="struts.objectFactory.spring.autoWire" value="name" />

  
  <!-- إجراءات Struts /Tiles -->
  <package name="default" namespace="/" extends="tiles-default">
    ...
   <!-- إجراء RetirerSimulation -->
    <action name="SupprimerSimulation" class="web.actions.Supprimer">
      <result name="erreur" type="tiles">erreur</result>
      <result name="simulations" type="tiles">simulations</result>
    </action>
   ...
  </package>

    <!-- أضف الحزم هنا -->

</struts>
  • السطر 16: الإجراء [SupprimerSimulation] مرتبط بالفئة [Supprimer]. ونظرًا لعدم تحديد أي طريقة، فسيتم تنفيذ طريقتها execute. الفئة [Supprimer] هي كما يلي:

package web.actions;

...
public class Supprimer extends ActionSupport implements SessionAware {

  // الجلسة
  private Map<String, Object> session;
  // معرف المحاكاة المراد حذفها
  private String id;
  // القائمة
  private Menu menu;

  @Override
  public void setSession(Map<String, Object> session) {
    this.session = session;
  }

  // تنفيذ الإجراء
  public String execute() {
    // يتم استرداد عمليات المحاكاة من الجلسة
    List<Simulation> simulations = (List<Simulation>) session.get("simulations");
    if (simulations == null) {
      // حالة غير طبيعية - يبدو أن الجلسة قد انتهت صلاحيتها
      menu = new Menu(false, false, false, false, true, true);
      return "erreur";
    }
    // اختبار المعرف
    int num = 0;
    boolean erreur = false;
    try {
      num = Integer.parseInt(id);
      erreur = num <= 0;
    } catch (NumberFormatException ex) {
      // غير طبيعي
      erreur = true;
    }
    // خطأ؟
    if (erreur) {
      menu = new Menu(false, false, false, false, true, true);
      return "erreur";
    }
    // يتم البحث عن المحاكاة المراد حذفها
    for (int i = 0; i < simulations.size(); i++) {
      if (num == simulations.get(i).getNum()) {
        simulations.remove(i);
        break;
      }
    }
    // يتم عرض قائمة عمليات المحاكاة
    menu = new Menu(false, false, false, false, true, true);
    return "simulations";
  }

  // وظائف الحصول والتعيين
...
}
  • السطر 4: تنفذ العملية [Supprimer] واجهة [SessionAware] للوصول إلى الجلسة.
  • السطر 7: الجلسة
  • السطر 9: رقم المحاكاة المراد حذفها. نتذكر في الواقع أننا نصل إلى إنشاء مثيل للفئة [Supprimer] عبر عنوان URL HTML:
<a href="<a href="view-source:http://localhost:8084/pam-01/SupprimerSimulation.action?id=2">/pam-01/SupprimerSimulation.action?id=</a>num">Retirer</a>

حيث num هو رقم المحاكاة المراد حذفها. سيتم تخزين هذا الرقم في الحقل id في السطر 9.

  • السطر 11: القائمة الخاصة بالعرض الذي سيتم عرضه استجابةً للطلب
  • السطر 19: الطريقة execute التي ستقوم بإنشاء الاستجابة للطلب.
  • السطر 21: يتم استرداد قائمة عمليات المحاكاة التي تم إجراؤها بالفعل في الجلسة
  • الأسطر 22-26: عدم استرداد هذه القائمة من الجلسة يعني على الأرجح أن الجلسة قد انتهت صلاحيتها. وقد واجهنا هذه الحالة من قبل. نقوم بإرجاع المفتاح erreur الذي يعرض طريقة العرض Tiles erreur:

<!-- إجراء RetirerSimulation -->
    <action name="SupprimerSimulation" class="web.actions.Supprimer">
      <result name="erreur" type="tiles">erreur</result>
      ...
</action>

تم عرض طريقة العرض Tiles erreur في الفقرة 19.9.

  • الأسطر 28-36: نتحقق من أن السلسلة id في السطر 9 تمثل بالفعل عددًا صحيحًا >0.
  • الأسطر 38-40: إذا لم يكن الأمر كذلك، يتم إرجاع المفتاح erreur مرة أخرى، مما سيؤدي إلى عرض عرض Tiles erreur
  • الأسطر 43-48: يتم البحث عن المحاكاة المراد إزالتها في قائمة المحاكاة. إذا تم العثور عليها، يتم حذفها.
  • السطر 50: يتم تحديث القائمة لتناسب عرض Tiles simulations.
  • السطر 51: يتم إرجاع المفتاح simulations. سيؤدي هذا إلى عرض طريقة العرض Tiles simulations:

<!-- الإجراء RetirerSimulation -->
    <action name="SupprimerSimulation" class="web.actions.Supprimer">
      ...
      <result name="simulations" type="tiles">simulations</result>
</action>

تم عرض طريقة العرض Tiles simulations في الفقرة 19.9.

19.11. العودة إلى النموذج

من عرض Tiles simulations، يمكن للمستخدم العودة إلى النموذج:

  • في [1]، انقر على رابط العودة إلى النموذج
  • في [2]، يظهر نموذج فارغ

الرابط [Retour au formulaire de simulation] محدد في الجزء [Entete.jsp]:


<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>

<table>
  <tr>
    <td><h1><s:text name="Pam.titre"/></h1></td>
    <td>
      ...
      <s:if test="menu.retourFormulaire">
        |<a href="<s:url action="RetourFormulaire"/>"><s:text name="Menu.RetourFormulaire"/></a><br/>
      </s:if>
      ...
    </td>
  </tr>
</table>
  • السطر 10: يشير الرابط إلى الإجراء [RetourFormulaire]. وقد تم تعريف هذا الإجراء على النحو التالي في الملف [struts.xml]:

   <!-- إجراء RetourFormulaire -->
    <action name="RetourFormulaire" >
      <result type="redirectAction">
        <param name="actionName">Formulaire!input</param>
        <param name="namespace">/</param>
      </result>
</action>

نلاحظ أن هذه العملية غير مرتبطة بأي فئة. فهي تكتفي بإعادة توجيه متصفح العميل إلى العملية [/Formulaire!input]. ونكون هنا في نفس الحالة التي كنا فيها عند عرض العرض الأولي الموضح في الفقرة 19.7. وبالتالي نجد هذا العرض الأولي [2].

19.12. انظر قائمة عمليات المحاكاة

انطلاقًا من طرق العرض Tiles simulation أو saisie، يمكن للمستخدم طلب عرض المحاكاة:

  • في [1]، انقر على الرابط [Voir les simulations]
  • في [2]، تظهر قائمة عمليات المحاكاة

الرابط [Voir les simulations] محدد في الجزء [Entete.jsp]:


<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>

<table>
  <tr>
    <td><h1><s:text name="Pam.titre"/></h1></td>
    <td>
      ...
      <s:if test="menu.voirSimulations">
        |<a href="<s:url action="VoirSimulations"/>"><s:text name="Menu.VoirSimulations"/></a><br/>
      </s:if>
      ...
    </td>
  </tr>
</table>
  • السطر 10: الرابط [Voir les simulations] يستدعي الإجراء [VoirSimulations]. وهذا الإجراء مُعرَّف على النحو التالي في الملف [struts.xml]:

   <!-- إجراء VoirSimulations -->
    <action name="VoirSimulations" class="web.actions.Voir">
      <result name="success" type="tiles">simulations</result>
</action>

يتم ربط الإجراء [VoirSimulations] بالفئة [Voir] دون تحديد طريقة. وبالتالي، سيتم تنفيذ الطريقة [Voir].execute. الفئة [Voir] هي كما يلي:


package web.actions;

import com.opensymphony.xwork2.ActionSupport;
import web.entities.Menu;

public class Voir extends ActionSupport{
  // القائمة
  private Menu menu=new Menu(false,false,false,false,true,true);
  // دالات الاسترجاع والتعيين

  public Menu getMenu() {
    return menu;
  }

  public void setMenu(Menu menu) {
    this.menu = menu;
  }
  
}

لا تقوم الإجراء [Voir] إلا بشيء واحد: وضع القائمة لعرض Tiles simulations (السطر 8). لا توجد طريقة execute. وبالتالي، سيتم تنفيذ الطريقة الخاصة بالفئة الأم [ActionSupport]. ونعلم أنها لا تقوم بأي شيء سوى إرجاع المفتاح success.

نعود إلى العملية في [struts.xml]:


   <!-- إجراء VoirSimulations -->
    <action name="VoirSimulations" class="web.actions.Voir">
      <result name="success" type="tiles">simulations</result>
</action>

في السطر 3، نرى أن المفتاح success يؤدي إلى عرض طريقة العرض Tiles simulations. وقد تم وصف هذه الطريقة في الصفحة 156.

19.13. مسح المحاكاة الحالية

من عرض Tiles simulation، يمكن للمستخدم طلب مسح المحاكاة الحالية:

  • في [1]، يتم مسح المحاكاة الحالية
  • في [2]، يظهر نموذج الإدخال فارغًا

الرابط [Effacer la simulation] مُعرَّف في الجزء [Entete.jsp]:


<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>

<table>
  <tr>
    <td><h1><s:text name="Pam.titre"/></h1></td>
    <td>
      ...
      <s:if test="menu.effacerSimulation">
        |<a href="<s:url action="Formulaire!input"/>"><s:text name="Menu.EffacerSimulation"/></a><br/>
      </s:if>
      ...
    </td>
  </tr>
</table>

في السطر 10، نرى أن الرابط [Effacer la simulation] يُطلق الإجراء [Formulaire!input]. ونعلم أن هذا الإجراء يؤدي إلى العرض الأولي [2].

19.14. إنهاء الجلسة الحالية

من جميع طرق العرض Tiles، يمكن للمستخدم طلب إنهاء الجلسة:

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

الرابط [Terminer la session] محدد في الجزء [Entete.jsp]:


<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>

<table>
  <tr>
    <td><h1><s:text name="Pam.titre"/></h1></td>
    <td>
      ...
      <s:if test="menu.terminerSession">
        |<a href="<s:url action="TerminerSession"/>"><s:text name="Menu.TerminerSession"/></a><br/>
      </s:if>
    </td>
  </tr>
</table>

في السطر 10، نلاحظ أن الرابط [Terminer la session] يُشغّل الإجراء [TerminerSession]. وقد تم تعريف هذا الإجراء على النحو التالي في الملف [struts.xml]:


    <action name="TerminerSession" class="web.actions.Terminer">
      <result name="success" type="redirectAction">
        <param name="actionName">Formulaire!input</param>
        <param name="namespace">/</param>
      </result>
</action>
  • السطر 1: نلاحظ أن الفئة [Terminer] سيتم إنشاء مثيل لها وتنفيذ أسلوبها execute.
  • الأسطر 2-5: بعد تنفيذ الطريقة [Terminer].execute، سيتم إعادة التوجيه إلى شاشة الإدخال الأولية. وهذا ما يفسر الشاشة رقم 2.

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


package web.actions;

import com.opensymphony.xwork2.ActionSupport;
import java.util.Map;
import org.apache.struts2.interceptor.SessionAware;

public class Terminer extends ActionSupport implements SessionAware {

  // الجلسة
  private Map<String, Object> session;
  
  @Override
  public String execute() {
    // إنهاء الجلسة الحالية
    session.clear();
    return SUCCESS;
  }

  @Override
  public void setSession(Map<String, Object> session) {
    this.session = session;
  }
}

تتمثل وظيفة الإجراء [Terminer] في مسح سمات الجلسة الحالية.

  • السطر 7: تنفذ الإجراء [Terminer] واجهة [SessionAware] للوصول إلى الجلسة.
  • السطر 10: قاموس الجلسة
  • السطر 13: يتم تنفيذ الأسلوب execute
  • السطر 15: تقوم بإفراغ قاموس الجلسة. ونتيجة لذلك، ستختفي قائمة عمليات المحاكاة الموجودة في الجلسة. وهذا ما يفسر الشاشة رقم 3.
  • السطر 16: تُرجع المفتاح success، الذي، كما رأينا، سيعرض طريقة العرض Tiles saisie [2].

19.15. Conclusion

لقد قمنا بتعليق النسخة 1 من دراسة الحالة الخاصة بنا بشكل كامل، والتي تعمل مع طبقة [metier] محاكاة:

لم يتبق لنا سوى «ربط» الطبقة التشغيلية الحقيقية بالطبقة [web].