Skip to content

12. Wersja 7 – Aplikacja internetowa PAM z wieloma widokami / wieloma stronami

Wracamy tutaj do pierwotnej architektury, w której warstwa [métier] była symulowana. Wiemy już, że można ją z łatwością zastąpić rzeczywistą warstwą [métier]. Symulowana warstwa [métier] ułatwia przeprowadzanie testów.

Aplikacja JSF jest typu MVC (Model-Widok-Kontroler):

  • serwlet [Faces Servlet] jest kontrolerem generycznym dostarczanym przez JSF. Kontroler ten jest rozszerzany przez specyficzne dla aplikacji procedury obsługi zdarzeń. Dotychczasowe procedury obsługi zdarzeń były metodami klas pełniących rolę szablonów dla stron JSF
  • Strony JSF wysyłają odpowiedzi do przeglądarki klienta. Są to widoki aplikacji.
  • Strony JSF zawierają elementy dynamiczne, które nazywane są modelem strony. Należy przypomnieć, że według niektórych autorów model obejmuje elementy przetwarzane przez aplikację, takie jak na przykład klasy FeuilleSalaire lub Employe. Aby odróżnić te dwa modele, można mówić o modelu aplikacji i modelu strony JSF.

W architekturze JSF przejście ze strony JSFi na stronę JSFj może stanowić problem.

  • Wyświetlono stronę JSFi. Z tej strony użytkownik wywołuje stronę POST poprzez dowolne zdarzenie [1]
  • w JSF, to POST zostanie przetworzone [2a,2b] zazwyczaj przez metodę C modelu Mi strony JSFi. Można powiedzieć, że metoda C jest kontrolerem drugorzędnym.
  • Jeśli po zakończeniu działania tej metody strona JSFj ma zostać wyświetlona, kontroler C musi:
  1. zaktualizować [2c] – model Mj strony JSFj
  2. przekazać kontrolerowi głównemu [2a] klucz nawigacyjny, który umożliwi wyświetlenie strony JSFj

Krok 1 wymaga, aby szablon Mi strony JSFi zawierał odwołanie do szablonu Mj strony JSFj. To nieco komplikuje sprawę, powodując wzajemną zależność szablonów Mi. W rzeczywistości menedżer C szablonu Mi, który aktualizuje szablon Mj, musi znać ten ostatni. Jeśli zajdzie potrzeba zmiany szablonu Mj, konieczna będzie również zmiana menedżera C szablonu Mi.

Istnieje jednak przypadek, w którym można uniknąć wzajemnej zależności modeli: jest to sytuacja, w której istnieje jeden model M, który obsługuje wszystkie strony JSF. Architektura ta ma zastosowanie wyłącznie w aplikacjach posiadających zaledwie kilka widoków, ale w takim przypadku okazuje się bardzo prosta w użyciu. Właśnie z niej obecnie korzystamy.

W tym kontekście architektura aplikacji wygląda następująco:

12.1. Widoki aplikacji

Użytkownikowi będą prezentowane następujące widoki:

  • widok [VueSaisies], który przedstawia formularz symulacji

  • widok [VueSimulation] służący do wyświetlania szczegółowych wyników symulacji:

Image

Image

  • widok [VueSimulations], zawierający listę symulacji przeprowadzonych przez klienta

Image

  • widok [VueSimulationsVides], który wskazuje, że klient nie ma żadnych symulacji lub nie ma ich już:

Image

  • widok [VueErreur], który wskazuje na jeden lub więcej błędów:

Image

12.2. Projekt NetBeans dla warstwy [web]

Projekt NetBeans tej wersji będzie następującym projektem Maven:

  • w [1], pliki konfiguracyjne,
  • w [2] – strony, w JSF,
  • w [3] – arkusz stylów i obraz tła widoków,
  • w pliku [4] – klasy warstwy [web],
  • w pliku [5] – niższe warstwy aplikacji,
  • w pliku [6] – plik komunikatów do internacjonalizacji aplikacji,
  • w pliku [7] – zależności projektu.

Przyjrzyjmy się niektórym z tych elementów.

12.2.1. Pliki konfiguracyjne

Pliki [web.xml] i [faces-config.xml] są identyczne jak w poprzednim projekcie, z wyjątkiem jednego szczegółu w pliku [web.xml]:


<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
  ...
  <welcome-file-list>
    <welcome-file>faces/saisie.xhtml</welcome-file>
  </welcome-file-list>
  ...
</web-app>
  • wiersze 4–6: stroną główną jest strona [saisie.xhtml]

12.2.2. Arkusz stylów

Plik [styles.css] ma następującą treść:


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

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

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

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

}

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

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

Oto przykłady kodu JSF wykorzystujące te style:

Widok symulacji


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

Otrzymany wynik wygląda następująco:

Image

Widok błędu


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

Image

12.2.3. Plik komunikatów

Plik komunikatów [messages_fr.properties] ma następującą treść:


form.titre=Simulateur de calcul de paie
form.comboEmployes.libell\u00e9=Employ\u00e9
form.heuresTravaill\u00e9es.libell\u00e9=Heures travaill\u00e9es
form.joursTravaill\u00e9s.libell\u00e9=Jours travaill\u00e9s
form.heuresTravaill\u00e9es.required=Indiquez le nombre d'heures travaill\u00e9es
form.heuresTravaill\u00e9es.validation=Donn\u00e9e incorrecte
form.joursTravaill\u00e9s.required=Indiquez le nombre de jours travaill\u00e9s
form.joursTravaill\u00e9s.validation=Donn\u00e9e incorrecte
form.btnSalaire.libell\u00e9=Salaire
form.btnRaz.libell\u00e9=Raz
exception.header=L'exception suivante s'est produite
exception.httpCode=Code HTTP de l'erreur
exception.message=Message de l'exception
exception.requestUri=Url demand\u00e9e lors de l'erreur
exception.servletName=Nom de la servlet demand\u00e9e lorsque l'erreur s'est produite
form.infos.employ\u00e9=Informations Employ\u00e9
form.employe.nom=Nom
form.employe.pr\u00e9nom=Pr\u00e9nom
form.employe.adresse=Adresse
form.employe.ville=Ville
form.employe.codePostal=Code postal
form.employe.indice=Indice
form.infos.cotisations=Informations Cotisations sociales
form.cotisations.csgrds=CSGRDS
form.cotisations.csgd=CSGD
form.cotisations.retraite=Retraite
form.cotisations.secu=S\u00e9curit\u00e9 sociale
form.infos.indemnites=Informations Indemnit\u00e9s
form.indemnites.salaireHoraire=Salaire horaire
form.indemnites.entretienJour=Entretien / Jour
form.indemnites.repasJour=Repas / Jour
form.indemnites.cong\u00e9sPay\u00e9s=Cong\u00e9s pay\u00e9s
form.infos.salaire=Informations Salaire
form.salaire.base=Salaire de base
form.salaire.cotisationsSociales=Cotisations sociales
form.salaire.entretien=Indemnit\u00e9s d'entretien
form.salaire.repas=Indemnit\u00e9s de repas
form.salaire.net=Salaire net
form.menu.faireSimulation=| Faire la simulation
form.menu.effacerSimulation=| Effacer la simulation
form.menu.enregistrerSimulation=| Enregistrer la simulation
form.menu.retourSimulateur=| Retour au simulateur
form.menu.voirSimulations=| Voir les simulations
form.menu.terminerSession=| Terminer la session
simulations.headers.nom=Nom
simulations.headers.nom=Nom
simulations.headers.prenom=Pr\u00e9nom
simulations.headers.heuresTravaillees=Heures travaill\u00e9es
simulations.headers.joursTravailles=Jours Travaill\u00e9s
simulations.headers.salaireBase=Salaire de base
simulations.headers.indemnites=Indemnit\u00e9s
simulations.headers.cotisationsSociales=Cotisations sociales
simulations.headers.salaireNet=SalaireNet
simulations.headers.numero=N\u00b0
erreur.titre=Une erreur s'est produite.
erreur.exceptions=Cha\u00eene des exceptions
exception.type=Type de l'exception
exception.message=Message associ\u00e9

12.2.4. Warstwa [métier]

Symulowana warstwa [métier] została zmodyfikowana w następujący sposób:


package metier;

...
public class Metier implements ImetierLocal, Serializable {
  
  // lista pracowników
  private Map<String,Employe> hashEmployes=new HashMap<String,Employe>();
  private List<Employe> listEmployes;
  
  // pobierz listę płac
  public FeuilleSalaire calculerFeuilleSalaire(String SS,
    double nbHeuresTravaillées, int nbJoursTravaillés) {
    // pobieranie danych pracownika
    Employe e=hashEmployes.get(SS);
    // zwracamy wyjątek, jeśli pracownik nie istnieje
    if(e==null){
      throw new PamException(String.format("L'employé de n° SS [%s] n'existe pas",SS),1);
    }
    // zwracamy fikcyjny list płac
    return new FeuilleSalaire(e,new Cotisation(3.49,6.15,9.39,7.88),e.getIndemnite(),new ElementsSalaire(100,100,100,100,100));
  }
  
  // lista pracowników
  public List<Employe> findAllEmployes() {
    if(listEmployes==null){
      // tworzy się listę trzech pracowników
      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)));
      // słownik pracowników
      for(Employe e:listEmployes){
        hashEmployes.put(e.getSS(),e);
      }
      // dodajemy pracownika, który nie będzie figurował w słowniku
      listEmployes.add(new Employe("X","Y","Z","La brûlerie","St Marcel","49014",new Indemnite(1,1.93,2,3,12)));
    }
    // wyświetlanie listy pracowników
    return listEmployes;
  }
}
  • wiersz 8: lista pracowników
  • wiersz 7: ta sama lista w postaci słownika indeksowanego według numeru SS pracowników
  • wiersze 24–39: metoda findAllEmployes, która zwraca listę pracowników. Metoda ta tworzy listę statyczną i odwołuje się do pola employés z wiersza 8.
  • wiersze 27–33: tworzona jest lista oraz słownik zawierający dwóch pracowników
  • wiersz 35: pracownik zostaje dodany do listy pracowników (wiersz 8), ale nie do słownika hashEmployes (wiersz 7). Ma to na celu wyświetlenie go w liście rozwijanej pracowników, ale jednocześnie uniemożliwienie jego rozpoznania przez metodę calculerFeuilleSalaire (wiersz 14), co spowoduje wygenerowanie wyjątku (wiersz 17).
  • wiersze 11–21: metoda calculerFeuilleSalaire
  • wiersz 14: pracownik jest wyszukiwany w słowniku hashEmployes na podstawie jego numeru SS. Jeśli nie zostanie znaleziony, generowany jest wyjątek (wiersze 16–18). W ten sposób otrzymamy wyjątek dla pracownika o numerze SS X, dodanego w wierszu 35 do listy pracowników, ale nieznajdującego się w słowniku hashEmployes.
  • W wierszu 20 tworzona jest i zwracana fikcyjna karta płacowa.

12.3. Beany aplikacji

Będą trzy komponenty typu bean o trzech różnych zakresach:

  

12.3.1. Bean ApplicationData

Bean ApplicationData będzie miał zakres application:


package web.beans.application;

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

@Named
@ApplicationScoped
public class ApplicationData implements Serializable {

  // warstwa biznesowa
  private IMetierLocal metier = new Metier();
  // moduł rejestrujący
  private boolean logsEnabled = true;
  private final Logger logger = Logger.getLogger("pam");

  public ApplicationData() {
  }

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

  // metody pobierające i ustawiające
...
}
  • wiersz 11: adnotacja @Named sprawia, że klasa staje się beanem zarządzanym. Należy zauważyć, że w odróżnieniu od poprzedniego projektu nie zastosowano adnotacji @ManagedBean. Wynika to z faktu, że odwołanie do tej klasy musi zostać wstrzyknięte do innej klasy za pomocą adnotacji @Inject, a ta wstrzykuje wyłącznie klasy opatrzone adnotacją @Named.
  • wiersz 12: adnotacja @ApplicationScoped sprawia, że klasa staje się obiektem o zasięgu aplikacji. Należy zauważyć, że klasą adnotacji jest [javax.enterprise.context.ApplicationScoped] (wiersz 6), a nie [javax.faces.bean.ApplicationScoped], jak w przypadku beanów z poprzedniego projektu.

Bean ApplicationData służy do dwóch rzeczy:

  • wiersz 16: utrzymywanie odwołania do warstwy [métier],
  • wiersze 18–19: zdefiniowanie modułu logującego, z którego będą mogły korzystać inne komponenty w celu rejestrowania logów na konsoli Glassfish.

12.3.2. Bean SessionData

Bean SessionData będzie miał zasięg sesji:


package web.beans.session;

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

@Named
@SessionScoped
public class SessionData implements Serializable {

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

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

  // zarządzanie menu
  public void setMenu(boolean menuFaireSimulationIsRendered, boolean menuEnregistrerSimulationIsRendered, boolean menuEffacerSimulationIsRendered, boolean menuVoirSimulationsIsRendered, boolean menuRetourSimulateurIsRendered, boolean menuTerminerSessionIsRendered) {
    this.setMenuFaireSimulationIsRendered(menuFaireSimulationIsRendered);
    this.setMenuEnregistrerSimulationIsRendered(menuEnregistrerSimulationIsRendered);
    this.setMenuVoirSimulationsIsRendered(menuVoirSimulationsIsRendered);
    this.setMenuEffacerSimulationIsRendered(menuEffacerSimulationIsRendered);
    this.setMenuRetourSimulateurIsRendered(menuRetourSimulateurIsRendered);
    this.setMenuTerminerSessionIsRendered(menuTerminerSessionIsRendered);
  }

  // metody pobierające i ustawiające
  ...
}
  • wiersz 13: klasa SessionData jest beanem zarządzanym (@Named), który może być wstrzykiwany do innych beanów zarządzanych,
  • wiersz 14: ma zasięg sesji (@SessionScoped),
  • wiersze 18–19: do niego wstrzykiwane jest odwołanie do fasoli ApplicationData (@Inject),
  • wiersze 21–32: dane aplikacji, które muszą być zachowane między sesjami.
  • wiersz 21: lista symulacji wykonanych przez użytkownika,
  • wiersz 22: numer ostatniej zapisanej symulacji,
  • wiersz 23: ostatnia przeprowadzona symulacja,
  • wiersze 25–30: opcje menu,
  • wiersz 32: ustawienia regionalne aplikacji.

Wiersze 39–44: metoda init jest wykonywana po utworzeniu instancji klasy (@PostConstruct). W tym przypadku służy ona wyłącznie do pozostawienia śladu jej wykonania. Należy sprawdzić, czy jest ona wykonywana tylko raz na użytkownika, ponieważ klasa ma zasięg sesji. Wiersz 42: metoda korzysta z logera zdefiniowanego w klasie ApplicationData. Z tego powodu konieczne było wstrzyknięcie odwołania do tego beana (wiersze 18–19).

12.3.3. Bean Form

Bean Form ma zakres requête:


package web.beans.request;

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

@Named
@RequestScoped
public class Form {

  public Form() {
  }
  // inne komponenty
  @Inject
  private ApplicationData applicationData;
  @Inject
  private SessionData sessionData;
  // model widoków
  private String comboEmployesValue = "";
  private String heuresTravaillées = "";
  private String joursTravaillés = "";
  private Integer numSimulationToDelete;
  private List<Erreur> erreurs = new ArrayList<Erreur>();
  private FeuilleSalaire feuilleSalaire;

  
  // lista pracowników
  public List<Employe> getEmployes(){
    return applicationData.getMetier().findAllEmployes();
  }
  
  // akcja menu
  public String faireSimulation() {
    ...
  }

  public String enregistrerSimulation() {
  ...
  }

  public String effacerSimulation() {
  ...
  }

  public String voirSimulations() {
  ...
  }

  public String retourSimulateur() {
   ...
  }

  public String terminerSession() {
  ...
  }

  public String retirerSimulation() {
 ...
  }

  private void razFormulaire() {
 ...
  }

// metody getter i setter
...
}
  • w wierszu 17 klasa jest beanem zarządzanym (@Named),
  • w wierszu 18 – o zasięgu żądania (@RequestScoped),
  • wiersze 24–25: wstrzyknięcie odwołania do beana o zasięgu aplikacji ApplicationData,
  • wiersze 26–27: wstrzyknięcie odwołania do bean o zasięgu sesji SessionData.

12.4. Strony aplikacji

  

12.4.1. [layout.xhtml]

Strona [layout.xhtml] odpowiada za układ wszystkich widoków:


<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:ui="http://java.sun.com/jsf/facelets">

  <f:view locale="#{sessionData.locale}">
    <h:head>
      <title><h:outputText value="#{msg['form.titre']}"/></title>
      <h:outputStylesheet library="css" name="styles.css"/>
    </h:head>
    <script type="text/javascript">
      function raz(){
        // zmiana wartości przesłanych
        document.forms['formulaire'].elements['formulaire:comboEmployes'].value="0";
        document.forms['formulaire'].elements['formulaire:heuresTravaillees'].value="0";
        document.forms['formulaire'].elements['formulaire:joursTravailles'].value="0";
      }
    </script>
    <h:body style="background-image: url('${request.contextPath}/resources/images/standard.jpg');">
      <h:form id="formulaire">
        <!-- nagłówek -->
        <ui:include src="entete.xhtml" />
        <!-- treść -->
        <ui:insert name="part1" >
          Gestion des assistantes maternelles
        </ui:insert>
        <ui:insert name="part2"/>
      </h:form>
    </h:body>
  </f:view>
</html>

Każdy widok składa się z następujących elementów:

  • nagłówek wyświetlany przez fragment [entete.xhtml] (wiersz 24),
  • treść złożona z dwóch fragmentów o nazwach part1 (wiersze 26–28) i part2 (wiersz 29),
  • wiersz 8: atrybut „locale” zapewnia internacjonalizację stron. W tym przypadku będzie tylko jeden: fr_FR,
  • wiersze 14–19: kod JavaScript.

Wygląd strony [layout.xhtml] jest następujący:

  • w [1] nagłówek [entete.xhtml],
  • w [2] – fragment „part1”.

12.4.2. Nagłówek [entete.xhtml]

Kod strony [entete.xhtml] jest następujący:


<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:ui="http://java.sun.com/jsf/facelets">

  <ui:composition>
    <!-- nagłówek -->
    <h:panelGrid columns="2">
      <h:panelGroup>
        <h2><h:outputText value="#{msg['form.titre']}"/></h2>
      </h:panelGroup>
      <h:panelGroup>
        <h:panelGrid columns="1">
          <h:commandLink id="cmdFaireSimulation" value="#{msg['form.menu.faireSimulation']}" action="#{form.faireSimulation}" rendered="#{sessionData.menuFaireSimulationIsRendered}"/>
          <h:commandLink id="cmdEffacerSimulation"  onclick="raz()" value="#{msg['form.menu.effacerSimulation']}" action="#{form.effacerSimulation}" rendered="#{sessionData.menuEffacerSimulationIsRendered}"/>
          <h:commandLink id="cmdEnregistrerSimulation" immediate="true" value="#{msg['form.menu.enregistrerSimulation']}" action="#{form.enregistrerSimulation}" rendered="#{sessionData.menuEnregistrerSimulationIsRendered}"/>
          <h:commandLink id="cmdVoirSimulations" immediate="true" value="#{msg['form.menu.voirSimulations']}" action="#{form.voirSimulations}" rendered="#{sessionData.menuVoirSimulationsIsRendered}"/>
          <h:commandLink id="cmdRetourSimulateur" immediate="true" value="#{msg['form.menu.retourSimulateur']}" action="#{form.retourSimulateur}" rendered="#{sessionData.menuRetourSimulateurIsRendered}"/>
          <h:commandLink id="cmdTerminerSession" immediate="true" value="#{msg['form.menu.terminerSession']}" action="#{form.terminerSession}" rendered="#{sessionData.menuTerminerSessionIsRendered}"/>
        </h:panelGrid>
      </h:panelGroup>
    </h:panelGrid>
    <hr/>
  </ui:composition>
</html>
  • wiersz 12: tytuł aplikacji,
  • wiersze 16–21: sześć linków odpowiadających sześciu czynnościom, które może wykonać użytkownik. Linki te są kontrolowane (atrybut rendered) przez wartości logiczne z bean’a SessionData,
  • wiersz 17: kliknięcie linku [Effacer la simulation] powoduje wykonanie funkcji JavaScript o nazwie „raz”. Została ona zdefiniowana w modelu [layout.xhtml],
  • wiersz 18: atrybut immediate=true powoduje, że poprawność danych nie jest sprawdzana przed wykonaniem metody [Form].enregistrerSimulation. Jest to zamierzone. Można chcieć zapisać ostatnią symulację przeprowadzoną minutę temu, nawet jeśli dane wprowadzone od tego czasu w formularzu (ale jeszcze nie zweryfikowane) są nieprawidłowe. To samo dotyczy akcji [Voir les simulations], [Effacer la simulation], [Retour au simulateur] i [Terminer la session].

12.5. Przykłady zastosowań aplikacji

12.5.1. Wyświetlanie strony głównej

Strona główna to następująca strona o numerze [saisie.xhtml]:


<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:ui="http://java.sun.com/jsf/facelets">

  <ui:composition template="layout.xhtml">
    <ui:define name="part1">
      <ui:include src="saisie2.xhtml"/>
    </ui:define>
  </ui:composition>
</html>
  • wiersz 8: wyświetla się wewnątrz strony [layout.xhtml],
  • wiersz 9: w miejscu fragmentu o nazwie part1. W tym fragmencie wyświetlana jest strona [saisie2.xhtml]:

<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:ui="http://java.sun.com/jsf/facelets">

  <h:panelGrid columns="3">
    <h:outputText value="#{msg['form.comboEmployes.libellé']}"/>
    <h:outputText value="#{msg['form.heuresTravaillées.libellé']}"/>
    <h:outputText value="#{msg['form.joursTravaillés.libellé']}"/>
    <h:selectOneMenu id="comboEmployes" value="#{form.comboEmployesValue}">
      <f:selectItems .../>
    </h:selectOneMenu>
    <h:inputText id="heuresTravaillees" value="#{form.heuresTravaillées}" required="true" requiredMessage="#{msg['form.heuresTravaillées.required']}" validatorMessage="#{msg['form.heuresTravaillées.validation']}">
      <f:validateDoubleRange minimum="0" maximum="300"/>
    </h:inputText>
    <h:inputText id="joursTravailles" value="#{form.joursTravaillés}" required="true" requiredMessage="#{msg['form.joursTravaillés.required']}" validatorMessage="#{msg['form.joursTravaillés.validation']}">
      <f:validateLongRange minimum="0" maximum="31"/>
    </h:inputText>
    <h:panelGroup></h:panelGroup>
    <h:message for="heuresTravaillees" styleClass="error"/>
    <h:message for="joursTravailles" styleClass="error"/>
  </h:panelGrid>
  <hr/>
</html>

Ten kod wyświetla następujący widok:

 

Zadanie: uzupełnij wiersz 13 kodu XHTML. Lista elementów listy rozwijanej pracowników jest dostarczana przez metodę fasoli [Form]. Napisz tę metodę. Elementy wyświetlane w liście rozwijanej będą miały właściwość itemValue równą numerowi SS pracownika, a właściwość itemLabel będzie ciągiem znaków składającym się z imienia i nazwiska tego pracownika.



Pytanie: w jaki sposób należy zainicjować bean [SessionData], aby po wysłaniu pierwszego żądania GET do formularza menu w nagłówku wyglądało tak, jak pokazano powyżej?


12.5.2. Akcja [faireSimulation]

Kod JSF akcji [faireSimulation] wygląda następująco:


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

Akcja [faireSimulation] oblicza listę płac:

Image

Na podstawie poprzedniej strony otrzymujemy następujący wynik:

Symulacja jest wyświetlana na następującej stronie [simulation.xhtml]:


<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:ui="http://java.sun.com/jsf/facelets">

  <ui:composition template="layout.xhtml">
    <ui:define name="part1">
      <ui:include src="saisie2.xhtml"/>
    </ui:define>
    <ui:define name="part2">
      <h:outputText value="#{msg['form.infos.employé']}" styleClass="titreInfos"/>
      <br/><br/>
      <h:panelGrid columns="3" rowClasses="libelle,info">
        <h:outputText value="#{msg['form.employe.nom']}"/>
        <h:outputText value="#{msg['form.employe.prénom']}"/>
        <h:outputText value="#{msg['form.employe.adresse']}"/>
        <h:outputText value="#{form.feuilleSalaire.employe.nom}"/>
        <h:outputText value="#{form.feuilleSalaire.employe.prenom}"/>
        <h:outputText value="#{form.feuilleSalaire.employe.adresse}"/>
      </h:panelGrid>
      <h:panelGrid columns="3" rowClasses="libelle,info">
        <h:outputText value="#{msg['form.employe.ville']}"/>
        <h:outputText value="#{msg['form.employe.codePostal']}"/>
        <h:outputText value="#{msg['form.employe.indice']}"/>
        <h:outputText value="#{form.feuilleSalaire.employe.ville}"/>
        <h:outputText value="#{form.feuilleSalaire.employe.codePostal}"/>
        <h:outputText value="#{form.feuilleSalaire.employe.indemnite.indice}"/>
      </h:panelGrid>
      <br/>
      <h:outputText value="#{msg['form.infos.cotisations']}" styleClass="titreInfos"/>
      <br/><br/>
      <h:panelGrid columns="4" rowClasses="libelle,info">
        <h:outputText value="#{msg['form.cotisations.csgrds']}"/>
        <h:outputText value="#{msg['form.cotisations.csgd']}"/>
        <h:outputText value="#{msg['form.cotisations.retraite']}"/>
        <h:outputText value="#{msg['form.cotisations.secu']}"/>
        <h:outputText value="#{form.feuilleSalaire.cotisation.csgrds} %"/>
        <h:outputText value="#{form.feuilleSalaire.cotisation.csgd} %"/>
        <h:outputText value="#{form.feuilleSalaire.cotisation.retraite} %"/>
        <h:outputText value="#{form.feuilleSalaire.cotisation.secu} %"/>
      </h:panelGrid>
      <br/>
      <h:outputText value="#{msg['form.infos.indemnites']}" styleClass="titreInfos"/>
      <br/><br/>
      <h:panelGrid columns="4" rowClasses="libelle,info">
        <h:outputText value="#{msg['form.indemnites.salaireHoraire']}"/>
        <h:outputText value="#{msg['form.indemnites.entretienJour']}"/>
        <h:outputText value="#{msg['form.indemnites.repasJour']}"/>
        <h:outputText value="#{msg['form.indemnites.congésPayés']}"/>
        <h:outputFormat value="{0,number,currency}">
          <f:param value="#{form.feuilleSalaire.employe.indemnite.baseHeure}"/>
        </h:outputFormat>
        <h:outputFormat value="{0,number,currency}">
          <f:param value="#{form.feuilleSalaire.employe.indemnite.entretienJour}"/>
        </h:outputFormat>
        <h:outputFormat value="{0,number,currency}">
          <f:param value="#{form.feuilleSalaire.employe.indemnite.repasJour}"/>
        </h:outputFormat>
        <h:outputText value="#{form.feuilleSalaire.employe.indemnite.indemnitesCP} %"/>
      </h:panelGrid>
      <br/>
      <h:outputText value="#{msg['form.infos.salaire']}" styleClass="titreInfos"/>
      <br/><br/>
      <h:panelGrid columns="4" rowClasses="libelle,info">
        <h:outputText value="#{msg['form.salaire.base']}"/>
        <h:outputText value="#{msg['form.salaire.cotisationsSociales']}"/>
        <h:outputText value="#{msg['form.salaire.entretien']}"/>
        <h:outputText value="#{msg['form.salaire.repas']}"/>
        <h:outputFormat value="{0,number,currency}">
          <f:param value="#{form.feuilleSalaire.elementsSalaire.salaireBase}"/>
        </h:outputFormat>
        <h:outputFormat value="{0,number,currency}">
          <f:param value="#{form.feuilleSalaire.elementsSalaire.cotisationsSociales}"/>
        </h:outputFormat>
        <h:outputFormat value="{0,number,currency}">
          <f:param value="#{form.feuilleSalaire.elementsSalaire.indemnitesEntretien}"/>
        </h:outputFormat>
        <h:outputFormat value="{0,number,currency}">
          <f:param value="#{form.feuilleSalaire.elementsSalaire.indemnitesRepas}"/>
        </h:outputFormat>
      </h:panelGrid>
      <br/>
      <h:panelGrid columns="3" columnClasses="libelle,col2,info">
        <h:outputText value="#{msg['form.salaire.net']}"/>
        <h:panelGroup></h:panelGroup>
        <h:outputFormat value="{0,number,currency}">
          <f:param value="#{form.feuilleSalaire.elementsSalaire.salaireNet}"/>
        </h:outputFormat>
      </h:panelGrid>
    </ui:define>
  </ui:composition>
</html>
  • w wierszu 8 strona [simulation.xhtml] jest wstawiana do strony [layout.xhtml],
  • wiersze 9–11: obszar wprowadzania danych jest wyświetlany we fragmencie part1 strony layout,
  • wiersze 12–91: symulacja jest wyświetlana w fragmencie part2 strony layout.

Zadanie: napisać metodę [faireSimulation] klasy [Form]. Symulacja zostanie zapisana w bean SessionData.


12.5.3. Obsługa błędów

Chcemy zapewnić prawidłowe obsługiwanie wyjątków, które mogą wystąpić podczas obliczania symulacji. W tym celu kod metody [faireSimulation] będzie wykorzystywał blok try / catch:


  // działanie z menu
  public String faireSimulation(){
    try{
      // obliczanie listy płac
      feuilleSalaire= ...
      // wyświetla się symulacja
      ...
      // aktualizacja menu
      ...
       // przechodzi do widoku symulacji
      return "simulation";
    }catch(Throwable th){
      // czyści się listę poprzednich błędów
      ...
      // tworzy się nową listę błędów
      ...
      // wyświetla się widok vueErreur
      ...
      // aktualizowanie menu
      ...
       // wyświetla się widok błędu
      return "erreurs";
    }
}

Lista błędów utworzona w wierszu 15 wygląda następująco:


  // szablon widoków
  ...
  private List<Erreur> erreurs=new ArrayList<Erreur>();
...

Klasa „Błąd” jest zdefiniowana w następujący sposób:


package web.entities;

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

  // konstruktor
  public Erreur(String classe, String message){
    this.setClasse(classe);
    this.message=message;
  }
  
  // metody pobierające i ustawiające
...  
}

Błędy będą stanowić wyjątki, których nazwa klasy jest zapisywana w polu „klasa”, a komunikat w polu „komunikat”.

Lista błędów utworzona w metodzie [faireSimulation] składa się z:

  • pierwotnego wyjątku th typu Throwable, który wystąpił,
  • jej przyczyny th.getCause(), o ile taka istnieje,
  • przyczyny tej przyczyny o typie h.getCause().getCause(), o ile istnieje.
  • ...

Oto przykładowa lista błędów:

Image

W powyższym przykładzie pracownik [Z Y] nie istnieje w słowniku pracowników używanym przez symulowaną warstwę [métier]. W takim przypadku symulowana warstwa [métier] zgłasza wyjątek (wiersz 6 poniżej):


  public FeuilleSalaire calculerFeuilleSalaire(String SS, double nbHeuresTravaillées, int nbJoursTravaillés) {
    // pobieramy pracownika
    Employe e=hashEmployes.get(SS);
    // zwracamy wyjątek, jeśli pracownik nie istnieje
    if(e==null){
      throw new PamException(String.format("L'employé de n° SS [%s] n'existe pas",SS),1);
    }
...
}

Otrzymano następujący wynik:

Image

Ten widok jest wyświetlany przez następującą stronę [erreurs.xhtml]:


<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:ui="http://java.sun.com/jsf/facelets">

  <ui:composition template="layout.xhtml">
    <ui:define name="part1">
      <h3><h:outputText value="#{msg['erreur.titre']}"/></h3>
      <h:dataTable value="#{form.erreurs}" var="erreur"
                   headerClass="erreursHeaders" columnClasses="erreurClasse,erreurMessage">
        <f:facet name="header">
          <h:outputText value="#{msg['erreur.exceptions']}"/>
        </f:facet>
        <h:column>
          <f:facet name="header">
            <h:outputText value="#{msg['exception.type']}"/>
          </f:facet>
          <h:outputText value="#{erreur.classe}"/>
        </h:column>
        <h:column>
          <f:facet name="header">
            <h:outputText value="#{msg['exception.message']}"/>
          </f:facet>
          <h:outputText value="#{erreur.message}"/>
        </h:column>
      </h:dataTable>
    </ui:define>
  </ui:composition>
</html>

Zadanie: uzupełnić metodę [faireSimulation] tak, aby w przypadku wystąpienia wyjątku wyświetlała widok [vueErreur].


12.5.4. Akcja [effacerSimulation]

Akcja [effacerSimulation] umożliwia użytkownikowi wyświetlenie pustego formularza:

Kod JSF linku [effacerSimulation] jest następujący:


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

Kliknięcie linku [EffacerSimulation] powoduje najpierw wywołanie funkcji JavaScript raz(). Metoda ta jest zdefiniowana na stronie [layout.xhtml]:


    <script type="text/javascript">
      function raz(){
        // zmiana przesłanych wartości
        document.forms['formulaire'].elements['formulaire:comboEmployes'].value="0";
        document.forms['formulaire'].elements['formulaire:heuresTravaillees'].value="0";
        document.forms['formulaire'].elements['formulaire:joursTravailles'].value="0";
      }
</script>

Wiersze 4–6 zmieniają wartości przesłane. Należy zauważyć, że

  • przesłane wartości są prawidłowe (c.a.d) i przejdą testy walidacji pól wprowadzania danych (heuresTravaillees oraz joursTravailles).
  • Funkcja raz nie wysyła formularza. Zostanie on bowiem wysłany za pomocą linku cmdEffacerSimulation. Operacja post zostanie wykonana po uruchomieniu funkcji JavaScript raz.

W trakcie działania funkcji post przesłane wartości przejdą standardową ścieżkę: walidację, a następnie przypisanie do pól modelu. W klasie [Form] są to następujące pola:


  // szablon widoków
  private String comboEmployesValue;
  private String heuresTravaillées;
  private String joursTravaillés;
  ...

W tych trzech polach zostaną zapisane trzy wartości {"0", „0”, „0”}. Po dokonaniu tego przypisania zostanie wykonana metoda effacerSimulation.


Pytanie: Należy napisać metodę [effacerSimulation] klasy [Form]. Należy zadbać o to, aby:

  • wyświetlany był tylko obszar wprowadzania danych,

  • lista rozwijana była ustawiona na pierwszym elemencie,

  • pola wprowadzania danych heuresTravaillees i joursTravailles wyświetlały puste ciągi znaków.


12.5.5. Akcja [enregistrerSimulation]

Kod JSF powiązany z linkiem [enregistrerSimulation] wygląda następująco:


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

Akcja [enregistrerSimulation] powiązana z tym linkiem pozwala zapisać bieżącą symulację na liście symulacji przechowywanej w klasie [SessionData]:


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

Klasa : Simulation” ma następujący wygląd:


package web.entities;

import metier.FeuilleSalaire;

public class Simulation {
  
  public Simulation() {
  }

  // pola symulacji
  private Integer num;
  private FeuilleSalaire feuilleSalaire;
  private String heuresTravaillées;
  private String joursTravaillés;
  
  // konstruktor
  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();
  }

  // metody pobierające i ustawiające
...
}

Klasa ta umożliwia zapisanie symulacji wykonanej przez użytkownika:

  • wiersz 11: numer symulacji,
  • wiersz 12: rozliczenie wynagrodzenia, które zostało obliczone,
  • wiersz 13: liczba przepracowanych godzin,
  • wiersz 14: liczba przepracowanych dni.

Oto przykładowy wpis:

Image

Na podstawie poprzedniej strony otrzymujemy następujący wynik:

Image

Numer symulacji to liczba zwiększana przy każdym nowym wpisie. Należy on do bean’a SessionData:


  // symulacje
  private List<Simulation> simulations = new ArrayList<Simulation>();
  private int numDerniereSimulation = 0;
private Simulation simulation;
  • wiersz 2: numer ostatniej przeprowadzonej symulacji.

Metoda [enregistrerSimulation] może działać w następujący sposób:

  • pobrać numer ostatniej symulacji z bean [SessionData] i zwiększyć go o 1,
  • dodać nową symulację do listy symulacji zarządzanej przez klasę [SessionData],
  • wyświetlić tabelę symulacji:

Tabela symulacji jest wyświetlana przez stronę [simulations.xhtml]:


<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:ui="http://java.sun.com/jsf/facelets">

  <ui:composition template="layout.xhtml">
    <ui:define name="part1">
      <!-- tabela symulacji -->
      <h:dataTable value="#{sessionData.simulations}" var="simulation"
                   headerClass="simulationsHeaders" columnClasses="simuNum,simuNom,simuPrenom,simuHT,simuJT,simuSalaireBase,simuIndemnites,simuCotisationsSociales,simuSalaireNet">
        <h:column>
          <f:facet name="header">
            <h:outputText value="#{msg['simulations.headers.numero']}"/>
          </f:facet>
          <h:outputText value="#{simulation.num}"/>
        </h:column>
        <h:column>
          <f:facet name="header">
            <h:outputText value="#{msg['simulations.headers.nom']}"/>
          </f:facet>
          <h:outputText value="#{simulation.feuilleSalaire.employe.nom}"/>
        </h:column>
        <h:column>
          <f:facet name="header">
            <h:outputText value="#{msg['simulations.headers.prenom']}"/>
          </f:facet>
          <h:outputText value="#{simulation.feuilleSalaire.employe.prenom}"/>
        </h:column>
        <h:column>
          <f:facet name="header">
            <h:outputText value="#{msg['simulations.headers.heuresTravaillees']}"/>
          </f:facet>
          <h:outputText value="#{simulation.heuresTravaillées}"/>
        </h:column>
        <h:column>
          <f:facet name="header">
            <h:outputText value="#{msg['simulations.headers.joursTravailles']}"/>
          </f:facet>
          <h:outputText value="#{simulation.joursTravaillés}"/>
        </h:column>
        <h:column>
          <f:facet name="header">
            <h:outputText value="#{msg['simulations.headers.salaireBase']}"/>
          </f:facet>
          <h:outputText value="#{simulation.feuilleSalaire.elementsSalaire.salaireBase}"/>
        </h:column>
        <h:column>
          <f:facet name="header">
            <h:outputText value="#{msg['simulations.headers.indemnites']}"/>
          </f:facet>
          <h:outputText value="#{simulation.indemnites}"/>
        </h:column>
        <h:column>
          <f:facet name="header">
            <h:outputText value="#{msg['simulations.headers.cotisationsSociales']}"/>
          </f:facet>
          <h:outputText value="#{simulation.feuilleSalaire.elementsSalaire.cotisationsSociales}"/>
        </h:column>
        <h:column>
          <f:facet name="header">
            <h:outputText value="#{msg['simulations.headers.salaireNet']}"/>
          </f:facet>
          <h:outputText value="#{simulation.feuilleSalaire.elementsSalaire.salaireNet}"/>
        </h:column>
        <h:column>
          <h:commandLink value="Retirer" action="#{form.retirerSimulation}">
            <f:setPropertyActionListener target="#{form.numSimulationToDelete}" value="#{simulation.num}"/>
          </h:commandLink>
        </h:column>
      </h:dataTable>
    </ui:define>
  </ui:composition>
</html>
  • w wierszu 8 strona [simulations.xhtml] zostaje wstawiona do strony [layout.xhtml],
  • w wierszu 9, w miejsce fragmentu o nazwie part1,
  • w wierszu 11 tag <h:dataTable> wykorzystuje pole #{sessionData.simulations} jako źródło danych, c.a.d. następujące pole:

  // symulacje
private List<Simulation> simulations;
  • atrybut var="simulation" określa nazwę zmiennej reprezentującej bieżącą symulację wewnątrz tagu <h:datatable>

  • atrybut headerClass="simulationsHeaders" określa styl nagłówków kolumn tabeli.

  • atrybut columnClasses="...." określa styl każdej z kolumn tabeli

Przyjrzyjmy się jednej z kolumn tabeli i zobaczmy, jak jest zbudowana:

Kod JSF kolumny Nom wygląda następująco:


            <h:column>
              <f:facet name="header">
                <h:outputText value="#{msg['simulations.headers.nom']}"/>
              </f:facet>
              <h:outputText value="#{simulation.feuilleSalaire.employe.nom}"/>
</h:column>
  • wiersze 2–4: tag <f:facet name="header"> określa tytuł kolumny
  • wiersz 5: wpisano nazwisko pracownika:
    • symulacja odnosi się do atrybutu var tagu <h:dataTable ...>:

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

„symulacja” oznacza bieżącą symulację z listy symulacji: najpierw pierwszą, potem drugą, …

  • (ciąg dalszy)
    • simulation.feuilleSalaire odnosi się do pola feuilleSalaire bieżącej symulacji
    • simulation.feuilleSalaire.employe odnosi się do pola „pracownik” w polu feuilleSalaire
    • simulation.feuilleSalaire.employe.nom odnosi się do pola „nom” w polu „employe”

Ta sama procedura jest powtarzana dla wszystkich kolumn tabeli. Problem pojawia się w przypadku kolumny „Indemnités”, która jest generowana za pomocą następującego kodu:


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

W wierszu 5 wyświetlana jest wartość simulation.indemnites. Jednak klasa Simulation nie posiada pola „indemnites”. Należy pamiętać, że pole „indemnites” nie jest wykorzystywane bezpośrednio, lecz za pośrednictwem metody simulation.getIndemnites(). Wystarczy zatem, że ta metoda istnieje. Pole indemnites może natomiast nie istnieć. Metoda getIndemnites musi zwracać sumę dodatków pracownika. Wymaga to obliczeń pośrednich, ponieważ suma ta nie jest bezpośrednio dostępna w liście płac. Metoda getIndemnites została podana w paragrafie 12.5.5.


Zadanie: napisać metodę [enregistrerSimulation] klasy [Form].


12.5.6. Akcja [retourSimulateur]

Kod JSF powiązania [retourSimulateur] ma następującą postać:


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

Akcja [retourSimulateur] powiązana z linkiem pozwala użytkownikowi powrócić z widoku [vueSimulations] do widoku [vueSaisies]:

Image

Uzyskany wynik:

Image


Zadanie: należy napisać metodę [retourSimulateur] klasy [Form]. Przedstawiony formularz wprowadzania danych musi być pusty, tak jak powyżej.


12.5.7. Akcja [voirSimulations]

Kod JSF dla linku [voirSimulations] wygląda następująco:


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

Akcja [voirSimulations] powiązana z linkiem umożliwia użytkownikowi wyświetlenie tabeli symulacji, niezależnie od stanu wprowadzonych danych:

Image

Uzyskany wynik:

Image


Zadanie: napisać metodę [voirSimulations] klasy [Form].


Należy zadbać o to, aby w przypadku, gdy lista symulacji jest pusta, wyświetlany widok to [vueSimulationsVides]:

Image

Aby wyświetlić powyższy widok, należy użyć następującej strony [simulationsVides.xhtml]:


<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:ui="http://java.sun.com/jsf/facelets">

  <ui:composition template="layout.xhtml">
    <ui:define name="part1">
      <h2>Votre liste de simulations est vide.</h2>
    </ui:define>
  </ui:composition>
</html>

12.5.8. Akcja [retirerSimulation]

Użytkownik może usunąć symulacje ze swojej listy:

Image

Otrzymany wynik jest następujący:

Image

Jeśli z powyższej listy usuniemy ostatnią symulację, otrzymamy następujący wynik:

Image

Kod JSF z kolumny [Retirer] w tabeli symulacji wygląda następująco:


          <h:dataTable value="#{form.simulations}" var="simulation"
                       headerClass="simulationsHeaders" columnClasses="simuNum,simuNom,simuPrenom,simuHT,simuJT,simuSalaireBase,simuIndemnites,simuCotisationsSociales,simuSalaireNet">
...
            <h:column>
              <h:commandLink value="Retirer" action="#{form.retirerSimulation}">
                <f:setPropertyActionListener target="#{form.numSimulationToDelete}" value="#{simulation.num}"/>
              </h:commandLink>
            </h:column>
          </h:dataTable>
  • wiersz 5: link [Retirer] jest powiązany z metodą [retirerSimulation] klasy [Form]. Metoda ta wymaga podania numeru symulacji, którą należy usunąć. Numer ten jest przekazywany przez tag <f:setPropertyActionListener> w wierszu 8. Tag ten posiada dwa atrybuty: target i value; atrybut target wskazuje pole w modelu, do którego zostanie przypisana wartość atrybutu value. W tym przypadku numer symulacji, którą należy usunąć, #{simulation.num}, zostanie przypisany do pola numSimulationToDelete klasy [Form]:

  // model widoków
  ...
  private Integer numSimulationToDelete;

Gdy zostanie wykonana metoda [retirerSimulation] klasy [Form], będzie mogła wykorzystać wartość, która została wcześniej zapisana w polu numSimulationToDelete.


Zadanie: napisać metodę [retirerSimulation] klasy [Form].


12.5.9. Akcja [terminerSession]

Kod JSF powiązania [Terminer la session] wygląda następująco:


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

Akcja [terminerSession] powiązana z linkiem pozwala użytkownikowi zakończyć sesję i powrócić do pustego formularza:

Image

Image

Jeśli użytkownik posiadał listę symulacji, zostaje ona wyczyszczona. Ponadto numeracja symulacji zaczyna się od nowa od 1.


Zadanie: napisać metodę [terminerSession] klasy [Form].


12.6. Włączenie warstwy internetowej do architektury trójwarstwowej JSF / EJB

Poprzednia architektura aplikacji internetowej wyglądała następująco:

Zastępujemy symulowaną warstwę [métier] warstwami [métier, DAO, jpa] zaimplementowanymi przez EJB w punkcie 7.1:


Zadanie praktyczne: przeprowadzić integrację warstw JSF i EJB zgodnie z metodologią opisaną w punkcie 11.