Introduction à STRUTS2 par l'exemple


précédentsommairesuivant

XIX. Etude de cas - version 1

XIX-A. La couche [metier] simulée

Revenons sur l'architecture de l'application que nous construisons :

Image non disponible

Nous disposons de l'archive des couches [metier, dao, jpa] et nous avons présenté les éléments de ces couches que la couche [web] devait connaître. Nous sommes prêts pour écrire celle-ci à l'aide du framework Struts.

Pour simplifier les tests de notre application en cours de développement, nous allons créer une couche métier simulée qui respectera l'interface de la couche [metier]. L'architecture va devenir la suivante :

Image non disponible

Nous allons développer la couche [web] avec la couche [métier] simulée. Les tests seront plus simples à faire car il n'y a plus de base de données dans l'architecture. Grâce à Spring et à l'utilisation d'interfaces, remplacer ultérieurement la couche [metier] simulée par la véritable architecture [metier, dao, jpa] aura un impact nul sur le code de la couche [web / struts2]. La couche [web / struts2] que nous allons développer maintenant pourra être utilisée telle quelle.

La couche [metier] simulée que nous allons utiliser est la suivante :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
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 {

  // liste des employes
  private Map<String, Employe> hashEmployes = new HashMap<String, Employe>();
  private List<Employe> listEmployes;

  // obtenir la feuille de salaire
  public FeuilleSalaire calculerFeuilleSalaire(String SS,
          double nbHeuresTravaillées, int nbJoursTravaillés) {
    // on récupère l'employé de  SS
    Employe e = hashEmployes.get(SS);
    // on rend une feuille de salaire fictive
    return new FeuilleSalaire(e, new Cotisation(3.49, 6.15, 9.39, 7.88), new ElementsSalaire(100, 100, 100, 100, 100));
  }

  // liste des employés
  public List<Employe> findAllEmployes() {
    if (listEmployes == null) {
      // on crée une liste de deux employés
      listEmployes = new ArrayList<Employe>();
      listEmployes.add(new Employe("254104940426058", "Jouveinal", "Marie", "5 rue des oiseaux", "St Corentin", "49203", new Indemnite(2, 2.1, 2.1, 3.1, 15)));
      listEmployes.add(new Employe("260124402111742", "Laverti", "Justine", "La br&#65533;lerie", "St Marcel", "49014", new Indemnite(1, 1.93, 2, 3, 12)));
      // dictionnaire des employes
      for (Employe e : listEmployes) {
        hashEmployes.put(e.getSS(), e);
      }
    }
    // on rend la liste des employés
    return listEmployes;
  }
}
  • ligne 11 : la classe [MetierSimule] implémente l'interface [IMetier] que la véritable couche [metier] implémente.
  • ligne 14 : un dictionnaire des employés indexés par leur n° INSEE
  • ligne15 : la liste des employes
  • lignes 27-39 : implémentation de la méthode findAllEmployes de l'interface [IMetier].
  • lignes 30-33 : création d'une liste de deux employés
  • lignes 34-36 : création du dictionnaire des employés indexé par le n° INSEE
  • lignes 18-24 : implémentation de la méthode calculerSalaire de l'interface [IMetier]. Ici on rend une feuille de salaire fictive.

XIX-B. Le projet Netbeans

Le projet Netbeans est le suivant :

Image non disponible

  • en [1] :
    • [applicationContext.xml] est le fichier de configuration de Spring
    • [tiles.xml] est le fichier de configuration d'un framework appelé Tiles.
    • [web.xml] est le fichier de configuration de l'application web
  • en [2] : les différentes vues de l'application
  • en [3] :
    • [messages.properties] : le fichier des messages
    • [struts.xml] : le fichier de configuration de Struts

Image non disponible

  • en [4] : les codes source de l'application. Les actions Struts sont dans le package [web.actions].
  • en [5] : la couche [metier] simulée
  • en [6] : les archives utilisées. On trouve les archives des différents outils utilisés : Spring, Tiles, Struts 2, le plugin d'intégration Struts 2 / Spring, le plugin d'intégration Struts 2 / Tiles.
  • en [7] : l'archive de la couche [metier, dao, jpa] véritable. Elle nous permet d'avoir accès aux entités Jpa, à l'interface [IMetier], aux classes [FeuilleSalaire] et [ElementsSalaire]. Tous ces éléments sont en effet utilisés par notre classe [MetierSimule].

XIX-C. Configuration du projet

Le projet est configuré par différents fichiers :

  • [web.xml] qui configure l'application web
  • [struts.xml] qui configure le framework Struts
  • [applicationContext.xml] qui configure le framework Spring
  • [tiles.xml] qui configure le framework Tiles

XIX-C-1. Configuration de l'application web

Le fichier [web.xml] est le suivant :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
<?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>
  • lignes 13-20 : configurent le filtre Struts 2 - déjà vu
  • lignes 22-24 : configurent le listener de Spring - déjà vu
  • lignes 9-11 : configurent le listener Tiles. La classe [org.apache.struts2.tiles.StrutsTilesListener] va être instanciée au démarrage de l'application web. Elle va alors exploiter son fichier de configuration. Celui-ci est défini par les lignes 5-8. Le fichier de configuration de Tiles est donc le fichier [WEB-INF/tiles.xml].

Au final, au démarrage de l'application Struts, trois classes sont instanciées :

  • l'une pour le filtre Struts 2. C'est elle qui assure le C de MVC.
  • une autre pour le listener Spring. Spring va exploiter le fichier [applicationContext.xml] pour instancier les couches [métier, dao, jpa] de l'application. Spring va également instancier, comme dans un exemple précédent, une classe [Config] qui contiendra les données de portée Application. Enfin Spring injectera dans chaque action Struts qui en a besoin, une référence sur cette unique instance [Config].
  • une autre pour le listener Tiles. Ce framework va assurer la gestion des vues. Nous y reviendrons bientôt.

XIX-C-2. Configuration du framework Struts

Le framework Struts est configuré par le fichier [struts.xml] suivant :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
<?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>
  <!-- internationalisation -->
  <constant name="struts.custom.i18n.resources" value="messages" />
  <!-- intégration Spring -->
  <constant name="struts.objectFactory.spring.autoWire" value="name" />

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

Nous commenterons les différentes actions Struts au fur et à mesure où nous les étudierons. Pour l'instant, on peut noter les points suivants :

  • ligne 8 : définit le fichier des messages
  • ligne 10 : définit le mode d'injection des beans Spring dans les actions Struts. L'injection se fait selon le nom du bean. Il faut que le champ de l'action Struts qui doit être initialisé par Spring porte le nom du bean à injecter.
  • ligne 25 : définit la vue à afficher pour la clé de navigation success de l'action [Formulaire]. On voit que le résultat <result> a un attribut type='tiles' que nous ne connaissons pas. Nous connaissions le type redirect qui permet de rediriger le client vers une vue. Ici la vue de type tiles est gérée par le framework Tiles . Le type tiles est défini dans le fichier [struts-plugin.xml] de l'archive [struts2-tiles-plugin-2.2.3.1.jar] :
 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
<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>
  • lignes 3-5 : la définition du type de résultat tiles .
  • ligne 2 : ce type est défini dans le package [tiles-default] qui étend le package [struts-default].

ligne 14 : définit le package [default] dans lequel seront toutes les actions de l'application. Pour profiter de la définition du type de vue tiles , le package étend [tiles-default].

XIX-C-3. Configuration du framework Spring

Le framework Spring est configuré par le fichier [WEB-INF/applicationContext.xml] suivant :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
<?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">
  
  <!-- couches applicatives -->
 
 <!-- web -->
  <bean id="config" class="web.Config" init-method="init">
    <property name="metier" ref="metier"/>
  </bean>
  <!-- métier -->
  <bean id="metier" class="metier.MetierSimule"/>

</beans>
  • ligne 13 : la couche [metier] simulée instanciée par la classe [metier.MetierSimule]
  • lignes 9-11 : configurent un bean nommé config . Comme dans un exemple précédemment étudié, ce bean servira à encapsuler les informations de portée Application . La classe associée à ce bean est la classe [Config] suivante :
 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
package web;

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

public class Config {

  // couche métier initialisée par Spring
  private IMetier metier;
  // liste des employés
  private List<Employe> employes;
  // erreurs
  private Exception initException;

  // constructeur
  public Config() {
  }

  // méthode Spring d'initialisation de l'objet
  public void init() {
    // on demande la liste des employés
    try {
      employes = metier.findAllEmployes();
    } catch (Exception ex) {
      initException = ex;
    }
  }

  // getters et setters
  ... 
}

Revenons à la configuration du bean config  :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
<bean id="config" class="web.Config" init-method="init">
    <property name="metier" ref="metier"/>
  </bean>
  <!-- métier -->
  <bean id="metier" class="metier.Metier">
    ...  
</bean>

Ligne 2, on peut voir que le bean metier de la ligne 5, est injecté (ref) dans le champ nommé metier (name) de l'objet [Config]. Le bean metier est une référence sur la couche [metier] :

Image non disponible

Pour dialoguer avec la couche [metier], toutes les actions Struts de la couche [web] auront besoin d'une référence sur celle-ci. On peut dire que la référence sur la couche [metier] est une donnée de portée Application. Toutes les requêtes de tous les utilisateurs en auront besoin. C'est pourquoi, nous mettons cette référence dans l'objet [Config]. Par ailleurs, ligne 1, la configuration du bean config a un attribut init-method. Cet attribut désigne la méthode du bean à exécuter après instanciation du bean. Ici, on indique qu'après instanciation de la classe [web.Config], on doit exécuter sa méthode init. Celle-ci est la suivante :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
// couche métier initialisée par Spring
  private IMetier metier;
  // liste des employés
  private List<Employe> employes;
  // erreurs
  private Exception initException;

  // constructeur
  public Config() {
  }

  // méthode Spring d'initialisation de l'objet
  public void init() {
    // on demande la liste des employés
    try {
      employes = metier.findAllEmployes();
    } catch (Exception ex) {
      initException = ex;
    }
}

Lorsque la méthode init est exécutée, le champ metier de la classe a été instancié par Spring. La méthode init a donc accès à la couche metier d'interface [IMetier] (ligne 2) :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
package metier;

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

public interface IMetier {
  // obtenir la feuille de salaire
  FeuilleSalaire calculerFeuilleSalaire(String SS, double nbHeuresTravaillées, int nbJoursTravaillés );
  // liste des employés
  List<Employe> findAllEmployes();
}
  • ligne 8 : la méthode permet de calculer le salaire d'un employé
  • ligne 10 : la méthode permet d'obtenir la liste des employés

On voit que la méthode init demande la liste des employés à la couche [métier]. Cette liste est enregistrée dans le champ de la ligne 4. S'il se produit une exception, celle-ci est mémorisée dans le champ de la ligne 6.

Pour conclure, l'objet unique [Config] contient :

  • une référence sur la couche [métier]
  • la liste des employés

XIX-D. Génération des vues Tiles

On l'a vu dans le fichier de configuration de Struts, les vues vont être générées par le framework Tiles. Nous n'allons expliquer que le strict nécessaire utile à l'écriture de notre application.

Tiles permet de générer des vues à partir d'une page maître. Celle-ci appelée ici [MasterPage.jsp] sera l'assemblage des fragments Jsp suivants :

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

Ces framents Jsp sont définis dans le projet Netbeans :

Image non disponible

Le framework Tiles nous permet de définir quels fragments seront insérés dans la page maître.

La page maître [MasterPage.jsp] est la suivante :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
<%@ 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>

La page maître est un conteneur de fragments Jsp. Ici, elle est l'assemblage de six fragments, ceux des lignes 17 à 23. A la génération, il peut y avoir de 0 à 6 fragments assemblés dans la page maître. Cette génération est gouvernée par le fichier [WEB-INF/tiles.xml] qui définit les vues Tiles :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
<?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>
 
 <!-- la page maître -->
  <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>
 
 <!-- la vue saisie -->
  <definition name="saisie" extends="masterPage">
    <put-attribute name="saisie"   value="/Saisie.jsp"/>     
  </definition>

  <!-- la vue simulation -->
 <definition name="simulation" extends="saisie">
    <put-attribute name="simulation"   value="/Simulation.jsp"/>     
  </definition>

 <!-- la vue simulations -->
  <definition name="simulations" extends="masterPage">
    <put-attribute name="simulations"   value="/Simulations.jsp"/>     
  </definition>
  
 <!-- la vue exception -->
  <definition name="exception" extends="masterPage">
    <put-attribute name="exception"   value="/Exception.jsp"/>     
  </definition>
 
 <!-- la vue erreur -->
 <definition name="erreur" extends="masterPage">
    <put-attribute name="erreur"   value="/Erreur.jsp"/>     
  </definition>
</tiles-definitions>
  • le fichier ci-dessus définit six vues Tiles nommées : masterPage (ligne 9), saisie (ligne 20), simulation (ligne 25), simulations (ligne 30), exception (ligne 35), erreur (ligne 40).
  • lignes 9-17 : définissent une vue appelée masterPage (name) et associée à la page maître [MasterPage.jsp] (template). On a vu que cette page Jsp définissait six sous-vues. Une vue Tiles associée à la page maître doit indiquer le fragment Jsp associé à chacune des six sous-vues. On voit que certaines sous-vues reçoivent pour valeur (value) la chaîne vide. Ces sous-vues ne seront pas incluses dans la page maître [MasterPage.jsp]. La vue Tiles nommée masterPage est donc constituée du seul sous-fragment [Entete.jsp].
  • lignes 20-22 : définissent une vue appelée saisie (name) et qui étend (extends) la vue nommée masterPage vue précédemment. Cela signifie qu'elle reprend toutes les définitions de la vue masterPage. Sa définition est équivalente à la suivante :
 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
<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>

On voit qu'elle est associée à la page Jsp [MasterPage.jsp] et qu'à ce titre elle doit définir les six sous-vues de cette page. On voit que la déinition de la ligne 9 annule celle de la ligne 4. La vue Tiles nommée saisie est donc constituée des fragments Jsp [Entete.jsp, Saisie.jsp]

Si nous continuons ce raisonnement, nous obtenons le tableau suivant :

vue Tiles pages 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

XIX-E. Les fichiers de messages

L'application a été internationalisée. On trouve les messages dans les fichiers [messages.properties] et [Formulaire.properties].

Le fichier [messages.properties] est le suivant :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
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}".

Le fichier [Formulaire.properties] est le suivant :

 
Sélectionnez
1.
2.
3.
4.
5.
# 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

XIX-F. La feuille de style

Les vues Tiles utilisent la feuille de style [styles.css] suivante :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
.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
}

XIX-G. La vue initiale

Pour étudier l'application, nous allons la présenter à partir des différentes actions de l'utilisateur. Nous étudierons à chaque fois, l'action Struts qui exécute cette action et la vue Tiles qui est envoyée en réponse.

Dans [struts.xml] nous avons les actions suivantes :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
<!-- 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>
  • lignes 2-8 : l'action par défaut de l'application est [Formulaire!input].

La classe [Formulaire] est la suivante :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
package web.actions;

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

  // configuration initialisée par Spring
  private Config config;
  // liste des employés
  private List<Employe> employes;
  // liste des erreurs
  private List<Erreur> erreurs;
  // feuille de salaire
  private FeuilleSalaire feuilleSalaire;
  // saisies
  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() {
  ....
  }

  // calcul du salaire
  public String calculSalaire() {
 ...
    }
  }

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

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

  // getters et setters
  ...
}
  • ligne 4 : l'action [Formulaire] implémente l'interface Preparable. Celle-ci n'a qu'une méthode, la méthode prepare de la ligne 24. Cette méthode est exécutée une fois avant toute méthode de l'action. Elle sert généralement à initialiser le modèle de l'action.

Le modèle de l'action est formé des lignes 6-21 :

  • ligne 7 : le champ config est initialisé par Spring comme il a été expliqué. Il donne accès aux données de portée application :
    • une référence sur la couche [métier]
    • une référence sur la liste des employés.
    • une référence sur l'exception qui a pu éventuellement se produire lors de l'instanciation de l'objet [Config]
  • ligne 9 : une liste des employés. Celle-ci va alimenter le combo des employés dans le fragment [Saisie.jsp].
  • ligne 11 : une liste d'erreurs. Celle-ci va alimenter le fragment [Erreur.jsp].
  • ligne 21 : la liste des options du menu du fragment [Entete.jsp]

Image non disponible

En [1], les liens du menu affiché sont contrôlés par le champ menu de l'action [Formulaire].

La méthode prepare est exécutée avant la méthode input. C'est la suivante :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
@Override
  public void prepare() throws Exception {
    // erreur de configuration ?
    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();
    }
}
  • ligne 4 : on récupère l'exception dans l'objet [Config] instancié par Spring
  • ligne 5 : s'il y a eu exception dans l'instanciation de l'objet [Config] alors on initialise la liste d'erreurs de la ligne 11. La classe [Erreur] est la suivante :
 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
package web.entities;

import java.io.Serializable;

public class Erreur implements Serializable{
  
  public Erreur() {
  }
  
  // champs
  private String classe;
  private String message;

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

La classe sert à mémoriser la pile des exceptions :

  • ligne 11 : la classe de l'exception
  • ligne 12 : le message de l'exception

Revenons à la méthode prepare :

  • ligne 13 : la liste des employés de l'objet [Config] est mémorisée dans le champ employes de l'action.

Une fois la méthode prepare exécutée, la méthode input va l'être à son tour. C'est la suivante :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
@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";
    }
}

La méthode input se contente de positionner la liste des options de menu à afficher. La classe [Menu] est la suivante :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
package web.entities;

import java.io.Serializable;

public class Menu implements Serializable {
  // éléments du menu

  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 et setters
...  
}
  • lignes 8-13 : il y a 6 liens possibles dans le menu
  • lignes 18-25 : le constructeur de la classe permet de fixer les liens qui doivent être affichés et ceux qui ne le doivent pas.

Les liens du menu sont affichés dans [Entete.jsp], fragment jsp présent dans toutes les vues Tiles. Chaque action aura un champ menu pour contrôler l'affichage du menu de [Entete.jsp].

Revenons à la méthode input :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
@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";
    }
}
  • lignes 3-6 : si la liste des erreurs est vide, le menu [Faire la simulation, Voir les simulations, Terminer la session] sera affiché et la clé input retournée.
  • lignes 9-10 : si la liste des erreurs n'est pas vide, le menu sera vide et la clé exception sera retournée.

Revenons à la configuration de l'action [Formulaire] dans [struts.xml] :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
<!-- 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>
  • ligne 5 : la clé input affiche la vue Tiles nommée saisie
  • ligne 4 : la clé exception affiche la vue Tiles nommée exception

Commençons par la vue Tiles nommée saisie. Elle est composée des framents Jsp [Entete.jsp] et [Saisie.jsp].

Le fragment [Entete.jsp] est le suivant :

Image non disponible

Son code est le suivant :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
<%@ 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>
  • lignes 8-25 : affichage des six liens du menu [Faire la simulation (lignes 8-10), Effacer la simulation (lignes 11-13), Voir les simulations (lignes 14-16), Retour au formulaire (lignes 17-19), Enregistrer la simulation (lignes 20-22), Terminer la session (lignes 23-25).
  • lignes 8, 11, 14, 17, 20, 23 : l'affichage des liens est contrôlé par le champ menu de l'action courante.

On remarquera que le fragment [Entete.jsp] affiche une table Html (lignes 4-28) mais n'est pas une page Html complète. Il ne faut pas oublier ici que toutes les vues de l'application s'insèrent dans la page maître [MasterPage.jsp] suivante :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
<%@ 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>

Le fragment [Entete.jsp] s'insère en ligne 17, à l'intérieur d'une page Html régulière.

Le fragment [Saisie.jsp] s'insère ligne 19. C'est la vue suivante :

Image non disponible

Le code du fragment [Saisie.jsp] est le suivant :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
<%@ 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(){
    // on poste le formulaire
    document.forms['Saisie'].elements['action'].name='action:Formulaire!calculSalaire'
    document.forms['Saisie'].submit();
  }  
</script>

<!-- saisie des informations -->
<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>
  • ligne 17 : le formulaire n'a pas d'attribut action. Par défaut, on a action='Formulaire'.
  • ligne 18 : affichage du combo des employés. Le contenu du combo (attribut list)est fourni par le champ employes de l'action courante. L'attribut value des options sera le n° SS des employés (attribut listKey). Le libellé affiché pour chaque option sera le prénom et le nom de l'employé (attribut listValue). Le n° SS de l'employé sélectionné dans le combo sera posté au champ [Formulaire].comboEmployesvalue (attribut name).
  • ligne 19 : champ de saisie des heures travaillées. La valeur affichée (attribut value) est celle du champ heuresTravaillées de l'action [Formulaire] au format suivant (Formulaire.properties) :

    La valeur sera postée au champ [Formulaire].heuresTravaillees (attribut name).
 
Sélectionnez
double.format= {0,number,#0.00##}
  • ligne 20 : champ de saisie des jours travaillés. La valeur affichée (attribut value) est celle du champ joursTravaillés de l'action [Formulaire].

La valeur sera postée au champ [Formulaire].joursTravailles (attribut name).

Au final, la vue Tiles saisie affichée au démarrage lorsqu'il n'y a pas d'erreurs est la suivante :

Image non disponible

Revenons à la configuration de l'action [Formulaire] :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
<!-- 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>

On a vu que l'action [Formulaire].input pouvait également rendre la clé exception de la ligne 4. Dans ce cas, c'est la vue Tiles nommée exception qui est affichée. Celle-ci est composée des fragments [Entete.jsp] et [Exception.jsp]. Nous avons déjà présenté le fragment [Entete.jsp]. Le fragment [Exception.jsp] est le suivant :

Image non disponible

Ceci est la page de démarrage de la version 2 de l'application lorsque le Sgbd n'a pas été lancé . Le code Jsp du fragment [Erreur.jsp] est le suivant :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
<%@ 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>
  • lignes 10-14 : un itérateur sur la collection List<Erreur> erreurs de l'action [Formulaire]. On se rappelle qu'en cas d'erreur on y avait stocké une pile d'exceptions.

XIX-H. Faire une simulation

Une fois la vue de démarrage obtenue, on peut faire un calcul de salaire via le lien [Faire une simulation].

XIX-H-1. Validation des saisies

Considérons la séquence suivante :

Image non disponible

  • en [1], une saisie erronée
  • en [2], la réponse envoyée.

Considérons la configuration de l'action [Formulaire] dans [struts.xml] :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
<!-- 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>

On sait qu'en cas d'erreur de validation, l'intercepteur de validation renvoie la clé input. C'est donc la vue Tiles saisie qui est renvoyée. Le processus de validation fait que les champs erronés sont accompagnés de messages d'erreur.

La validation de l'action [Formulaire] est assurée par le fichier [Formulaire-validation.xml] suivant :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
<!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>
  • les lignes 6-20 vérifient que le champ heuresTravaillees est un nombre réel dans l'intervalle [0,300].
  • les lignes 22-36 vérifient que le champ joursTravailles est un nombre entier dans l'intervalle [0,31].

Revenons la configuration de l'action [Formulaire] dans [struts.xml] :

 
Sélectionnez
1.
2.
3.
4.
5.
<!-- action Formulaire -->
    <action name="Formulaire" class="web.actions.Formulaire">
      ...
      <result name="input" type="tiles">saisie</result>
</action>

On sait qu'en cas d'erreur de validation, l'intercepteur de validation renvoie la clé input. C'est donc la vue Tiles saisie qui est renvoyée.

Rappelons que celle-ci est composée des fragments [Entete.jsp] et [Saisie.jsp] où [Entete.jsp] comporte un titre et un choix d'options et [Saisie.jsp] le formulaire de saisie. En cas d'erreurs de saisie, le processus de validation fait que les champs erronés sont accompagnés de messages d'erreur et affichent de plus leur valeur erronée. Le fragment [Entete.jsp] n'intervient en rien dans le processus de validation. Regardons son code :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
<%@ 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>

Les six liens sont configurés par le champ menu du modèle (lignes 8,11, 14, 17, 20, 23). Lorsqu'il y a une erreur, ce modèle n'est pas mis à jour par l'action, et on retrouve alors une page sans menu. Pour remédier à ce problème, la classe [Formulaire] a la méthode validate suivante :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
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() {
  ...
  }

  // calcul du salaire
  public String calculSalaire() {
  ...
  }

  @Override
  public void validate() {
    // des erreurs ?
    if (!getFieldErrors().isEmpty()) {
      // menu
      menu = new Menu(true, false, false, true, false, true);
    }
  }

  // getters et setters
  ...
}
  • ligne 27 : on sait que lorsqu'elle est présente, la méthode validate est exécutée par le processus de validation. On en profite pour mettre à jour le menu de la ligne 4 qui fait partie du modèle du fragment [Entete.jsp].
  • lignes 29-32 : s'il y a eu des erreurs de validation, alors on positionne le menu pour réafficher la vue Tiles saisie. S'il n'y a pas eu d'erreurs de validation, alors on ne fait rien. C'est la méthode calculSalaire qui est alors chargée de créer le modèle de la vue à afficher.

XIX-H-2. Le calcul du salaire

Revenons au code Jsp de l'entête :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
<%@ 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>
  • ligne 9 : lorsque l'utilisateur clique sur le lien [Faire la simulation], la fonction Javascript doSimulation est exécutée. Celle-ci est défini dans le fragment [Saisie.jsp] :
 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
<script language="javascript" type="text/javascript">
  function doSimulation(){
    // on poste le formulaire
    document.forms['Saisie'].elements['action'].name='action:Formulaire!calculSalaire'
    document.forms['Saisie'].submit();
  } 
</script>

<!-- saisie des informations -->
<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>
  • ligne 14 : un champ caché nommé action sera posté à l'action [Formulaire]. Ce champ va nous permettre de préciser l'action et la méthode qui doivent être exécutées au POST du formulaire. On se rappelle peut-être des premiers exemples, que celles-ci peuvent être précisées dans un paramètre nommé action:Action!méthode. Peu importe la valeur de ce paramètre. Il suffit qu'il soit présent.
  • lignes 2-6 : la fonction Javascript qui est exécutée lorsque l'utilisateur clique sur le lien [Faire la simulation] du fragment [Entete.jsp].
  • ligne 4 : on change l'attribut name du champ caché action. On fait en sorte qu'il soit de la forme action:Action!méthode attendue par Struts.
  • ligne 5 : le formulaire nommé Saisie de la ligne 5 est posté. Du coup est postée la chaîne de paramètres suivante :

    comboEmployesValue=SS1&heuresTravaillees=xx&joursTravailles=yy&action:Formulaire!calculSalaire

    SS1 : n° INSEE de l'employé sélectionné dans le combo

    heuresTravaillees : nombre d'heures travaillées

    joursTravailles : nombre de jours travaillés

    action:Formulaire!calculSalaire : les éléments ci-dessus seront postés à l'action [Formulaire] puis la méthode calculSalaire de cette action sera exécutée.

La méthode [Formulaire].calculSalaire est la suivante :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
// calcul du salaire
  public String calculSalaire() {
    try {
      // calcul salaire
      feuilleSalaire = config.getMetier().calculerFeuilleSalaire(comboEmployesValue, heuresTravaillees, joursTravailles);
      // on met la simulation dans la session
      session.put("simulation", new Simulation(0, "" + heuresTravaillees, "" + joursTravailles, feuilleSalaire));
      // menu
      menu = new Menu(true, true, true, true, false, true);
      // fini
      return "simulation";
    } catch (Throwable th) {
      ...
    }
}
  • ligne 5 : le calcul de la feuille de salaire est demandée à la couche [métier]
  • ligne 7 : on met un objet de type Simulation dans la session de l'utilisateur. En effet, on peut en avoir besoin lors d'une requête ultérieure. La classe [Simulation] est la suivante :
 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
package web.entities;

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

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

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

  // getters et setters
...
}
  • ligne 12 : le n° de la simulation. Est incrémenté à chaque nouvelle simulation enregistrée.
  • ligne 13 : la feuille de salaire de l'employé
  • ligne 14 : son nombre d'heures travaillées
  • ligne 15 : son nombre de jours travaillés
  • ligne 25 : la méthode getIndemnites rend le total des indmnités de l'employé

Nous verrons que la classe [Simulation] est le modèle du fragment [Simulations.jsp] qui présente toutes les simulations faites.

Retour à la méthode [Formulaire].calculSalaire :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
// calcul du salaire
  public String calculSalaire() {
    try {
      // calcul salaire
      feuilleSalaire = config.getMetier().calculerFeuilleSalaire(comboEmployesValue, heuresTravaillees, joursTravailles);
      // on met la simulation dans la session
      session.put("simulation", new Simulation(0, "" + heuresTravaillees, "" + joursTravailles, feuilleSalaire));
      // menu
      menu = new Menu(true, true, true, true, false, true);
      // fini
      return "simulation";
    } catch (Throwable th) {
 ...
}
  • ligne 9 : mise à jour du menu
  • ligne 11 : renvoi de la clé de navigation simulation.

Retour à la configuration de l'action [Formulaire] :

 
Sélectionnez
1.
2.
3.
4.
5.
<!-- action Formulaire -->
    <action name="Formulaire" class="web.actions.Formulaire">
         ...
      <result name="simulation" type="tiles">simulation</result>
</action>

La ligne 4, montre que la clé de navigation simulation fait afficher la vue Tiles nommée simulation. Celle-ci est composée des fragments Jsp suivants : [Entete, Saisie, Simulation].

La vue rendue est la suivante :

Image non disponible

  • en [1], le fragment [Entete.jsp]
  • en [2], le fragment [Saisie.jsp]
  • en [3], le fragment [Simulation.jsp]. On rappelle que la feuille de salaire présentée est la feuille de salaire fictive rendue par la couche [metier].

Les deux premiers fragments ont déjà été présentés. Le fragment [Simulation.jsp] est le suivant :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.
119.
120.
121.
122.
123.
124.
125.
126.
127.
128.
129.
130.
131.
132.
133.
134.
135.
136.
137.
138.
139.
140.
141.
142.
143.
144.
145.
146.
147.
148.
149.
150.
151.
152.
153.
154.
155.
156.
157.
158.
159.
160.
161.
162.
163.
164.
165.
166.
167.
168.
169.
170.
171.
172.
173.
174.
175.
176.
177.
178.
179.
180.
181.
182.
183.
184.
185.
186.
187.
188.
189.
190.
191.
192.
193.
194.
195.
196.
197.
198.
199.
200.
201.
202.
203.
204.
205.
206.
207.
208.
209.
210.
211.
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>

<hr/>
<!-- informations Employé -->
<span class="titreInfos">
  <s:text  name="Simulation.Infos.employe"/>
</span>
<br/><br/>
<table>
  <!-- ligne 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>
  <!-- ligne 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>
  <!-- ligne 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>
  <!-- ligne 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>
<!-- informations Cotisations -->
<br/>
<span class="titreInfos">
  <s:text name="Simulation.Infos.cotisations"/>
</span>

<br/><br/>
<table>
  <!-- ligne 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>
  <!-- ligne 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>
<!-- informations Indemnités -->
<br/>
<span class="titreInfos">
  <s:text name="Form.Infos.indemnites"/>
</span>
<br/><br/>
<table>
  <!-- ligne 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>
  <!-- ligne 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>
<!-- informations Salaire -->
<br/>
<span class="titreInfos">
  <s:text name="Simulation.Infos.Salaire"/>
</span>
<br/><br/>
<table>
  <!-- ligne 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>

  <!-- ligne 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>
<!-- Salaire net-->
<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>

C'est long ... mais c'est fonctionnellement simple. Ce fragment affiche les différentes propriétés du champ [Formulaire].feuilleSalaire qui représente la feuille de salaire de l'employé.

Retour à la méthode [Formulaire].calculSalaire :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
// calcul du salaire
  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";
    }
  }

La calcul du salaire peut mal se passer. Ce serait le cas notamment si le lien avec le Sgbd se cassait. Dans ce cas, on gère l'exception qui se produit. Nous avons déjà rencontré ce cas lors de l'étude de la méthode [Formulaire].input.

  • lignes 7-11 : on crée une liste d'objets Erreur à partir de la pile des exceptions
  • ligne 13 : on fixe le menu
  • ligne 14 : on rend la clé exception.

La clé exception va faire afficher la vue Tiles exception :

 
Sélectionnez
1.
2.
3.
4.
5.
<!-- action Formulaire -->
    <action name="Formulaire" class="web.actions.Formulaire">
      <result name="exception" type="tiles">exception</result>
      ...
</action>

Cette vue Tiles a déjà été présentée. Elle a l'allure suivante :

Image non disponible

XIX-I. Enregistrer une simulation

Après avoir fait une simulation, l'utilisateur peut vouloir l'enregistrer dans la session.


Image non disponible


Image non disponible

  • en [1], on enregistre la simulation
  • en [2], la réponse qui présente la liste des simulations déjà faites à laquelle est ajoutée la nouvelle simulation

Le lien [Enregistrer la simulation] se trouve dans le fragment [Entete.jsp] :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
<%@ 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>

On voit qu'un clic sur le lien provoque l'exécution de l'action [EnregistrerSimulation]. Celle-ci est configurée dans le fichier [struts.xml] de la façon suivante :

 
Sélectionnez
1.
2.
3.
4.
5.
<!-- 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>
  • ligne 1 : l'action [EnregistrerSimulation] est associée à la classe [Enregistrer] et à sa méthode execute.

La classe [Enregistrer] est la suivante :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
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;
  }

  // exécution de l'action
  public String execute() {
    // on récupère la dernière simulation dans la session
    Simulation simulation = (Simulation) session.get("simulation");
    if (simulation == null) {
      return ERROR;
    }
    ...
  }

  // getters et setters
  ...
}
  • ligne 4 : parce que l'action doit avoir accès à la session, elle implémente l'interface SessionAware.
  • ligne 7 : la session
  • ligne 9 : le menu

Lorsque l'action [Enregistrer] a été instanciée, sa méthode execute est exécutée. On rappelle que son travail est de mettre dans la session la dernière simulation. Celle-ci sera ajoutée à la liste de simulations déjà faites, elle-aussi conservée dans la session.

  • ligne 19 : on récupère dans la session la dernière simulation qui y a été placée.
  • lignes 20-22 : si on ne la trouve pas, alors c'est que probablement la session a expiré. En effet, celle-ci ne dure qu'un certain temps qu'on peut fixer dans le fichier [web.xml] qui configure l'application.
  • ligne 21 : on rend la clé error.

Retour à la configuration de l'action [EnregistrerSimulation] :

 
Sélectionnez
1.
2.
3.
4.
5.
<!-- 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>

On voit que la clé error (ligne 3) amène l'affichage de la vue Tiles nommée erreur. Celle-ci est composée des fragments [Entete.jsp] et [Erreur.jsp] et à l'aspect suivant :

Image non disponible

Le frament [Erreur.jsp] est le suivant :

 
Sélectionnez
1.
2.
3.
4.
5.
<%@ 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>

Retour à la méthode [Enregistrer].execute :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
// exécution de l'action
  public String execute() {
    // on récupère la dernière simulation dans la session
    Simulation simulation = (Simulation) session.get("simulation");
    if (simulation == null) {
      return ERROR;
    }
    // on récupère le  de la dernière simulation
    Integer numDerniereSimulation = (Integer) session.get("numDerniereSimulation");
    if (numDerniereSimulation == null) {
      numDerniereSimulation = 0;
    }
    // on l'incrémente
    numDerniereSimulation++;
    // on la remet le nouveau  dans la session
    session.put("numDerniereSimulation", numDerniereSimulation);
    // on récupère la liste des simulations
    List<Simulation> simulations = (List<Simulation>) session.get("simulations");
    if (simulations == null) {
      simulations = new ArrayList<Simulation>();
      session.put("simulations", simulations);
    }
    // on lui ajoute la simulation courante
    simulation.setNum(numDerniereSimulation);
    simulations.add(simulation);
    // on affiche la liste des simulations
    menu = new Menu(false, false, false, false, true, true);
    return "simulations";
}
  • lignes 9-16 : les différentes simulations sont numérotées à partir de 1. Le dernier n° attribué est mis dans la session sous la clé numDerniereSimulation. Le code des lignes 9-16 consiste à récupérer cette clé et à incrémenter la valeur qui lui est associée.
  • lignes 18-22 : la liste des simulations est conservée dans la session associée à la clé simulations. Les lignes 18-22 consistent à retrouver cette liste si elle existe ou à la créer si elle n'existe pas.
  • lignes 24-25 : une fois la liste des simulations obtenues, on lui ajoute la simulation courante (ligne 25). Auparavant, la simulation courante a reçu un numéro (ligne 24).
  • ligne 27 : on fixe le menu à afficher
  • ligne 28 : on rend la clé de navigation simulations.

Retour à la configuration de l'action [EnregistrerSimulation] dans [struts.xml] :

 
Sélectionnez
1.
2.
3.
4.
5.
<!-- 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>

Ligne 4, la clé simulations provoque l'affichage de la vue Tiles nommée simulations. Cette vue est composée des fragments [Entete.jsp] et [Simulations.jsp]. La vue affichée est la suivante :


Image non disponible

  • en [1], le fragment [Entete.jsp] que nous connaissons bien maintenant.
  • en [2], le fragment [Simulations.jsp]

Le fragment [Simulations.jsp] est le suivant :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>

<!-- liste de simulations vide -->
<s:if test="#session['simulations']==null || #session['simulations'].size()==0">
  <h2><s:text name="Pam.SimulationsVides.titre"/></h2>
</s:if>
<!-- liste de simulations non vide -->
<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>
  • lignes 5-7 : s'il n'y a pas de simulations dans la session, alors on affiche la vue suivante :

    Image non disponible
  • lignes 13-21 : affichage des entêtes des colonnes du tableau

    Image non disponible
  • lignes 23-55 : itérateur sur la liste des simulations trouvée dans la session
  • lignes 24-26 : création d'une Url nommé url (attribut id). Le lien Html généré par cette Url est le suivant :

    <a href="/pam/SupprimerSimulation.action?id=1">Retirer</a>

    On voit que le lien cible l'action [SupprimerSimulation] avec le paramètre id qui représente le n° de la simulation à retirer de la liste.
  • lignes 28-54 : à chaque itération sur la liste des simulations, on affiche les propriétés de la simulation courante.
Image non disponible

XIX-J. Retirer une simulation

L'utilisateur peut vouloir enlever une simulation de la liste des simulations :

Image non disponible


Image non disponible

  • en [1], on supprime la simulation n° 1
  • en [2], la simulation n° 1 a été supprimée

Le lien [Retirer] se trouve dans le fragment [Simulations.jsp] que nous avons déjà étudié :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>

<!-- liste de simulations vide -->
<s:if test="#session['simulations']==null || #session['simulations'].size()==0">
  <h2><s:text name="Pam.SimulationsVides.titre"/></h2>
</s:if>
<!-- liste de simulations non vide -->
<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>
  • lignes 16-18 : génèrent le lien Html

    <a href="/pam-01/SupprimerSimulation.action?id=num">Retirer</a>

    num est le n° de la simulation à retirer.

L'action [SupprimerSimulation] est définie comme suit dans le fichier [struts.xml] :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
<?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>
  <!-- internationalisation -->
  <constant name="struts.custom.i18n.resources" value="messages" />
  <!-- intégration Spring -->
  <constant name="struts.objectFactory.spring.autoWire" value="name" />

  
  <!-- actions Struts /Tiles -->
  <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>
  • ligne 16 : l'action [SupprimerSimulation] est associée à la classe [Supprimer]. Parce qu'aucune méthode n'est précisée, c'est sa méthode execute qui sera exécutée. La classe [Supprimer] est la suivante :
 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
package web.actions;

...
public class Supprimer extends ActionSupport implements SessionAware {

  // session
  private Map<String, Object> session;
  // id de la simulation à supprimer
  private String id;
  // menu
  private Menu menu;

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

  // exécution de l'action
  public String execute() {
    // on récupère les simulations dans la session
    List<Simulation> simulations = (List<Simulation>) session.get("simulations");
    if (simulations == null) {
      // cas anormal - la session a du expirer
      menu = new Menu(false, false, false, false, true, true);
      return "erreur";
    }
    // test de id
    int num = 0;
    boolean erreur = false;
    try {
      num = Integer.parseInt(id);
      erreur = num <= 0;
    } catch (NumberFormatException ex) {
      // anormal
      erreur = true;
    }
    // erreur ?
    if (erreur) {
      menu = new Menu(false, false, false, false, true, true);
      return "erreur";
    }
    // on recherche la simulation à supprimer
    for (int i = 0; i < simulations.size(); i++) {
      if (num == simulations.get(i).getNum()) {
        simulations.remove(i);
        break;
      }
    }
    // on affiche la liste des simulations
    menu = new Menu(false, false, false, false, true, true);
    return "simulations";
  }

  // getters et setters
...
}
  • ligne 4 : l'action [Supprimer] implémente l'interface [SessionAware] pour avoir accès à la session.
  • ligne 7 : la session
  • ligne 9 : le n° de la simulation à supprimer. On se rappelle en effet qu'on arrive à l'instanciation de la classe [Supprimer] par l'Url Html :

    <a href="/pam-01/SupprimerSimulation.action?id=num">Retirer</a>

    num est le n° de la simulation à retirer. Ce numéro sera stocké dans le champ id de la ligne 9.
  • ligne 11 : le menu pour la vue qui sera affichée en réponse à la requête
  • ligne 19 : la méthode execute qui va générer la réponse à la requête.
  • ligne 21 : on récupère la liste des simulations déjà faites dans la session
  • lignes 22-26 : ne pas récupérer cette liste dans la session veut probablement dire que la session a expiré. On a déjà rencontré ce cas. On rend la clé erreur qui fait afficher la vue Tiles erreur :
 
Sélectionnez
1.
2.
3.
4.
5.
<!-- action RetirerSimulation -->
    <action name="SupprimerSimulation" class="web.actions.Supprimer">
      <result name="erreur" type="tiles">erreur</result>
      ...
</action>

La vue Tiles erreur a été présentée chapitre XIX.

  • lignes 28-36 : on vérifie que la chaîne id de la ligne 9 représente bien un nombre entier >0.
  • lignes 38-40 : si ce n'est pas le cas, on rend de nouveau la clé erreur qui fera afficher la vue Tiles erreur
  • lignes 43-48 : la simulation à retirer est cherchée dans la liste des simulations. Si elle est trouvée, elle est supprimée.
  • ligne 50 : on met le menu à jour pour la vue Tiles simulations.
  • ligne 51 : on rend la clé simulations. Celle-ci va faire afficher la vue Tiles simulations :
 
Sélectionnez
1.
2.
3.
4.
5.
<!-- action RetirerSimulation -->
    <action name="SupprimerSimulation" class="web.actions.Supprimer">
      ...
      <result name="simulations" type="tiles">simulations</result>
</action>

La vue Tiles simulations a été présentée chapitre XIX.

XIX-K. Revenir au formulaire

A partir de la vue Tiles simulations, l'utilisateur peut revenir au formulaire :


Image non disponible


Image non disponible

  • en [1], on clique sur le lien de retour au formulaire
  • en [2], on retrouve un formulaire vide

Le lien [Retour au formulaire de simulation] est défini dans le fragment [Entete.jsp] :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
<%@ 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>
  • ligne 10 : le lien pointe sur l'action [RetourFormulaire]. Celle-ci est définie de la façon suivante dans le fichier [struts.xml] :
 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
   <!-- action RetourFormulaire -->
    <action name="RetourFormulaire" >
      <result type="redirectAction">
        <param name="actionName">Formulaire!input</param>
        <param name="namespace">/</param>
      </result>
</action>

On voit que cette action n'est associée à aucune classe. Elle se contente de rediriger le navigateur client vers l'action [/Formulaire!input]. On est alors dans le même cas que lors de l'affichage de la vue initiale expliquée au chapitre XIX. On retrouve donc cette vue initiale [2].

XIX-L. Voir la liste des simulations

A partir des vues Tiles simulation ou saisie, l'utilisateur peut demander à voir les simulations :


Image non disponible

Image non disponible

  • en [1], on clique sur le lien [Voir les simulations]
  • en [2], on retrouve la liste des simulations

Le lien [Voir les simulations] est défini dans le fragment [Entete.jsp] :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
<%@ 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>
  • ligne 10 : le lien [Voir les simulations] appelle l'action [VoirSimulations]. Celle-ci est définie de la façon suivante dans le fichier [struts.xml] :
 
Sélectionnez
1.
2.
3.
4.
   <!-- action VoirSimulations -->
    <action name="VoirSimulations" class="web.actions.Voir">
      <result name="success" type="tiles">simulations</result>
</action>

L'action [VoirSimulations] est associée à la classe [Voir] sans précision de méthode. C'est donc la méthode [Voir].execute qui sera exécutée. La classe [Voir] est la suivante :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
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 et setters

  public Menu getMenu() {
    return menu;
  }

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

L'action [Voir] ne fait qu'une chose : positionner le menu pour la vue Tiles simulations (ligne 8). Il n'y a pas de méthode execute. C'est donc celle de la classe parent [ActionSupport] qui sera exécutée. On sait qu'elle ne fait rien si ce n'est rendre la clé success.

Retour à l'action dans [struts.xml] :

 
Sélectionnez
1.
2.
3.
4.
   <!-- action VoirSimulations -->
    <action name="VoirSimulations" class="web.actions.Voir">
      <result name="success" type="tiles">simulations</result>
</action>

Ligne 3, on voit que la clé success conduit à l'affichage de la vue Tiles simulations. Celle-ci a été décrite dans le chapitre XIX.

XIX-M. Effacer la simulation courante

A partir de la vues Tiles simulation, l'utilisateur peut demander à effacer la simulation courante :

Image non disponible

  • en [1], on efface la simulation courante
  • en [2], on retrouve le formulaire de saisie vide

Le lien [Effacer la simulation] est défini dans le fragment [Entete.jsp] :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
<%@ 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>

Ligne 10, on voit que le lien [Effacer la simulation] déclenche l'action [Formulaire!input]. On sait que cette action amène à la vue initiale [2].

XIX-N. Terminer la session courante

A partir de toutes les vues Tiles, l'utilisateur peut demander à terminer la session :

Image non disponible

Image non disponible

  • en [1], on part de la vue des simulations et on termine la session
  • en [2], on retrouve le formulaire de saisie vide. On demande à voir les simulations.
  • en [3], la liste des simulations est désormais vide.

Le lien [Terminer la session] est défini dans le fragment [Entete.jsp] :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
<%@ 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>

Ligne 10, on voit que le lien [Terminer la session] déclenche l'action [TerminerSession]. Celle-ci est définie de la façon suivante dans le fichier [struts.xml] :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
<action name="TerminerSession" class="web.actions.Terminer">
      <result name="success" type="redirectAction">
        <param name="actionName">Formulaire!input</param>
        <param name="namespace">/</param>
      </result>
</action>
  • ligne 1 : on voit que la classe [Terminer] va être instanciée et sa méthode execute exécutée.
  • lignes 2-5 : après exécution de la méthode [Terminer].execute, il y aura redirection sur la vue initiale de saisie. C'est ce qui explique l'écran n° 2.

La classe [Terminer] est la suivante :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
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() {
    // abandon de la session courante
    session.clear();
    return SUCCESS;
  }

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

Le rôle de l'action [Terminer] est de vider la session courante de ses attributs.

  • ligne 7 : l'action [Terminer] implémente l'interface [SessionAware] pour avoir accès à la session.
  • ligne 10 : le dictionnaire de la session
  • ligne 13 : la méthode execute qui est exécutée
  • ligne 15 : elle vide le dictionnaire de la session. Du coup, la liste des simulations qui est en session va disparaître. C'est ce qui explique l'écran n° 3.
  • ligne 16 : elle rend la clé success, qui on l'a vu, va afficher la vue Tiles saisie [2].

XIX-O. Conclusion

Nous avons entièrement commenté la version 1 de notre étude de cas qui travaille avec une couche [metier] simulée :

Image non disponible

Il nous reste plus qu'à " brancher " la vraie couche métier sur la couche [web].


précédentsommairesuivant

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2012 Serge Tahe. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.