Skip to content

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

19.1. الطبقة [التجارية] المحاكاة

دعونا نستعرض بنية التطبيق الذي نقوم ببنائه:

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

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

سنقوم بتطوير طبقة [الويب] باستخدام طبقة [الأعمال] المحاكاة. سيكون الاختبار أبسط لأنه لم يعد هناك قاعدة بيانات في البنية. بفضل Spring واستخدام الواجهات، لن يكون لاستبدال طبقة [الأعمال] المحاكاة بالبنية الفعلية [الأعمال، DAO، JPA] في مرحلة لاحقة أي تأثير على كود طبقة [الويب / Struts2]. يمكن استخدام طبقة [الويب / Struts2] التي نحن على وشك تطويرها كما هي.

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


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 {
 
  // list of employees
  private Map<String, Employe> hashEmployes = new HashMap<String, Employe>();
  private List<Employe> listEmployes;
 
  // get your payslip
  public FeuilleSalaire calculerFeuilleSalaire(String SS,
          double nbHeuresTravaillées, int nbJoursTravaillés) {
    // we retrieve employee n° SS
    Employe e = hashEmployes.get(SS);
    // a fictitious payslip is returned
    return new FeuilleSalaire(e, new Cotisation(3.49, 6.15, 9.39, 7.88), new ElementsSalaire(100, 100, 100, 100, 100));
  }
 
  // list of employees
  public List<Employe> findAllEmployes() {
    if (listEmployes == null) {
      // create a list of two employees
      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)));
      // employee dictionary
      for (Employe e : listEmployes) {
        hashEmployes.put(e.getSS(), e);
      }
    }
    // we return the list of employees
    return listEmployes;
  }
}
  • السطر 11: تنفذ فئة [MetierSimule] واجهة [IMetier] التي تنفذها طبقة [business] الفعلية.
  • السطر 14: قاموس للموظفين مفهرس حسب رقم INSEE الخاص بهم
  • السطر 15: قائمة الموظفين
  • الأسطر 27–39: تنفيذ طريقة findAllEmployees لواجهة [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]: طبقة [الأعمال] المحاكاة
  • في [6]: الأرشيفات المستخدمة. فيما يلي الأرشيفات الخاصة بالأدوات المختلفة المستخدمة: Spring، Tiles، Struts 2، المكون الإضافي للتكامل بين Struts 2 و Spring، والمكون الإضافي للتكامل بين Struts 2 و Tiles.
  • في [7]: أرشيف الطبقة الفعلية [business, DAO, JPA]. يتيح لنا الوصول إلى كيانات JPA، وواجهة [IMetier]، وفئتي [PayrollSheet] و[PayrollItems]. يتم استخدام جميع هذه العناصر بالفعل من قبل فئة [SimulatedBusiness] الخاصة بنا.

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>
    <!-- Tiles -->
  <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] لإنشاء مثيلات لطبقات [business، DAO، JPA] الخاصة بالتطبيق. سيقوم Spring أيضًا، كما في المثال السابق، بإنشاء مثيل لفئة [Config] التي ستحتوي على بيانات على نطاق التطبيق. وأخيرًا، سيقوم 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>
  <!-- internationalization -->
  <constant name="struts.custom.i18n.resources" value="messages" />
  <!-- spring integration -->
  <constant name="struts.objectFactory.spring.autoWire" value="name" />
 
 
  <!-- struts /Tiles actions -->
  <package name="default" namespace="/" extends="tiles-default">
    <!-- default action -->
    <default-action-ref name="index" />
    <action name="index">
      <result type="redirectAction">
        <param name="actionName">Formulaire</param>
        <param name="namespace">/</param>
      </result>
    </action>
    <!-- action Form -->
    <action name="Formulaire" class="web.actions.Formulaire" method="input">
      <result name="success" type="tiles">saisie</result>
      <result name="exception" type="tiles">exception</result>
    </action>
    <!-- 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>
    <!-- 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>
   <!-- action RetourFormulaire -->
    <action name="RetourFormulaire" >
      <result type="redirectAction">
        <param name="actionName">Formulaire</param>
        <param name="namespace">/</param>
      </result>
    </action>
   <!-- action VoirSimulations -->
    <action name="VoirSimulations" class="web.actions.Voir">
      <result name="success" type="tiles">simulations</result>
    </action>
   <!-- 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>
   <!-- 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" الخاص بإجراء [Form]. نلاحظ أن عنصر <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">
 
  <!-- application layers -->
 
<!-- web -->
  <bean id="config" class="web.Config" init-method="init">
    <property name="metier" ref="metier"/>
  </bean>
  <!-- business -->
  <bean id="metier" class="metier.MetierSimule"/>
 
</beans>

  • السطر 13: الطبقة [business] المحاكاة التي تم إنشاء مثيل لها بواسطة فئة [business.SimulatedBusiness]
  • الأسطر 9-11: تكوين عنصر يسمى config. كما في المثال الذي درسناه سابقًا، سيُستخدم هذا العنصر لتغليف معلومات نطاق التطبيق. الفئة المرتبطة بهذا العنصر هي فئة [Config] التالية:


package web;
 
import java.util.List;
import jpa.Employe;
import metier.IMetier;
 
public class Config {
 
  // business layer initialized by Spring
  private IMetier metier;
  // list of employees
  private List<Employe> employes;
  // errors
  private Exception initException;
 
  // manufacturer
  public Config() {
  }
 
  // spring method for object initialization
  public void init() {
    // we ask for the list of employees
    try {
      employes = metier.findAllEmployes();
    } catch (Exception ex) {
      initException = ex;
    }
  }
 
  // getters and setters
  ...
}

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


  <bean id="config" class="web.Config" init-method="init">
    <property name="metier" ref="metier"/>
  </bean>
  <!-- métier -->
  <bean id="metier" class="metier.Metier">
    ...  
</bean>

في السطر 2، يمكننا أن نرى أن حبة الأعمال من السطر 5 يتم حقنها (ref) في الحقل المسمى business (name) للكائن [Config]. حبة الأعمال هي مرجع إلى طبقة [business]:

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


// business layer initialized by Spring
  private IMetier metier;
  // list of employees
  private List<Employe> employes;
  // errors
  private Exception initException;
 
  // manufacturer
  public Config() {
  }
 
  // spring method for object initialization
  public void init() {
    // we ask for the list of employees
    try {
      employes = metier.findAllEmployes();
    } catch (Exception ex) {
      initException = ex;
    }
  }

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


package metier;
 
import java.util.List;
import jpa.Employe;
 
public interface IMetier {
  // get your payslip
  FeuilleSalaire calculerFeuilleSalaire(String SS, double nbHeuresTravaillées, int nbJoursTravaillés );
  // list of employees
  List<Employe> findAllEmployes();
}
  • السطر 8: تحسب الطريقة راتب الموظف
  • السطر 10: تسترد الطريقة قائمة الموظفين

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

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

  • إشارة إلى طبقة [business]
  • قائمة الموظفين

19.4. إنشاء طرق عرض المربعات

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

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

Header.jsp
 
Input.jsp
 
Simulation.jsp
 
Simulations.jsp
 
استثناء.jsp
 
Error.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. عند الإنشاء، قد يكون هناك ما بين 0 و 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>
 
<!-- the master page -->
  <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>
 
<!-- input view -->
  <definition name="saisie" extends="masterPage">
    <put-attribute name="saisie"   value="/Saisie.jsp"/>     
  </definition>

  <!-- simulation view -->
 <definition name="simulation" extends="saisie">
    <put-attribute name="simulation"   value="/Simulation.jsp"/>     
  </definition>
 
<!-- the simulations view -->
  <definition name="simulations" extends="masterPage">
    <put-attribute name="simulations"   value="/Simulations.jsp"/>     
  </definition>
 
<!-- the exceptional view -->
  <definition name="exception" extends="masterPage">
    <put-attribute name="exception"   value="/Exception.jsp"/>     
  </definition>
 
<!-- error view -->
 <definition name="erreur" extends="masterPage">
    <put-attribute name="erreur"   value="/Erreur.jsp"/>     
  </definition>
</tiles-definitions>
  • يحدد الملف أعلاه ستة عروض Tiles باسم: masterPage (السطر 9)، input (السطر 20)، simulation (السطر 25)، simulations (السطر 30)، exception (السطر 35)، error (السطر 40).
  • الأسطر 9-17: تحدد عرضًا باسم masterPage (الاسم) ومرتبطًا بالصفحة الرئيسية [MasterPage.jsp] (القالب). لقد رأينا أن صفحة JSP هذه حددت ستة عروض فرعية. يجب أن تحدد طريقة عرض Tiles المرتبطة بالصفحة الرئيسية جزء JSP المرتبط بكل عرض من العروض الفرعية الستة. نرى أن بعض العروض الفرعية تم تعيين سلسلة فارغة كقيمة لها. لن يتم تضمين هذه العروض الفرعية في الصفحة الرئيسية [MasterPage.jsp]. وبالتالي، تتكون طريقة عرض Tiles المسماة masterPage حصريًا من الجزء الفرعي [Entete.jsp].
  • الأسطر 20-22: تحدد عرضًا باسم `saisie` يمتد من عرض `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
الصفحة الرئيسية
Header.jsp
الإدخال
Header.jsp، Input.jsp
simulation
Header.jsp، Entry.jsp، Simulation.jsp
المحاكاة
Header.jsp، Simulations.jsp
استثناء
Header.jsp، Exception.jsp
خطأ
Header.jsp، Error.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
# formats
Format.heure = {0,time}
Format.nombre = {0,number,#0.0##}
Format.pourcent = {0,number,##0.00' %'}
Format.monnaie={0,number,##0.00' \u20ac'}
# liste des simulations
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
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
# msg d'erreur
Erreur.sessionexpiree=La session a expir\u00e9
Erreur.numSimulation=N\u00b0 de simulation incorrect
# erreur de conversion
xwork.default.invalid.fieldvalue=Valeur invalide pour le champ "{0}".

ملف [Form.properties] كما يلي:


# pour que les doubles soient au format local
double.format={0,number,#0.00##}
# msg d'erreur
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. ورقة الأنماط

تستخدم طرق عرض المربعات ورقة الأنماط التالية [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] لدينا الإجراءات التالية:


  <!-- action par défaut -->
    <default-action-ref name="index" />
    <action name="index">
      <result type="redirectAction">
        <param name="actionName">Formulaire!input</param>
        <param name="namespace">/</param>
      </result>
    </action>
    <!-- action 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>

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

فيما يلي فئة [Form]:


package web.actions;
 
...
public class Formulaire extends ActionSupport implements Preparable, SessionAware {
 
  // configuration initialized by Spring
  private Config config;
  // list of employees
  private List<Employe> employes;
  // error list
  private List<Erreur> erreurs;
  // payslip
  private FeuilleSalaire feuilleSalaire;
  // foreclosures
  private String comboEmployesValue;
  private Double heuresTravaillees;
  private Integer joursTravailles;
  // session
  private Map<String, Object> session;
  // menu
  private Menu menu;
 
  @Override
  public void prepare() throws Exception {
    ...
  }
 
  @Override
  public String input() {
  ....
  }
 
  // wage calculation
  public String calculSalaire() {
 ...
    }
  }
 
  @Override
  public void validate() {
  ...
  }
 
  @Override
  public void setSession(Map<String, Object> map) {
    session = map;
  }
 
  // getters and setters
  ...
}
  • السطر 4: يُنفذ الإجراء [Form] واجهة Preparable. تحتوي هذه الواجهة على طريقة واحدة فقط، وهي طريقة prepare الموجودة في السطر 24. يتم تنفيذ هذه الطريقة مرة واحدة قبل أي طريقة أخرى في الإجراء. وتُستخدم عادةً لتهيئة نموذج الإجراء.

يتم تعريف نموذج الإجراء في الأسطر 6–21:

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

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

يتم تنفيذ طريقة الإعداد قبل طريقة الإدخال. وهي كما يلي:


  @Override
  public void prepare() throws Exception {
    // configuration error?
    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. فئة [Error] هي كما يلي:

package web.entities;
 
import java.io.Serializable;
 
public class Erreur implements Serializable{
 
  public Erreur() {
  }
 
  // fields
  private String classe;
  private String message;
 
  // manufacturer
  public Erreur(String classe, String message){
    this.setClasse(classe);
    this.message=message;
  }
 
  // getters and setters
...
}

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

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

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

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

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


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

تقوم طريقة الإدخال ببساطة بتعيين قائمة خيارات القائمة المراد عرضها. فئة [Menu] هي كما يلي:


package web.entities;
 
import java.io.Serializable;
 
public class Menu implements Serializable {
  // menu items
 
  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;
  }
 
  // getters and setters
...  
}
  • الأسطر 8–13: هناك 6 روابط محتملة في القائمة
  • الأسطر 18-25: يسمح لك منشئ الفئة بتحديد الروابط التي يجب عرضها وتلك التي لا يجب عرضها.

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

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


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

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


    <!-- action 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>
  • السطر 5: يعرض مفتاح الإدخال عرض المربعات المسمى input
  • السطر 4: يعرض مفتاح exception عرض Tiles المسمى exception

لنبدأ بعرض Tiles المسمى "input". يتكون من أجزاء 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: يتم التحكم في عرض الروابط من خلال حقل القائمة الخاص بالإجراء الحالي.

لاحظ أن جزء [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(){
    // mail the form
    document.forms['Saisie'].elements['action'].name='action:Formulaire!calculSalaire'
    document.forms['Saisie'].submit();
  }  
</script>
 
<!-- data entry -->
<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='Form'.
  • السطر 18: عرض القائمة المنسدلة للموظفين. يتم توفير محتوى القائمة المنسدلة (سمة list) بواسطة حقل employees الخاص بالإجراء الحالي. ستكون سمة value للخيارات هي رقم الضمان الاجتماعي للموظف (سمة listKey). سيكون التسمية المعروضة لكل خيار هي الاسم الأول واسم العائلة للموظف (سمة listValue). سيتم إرسال رقم الضمان الاجتماعي للموظف المحدد في مربع القائمة المنسدلة إلى حقل [Form].comboEmployeesValue (سمة name).
  • السطر 19: حقل إدخال لساعات العمل. القيمة المعروضة (سمة value) هي قيمة حقل heuresTravaillees في الإجراء [Form] بالتنسيق التالي (Form.properties):

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

سيتم نشر القيمة في حقل [Form].hoursWorked (سمة الاسم).

  • السطر 20: حقل لإدخال أيام العمل. القيمة المعروضة (سمة القيمة) هي قيمة حقل daysWorked في الإجراء [Form].

سيتم نشر القيمة في حقل [Form].daysWorked (سمة name).

في النهاية، تكون طريقة عرض Tiles المعروضة عند بدء التشغيل في حالة عدم وجود أخطاء كما يلي:

Image

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


    <!-- action 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>

لقد رأينا أن الإجراء [Form].input يمكنه أيضًا تشغيل مفتاح exception في السطر 4. في هذه الحالة، يتم عرض عرض Tiles المسمى exception. يتكون هذا العرض من شظايا [Header.jsp] و [Exception.jsp]. لقد قدمنا بالفعل شظية [Header.jsp]. شظية [Exception.jsp] هي كما يلي:

Image

هذه هي صفحة بدء تشغيل الإصدار 2 من التطبيق عندما لا يكون نظام إدارة قواعد البيانات (DBMS) قد تم تشغيله. فيما يلي كود JSP لجزء [Error.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<Error> للأخطاء من الإجراء [Form]. تذكر أنه في حالة حدوث خطأ، تم تخزين مكدس من الاستثناءات هناك.

19.8. تشغيل محاكاة

بمجرد عرض العرض الأولي، يمكنك حساب الراتب عبر الرابط [تشغيل محاكاة].

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

ضع في اعتبارك التسلسل التالي:

  • في [1]، إدخال غير صحيح
  • في [2]، الاستجابة المرسلة.

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


    <!-- action 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>

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

يتم التعامل مع التحقق من صحة الإجراء [Form] بواسطة ملف [Form-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 من أن حقل hoursWorked هو عدد حقيقي في النطاق [0,300].
  • تتحقق الأسطر 22–36 من أن الحقل `joursTravaillés` هو عدد صحيح في النطاق [0,31].

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


    <!-- action Formulaire -->
    <action name="Formulaire" class="web.actions.Formulaire">
      ...
      <result name="input" type="tiles">saisie</result>
</action>

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

تذكر أن هذه العرض يتكون من الأجزاء [Header.jsp] و [Input.jsp]، حيث يحتوي [Header.jsp] على عنوان وقائمة من الخيارات، بينما يحتوي [Input.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>

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


package web.actions;
 
import com.opensymphony.xwork2.ActionSupport;
...
public class Formulaire extends ActionSupport implements Preparable, SessionAware {
 
  ...
  // menu
  private Menu menu;
 
  @Override
  public void prepare() throws Exception {
  ...
  }
 
  @Override
  public String input() {
  ...
  }
 
  // wage calculation
  public String calculSalaire() {
  ...
  }
 
  @Override
  public void validate() {
    // mistakes?
    if (!getFieldErrors().isEmpty()) {
      // menu
      menu = new Menu(true, false, false, true, false, true);
    }
  }
 
  // getters and setters
  ...
}
  • السطر 27: نعلم أنه عند وجود هذا، يتم تنفيذ طريقة validate بواسطة عملية التحقق من الصحة. ننتهز هذه الفرصة لتحديث القائمة في السطر 4، والتي تعد جزءًا من قالب المقتطف [Entete.jsp].
  • الأسطر 29–32: إذا كانت هناك أخطاء في التحقق من الصحة، فإننا نضبط القائمة لإعادة عرض طريقة عرض الإدخال Tiles. إذا لم تكن هناك أخطاء في التحقق من الصحة، فإننا لا نفعل شيئًا. ثم تتولى طريقة `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: عندما ينقر المستخدم على رابط [تشغيل المحاكاة]، يتم تنفيذ دالة JavaScript doSimulation. يتم تعريف هذه الدالة في جزء [Saisie.jsp]:

<script language="javascript" type="text/javascript">
  function doSimulation(){
    // mail the form
    document.forms['Saisie'].elements['action'].name='action:Formulaire!calculSalaire'
    document.forms['Saisie'].submit();
  }
</script>
 
<!-- data entry -->
<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 إلى إجراء [Form]. يتيح لنا هذا الحقل تحديد الإجراء والطريقة المراد تنفيذهما عند إرسال النموذج. وكما نتذكر من الأمثلة الأولى، يمكن تحديد هذين العنصرين في معلمة باسم action:Action!method. لا يهم قيمة هذه المعلمة؛ المهم فقط أن تكون موجودة.
  • الأسطر 2–6: دالة JavaScript التي يتم تنفيذها عندما ينقر المستخدم على رابط [Run Simulation] في جزء [Header.jsp].
  • السطر 4: نقوم بتغيير سمة الاسم لحقل الإجراء المخفي. نتأكد من أنه في صيغة action:Action!method التي يتوقعها Struts.
  • السطر 5: يتم إرسال النموذج المسمى "Saisie de la ligne 5". ونتيجة لذلك، يتم إرسال سلسلة المعلمات التالية:
comboEmployesValue=SS1&heuresTravaillees=xx&joursTravailles=yy&action:Formulaire!calculSalaire

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

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

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

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

طريقة [Form].calculateSalary هي كما يلي:


// wage calculation
  public String calculSalaire() {
    try {
      // salary calculation
      feuilleSalaire = config.getMetier().calculerFeuilleSalaire(comboEmployesValue, heuresTravaillees, joursTravailles);
      // put the simulation in the session
      session.put("simulation", new Simulation(0, "" + heuresTravaillees, "" + joursTravailles, feuilleSalaire));
      // menu
      menu = new Menu(true, true, true, true, false, true);
      // finish
      return "simulation";
    } catch (Throwable th) {
      ...
    }
  }
  • السطر 5: يتم طلب حساب الرواتب من طبقة [business]
  • السطر 7: تتم إضافة كائن Simulation إلى جلسة عمل المستخدم. وذلك لأنه قد يكون مطلوبًا لطلب لاحق. فئة [Simulation] هي كما يلي:

package web.entities;
 
import java.io.Serializable;
import metier.FeuilleSalaire;
 
public class Simulation implements Serializable{
 
  public Simulation() {
  }
 
  // simulation fields
  private Integer num;
  private FeuilleSalaire feuilleSalaire;
  private String heuresTravaillées;
  private String joursTravaillés;
 
  // manufacturer
  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 and setters
...
}
  • السطر 12: رقم المحاكاة. يتم زيادته مع كل محاكاة جديدة يتم حفظها.
  • السطر 13: كشف راتب الموظف
  • السطر 14: عدد ساعات العمل
  • السطر 15: عدد أيام العمل
  • السطر 25: تُرجع طريقة getIndemnites إجمالي التعويضات المستحقة للموظف

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

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


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

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


    <!-- action Formulaire -->
    <action name="Formulaire" class="web.actions.Formulaire">
         ...
      <result name="simulation" type="tiles">simulation</result>
</action>

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

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

  • في [1]، الجزء [Header.jsp]
  • في [2]، الجزء [Input.jsp]
  • في [3]، الجزء [Simulation.jsp]. لاحظ أن كشف الراتب المعروض هو كشف راتب وهمي تم عرضه بواسطة طبقة [business].

وقد تم عرض أول جزأين بالفعل. وفيما يلي نص الجزء [Simulation.jsp]:


<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
 
<hr/>
<!-- information Employees -->
<span class="titreInfos">
  <s:text  name="Simulation.Infos.employe"/>
</span>
<br/><br/>
<table>
  <!-- line 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>
  <!-- line 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>
  <!-- line 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>
  <!-- line 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>
<!-- information Contributions -->
<br/>
<span class="titreInfos">
  <s:text name="Simulation.Infos.cotisations"/>
</span>
 
<br/><br/>
<table>
  <!-- line 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>
  <!-- line 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>
<!-- information Indemnities -->
<br/>
<span class="titreInfos">
  <s:text name="Form.Infos.indemnites"/>
</span>
<br/><br/>
<table>
  <!-- line 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>
  <!-- line 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>
<!-- salary information -->
<br/>
<span class="titreInfos">
  <s:text name="Simulation.Infos.Salaire"/>
</span>
<br/><br/>
<table>
  <!-- line 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>
 
  <!-- line 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>
<!-- Net salary-->
<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>

إنه طويل... ولكنه بسيط من الناحية الوظيفية. يعرض هذا المقتطف الخصائص المختلفة لحقل [Form].paystub، الذي يمثل كشف راتب الموظف.

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


// wage calculation
  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
      menu = new Menu(false, false, false, false, true, true);
      return "exception";
    }
  }

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

  • الأسطر 7–11: نقوم بإنشاء قائمة من كائنات Error من مكدس الاستثناءات
  • السطر 13: نضبط القائمة
  • السطر 14: نضبط مفتاح الاستثناء.

سيعرض مفتاح الاستثناء عرض استثناء Tiles:


    <!-- action Formulaire -->
    <action name="Formulaire" class="web.actions.Formulaire">
      <result name="exception" type="tiles">exception</result>
      ...
</action>

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

Image

19.9. حفظ محاكاة

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

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

يوجد رابط [حفظ المحاكاة] في جزء [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>

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


   <!-- 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>
  • السطر 1: يرتبط الإجراء [SaveSimulation] بالفئة [Save] وطريقة التنفيذ الخاصة بها.

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


package web.actions;
 
...
public class Enregistrer extends ActionSupport implements SessionAware {
 
  // session
  private Map<String, Object> session;
  // menu
  private Menu menu;
 
  @Override
  public void setSession(Map<String, Object> session) {
    this.session = session;
  }
 
  // action execution
  public String execute() {
    // retrieve the last simulation in the session
    Simulation simulation = (Simulation) session.get("simulation");
    if (simulation == null) {
      return ERROR;
    }
    ...
  }
 
  // getters and setters
  ...
}
  • السطر 4: نظرًا لأن الإجراء يحتاج إلى الوصول إلى الجلسة، فإنه ينفذ واجهة SessionAware.
  • السطر 7: الجلسة
  • السطر 9: القائمة

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

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

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


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

يمكننا أن نرى أن المفتاح "error" (السطر 3) يؤدي إلى عرض عرض Tiles المسمى "error" . يتكون هذا العرض من الأجزاء [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>

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


// action execution
  public String execute() {
    // retrieve the last simulation in the session
    Simulation simulation = (Simulation) session.get("simulation");
    if (simulation == null) {
      return ERROR;
    }
    // retrieve the number of the last simulation
    Integer numDerniereSimulation = (Integer) session.get("numDerniereSimulation");
    if (numDerniereSimulation == null) {
      numDerniereSimulation = 0;
    }
    // increment it
    numDerniereSimulation++;
    // we give it the new number in the session
    session.put("numDerniereSimulation", numDerniereSimulation);
    // retrieve the list of simulations
    List<Simulation> simulations = (List<Simulation>) session.get("simulations");
    if (simulations == null) {
      simulations = new ArrayList<Simulation>();
      session.put("simulations", simulations);
    }
    // we add the current simulation
    simulation.setNum(numDerniereSimulation);
    simulations.add(simulation);
    // the list of simulations is displayed
    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]:


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

السطر 4: يؤدي المفتاح "simulations" إلى عرض طريقة عرض المربعات المسماة "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" %>
 
<!-- empty simulation list -->
<s:if test="#session['simulations']==null || #session['simulations'].size()==0">
  <h2><s:text name="Pam.SimulationsVides.titre"/></h2>
</s:if>
<!-- non-empty simulation list -->
<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"/>">Retirer</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>

يمكننا أن نرى أن الرابط يستهدف الإجراء [DeleteSimulation] باستخدام المعلمة id، التي تمثل رقم المحاكاة المراد إزالتها من القائمة.

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

Image

19.10. إزالة محاكاة

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

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

يوجد رابط [إزالة] في جزء [Simulations.jsp] الذي سبق أن درسناه:


<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
 
<!-- empty simulation list -->
<s:if test="#session['simulations']==null || #session['simulations'].size()==0">
  <h2><s:text name="Pam.SimulationsVides.titre"/></h2>
</s:if>
<!-- non-empty simulation list -->
<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"/>">Retirer</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 هو رقم المحاكاة المراد إزالتها.

يتم تعريف الإجراء [DeleteSimulation] على النحو التالي في ملف [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>
  <!-- internationalization -->
  <constant name="struts.custom.i18n.resources" value="messages" />
  <!-- spring integration -->
  <constant name="struts.objectFactory.spring.autoWire" value="name" />
 
 
  <!-- struts /Tiles actions -->
  <package name="default" namespace="/" extends="tiles-default">
    ...
   <!-- action RetirerSimulation -->
    <action name="SupprimerSimulation" class="web.actions.Supprimer">
      <result name="erreur" type="tiles">erreur</result>
      <result name="simulations" type="tiles">simulations</result>
    </action>
   ...
  </package>
 
    <!-- Add packages here -->
 
</struts>
  • السطر 16: الإجراء [SupprimerSimulation] مرتبط بفئة [Supprimer]. وبما أنه لم يتم تحديد أي طريقة، فسيتم تنفيذ طريقة execute الخاصة بها. فئة [Supprimer] هي كما يلي:

package web.actions;
 
...
public class Supprimer extends ActionSupport implements SessionAware {
 
  // session
  private Map<String, Object> session;
  // id of simulation to be deleted
  private String id;
  // menu
  private Menu menu;
 
  @Override
  public void setSession(Map<String, Object> session) {
    this.session = session;
  }
 
  // action execution
  public String execute() {
    // simulations are retrieved from the
    List<Simulation> simulations = (List<Simulation>) session.get("simulations");
    if (simulations == null) {
      // abnormal case - session must have expired
      menu = new Menu(false, false, false, false, true, true);
      return "erreur";
    }
    // id test
    int num = 0;
    boolean erreur = false;
    try {
      num = Integer.parseInt(id);
      erreur = num <= 0;
    } catch (NumberFormatException ex) {
      // abnormal
      erreur = true;
    }
    // mistake?
    if (erreur) {
      menu = new Menu(false, false, false, false, true, true);
      return "erreur";
    }
    // search for the simulation to be deleted
    for (int i = 0; i < simulations.size(); i++) {
      if (num == simulations.get(i).getNum()) {
        simulations.remove(i);
        break;
      }
    }
    // the list of simulations is displayed
    menu = new Menu(false, false, false, false, true, true);
    return "simulations";
  }
 
  // getters and setters
...
}
  • السطر 4: يقوم الإجراء [Delete] بتنفيذ واجهة [SessionAware] للوصول إلى الجلسة.
  • السطر 7: الجلسة
  • السطر 9: رقم المحاكاة المراد حذفها. تذكر أننا نقوم بإنشاء مثيل لفئة [Delete] عبر عنوان 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: الفشل في استرداد هذه القائمة من الجلسة يعني على الأرجح أن الجلسة قد انتهت صلاحيتها. لقد واجهنا هذا السيناريو من قبل. نُرجع مفتاح الخطأ، الذي يعرض عرض خطأ Tiles:

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

تم تقديم عرض أخطاء Tiles في القسم 19.9.

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

<!-- action RetirerSimulation -->
    <action name="SupprimerSimulation" class="web.actions.Supprimer">
      ...
      <result name="simulations" type="tiles">simulations</result>
</action>

تم تقديم عرض "مربعات المحاكاة" في القسم 19.9.

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

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

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

يتم تعريف رابط [العودة إلى نموذج المحاكاة] في جزء [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: يشير الرابط إلى الإجراء [ReturnForm]. يتم تعريفه على النحو التالي في ملف [struts.xml]:

   <!-- action RetourFormulaire -->
    <action name="RetourFormulaire" >
      <result type="redirectAction">
        <param name="actionName">Formulaire!input</param>
        <param name="namespace">/</param>
      </result>
</action>

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

19.12. عرض قائمة المحاكاة

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

  • في [1]، انقر على رابط [عرض عمليات المحاكاة]
  • في [2]، يتم عرض قائمة عمليات المحاكاة

يتم تعريف رابط [عرض المحاكاة] في جزء [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: الرابط [View Simulations] يستدعي الإجراء [ViewSimulations]. وهذا محدد على النحو التالي في ملف [struts.xml]:

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

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


package web.actions;
 
import com.opensymphony.xwork2.ActionSupport;
import web.entities.Menu;
 
public class Voir extends ActionSupport{
  // menu
  private Menu menu=new Menu(false,false,false,false,true,true);
  // getters and setters
 
  public Menu getMenu() {
    return menu;
  }
 
  public void setMenu(Menu menu) {
    this.menu = menu;
  }
 
}

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

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


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

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

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

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

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

يتم تعريف رابط [مسح المحاكاة] في جزء [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، نرى أن الرابط [Clear Simulation] يُشغّل الإجراء [Form!input]. ونعلم أن هذا الإجراء يؤدي إلى العرض الأولي [2].

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

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

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

يتم تعريف رابط [إنهاء الجلسة] في جزء [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، نلاحظ أن رابط [End Session] يُشغّل الإجراء [EndSession]. وقد تم تعريف هذا الإجراء على النحو التالي في ملف [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 {
 
  // session
  private Map<String, Object> session;
 
  @Override
  public String execute() {
    // quit current session
    session.clear();
    return SUCCESS;
  }
 
  @Override
  public void setSession(Map<String, Object> session) {
    this.session = session;
  }
}

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

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

19.15. الخلاصة

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

كل ما تبقى هو "ربط" طبقة الأعمال الحقيقية بطبقة [الويب].