Skip to content

12. 第 7 版 - PAM 多视图/多页面 Web 应用程序

此处我们回归到最初的架构,即模拟 [métier] 层的情况。现在我们知道,该层可以轻松地被真实的 [métier] 层所取代。模拟的 [métier] 层有助于测试。

JSF 应用程序属于 MVC 类型(模型-视图-控制器):

  • Servlet [Faces Servlet] 是 JSF 提供的通用控制器。该控制器由应用程序特有的事件处理程序进行扩展。 此前遇到的事件处理程序是作为 JSF 页面模型的类中的方法
  • JSF 页面将响应发送给客户端浏览器。这些是应用程序的视图。
  • JSF 页面包含动态元素,这些元素被称为页面的模型。 需要指出的是,对于某些作者而言,模型涵盖了应用程序所操作的实体,例如类 FeuilleSalaire Employe。为了区分这两种模型,我们可以分别称之为应用程序模型和页面模型 JSF。

在 JSF 架构中,从页面 JSFi 跳转至页面 JSFj 可能会遇到问题。

  • 页面 JSFi 已显示。从该页面出发,用户通过某个事件 [1] 触发了 POST
  • 在 JSF 中,该 POST 通常由页面 JSFi 的 Mi 模型中的 C 方法处理。可以说,该 C 方法是一个次级控制器。
  • 如果该方法执行完毕后需要显示页面 JSFj,控制器 C 必须:
  1. 更新页面 JSFj 的 Mj 模型 [2c]
  2. 将导航键 [2a] 返回给主控制器,该键将用于显示页面 JSFj

步骤 1 要求页面 JSFi 的 Mi 模板引用页面 JSFj 的 Mj 模板。这使得情况稍显复杂,因为 Mi 模板之间会相互依赖。实际上,负责更新 Mj 模板的 Mi 模板的 C 管理器必须了解 Mj 模板。 如果需要修改Mj模板,就必须相应地修改Mi模板的C管理器。

有一种情况可以避免模型之间的相互依赖:即存在一个唯一的M模型,用于所有JSF页面。这种架构仅适用于只有少数视图的应用程序,但使用起来非常简单。这就是我们目前采用的架构。

在此背景下,应用程序的架构如下:

12.1. 应用程序的视图

向用户展示的视图包括:

  • 视图 [VueSaisies],用于展示模拟表单

  • 视图 [VueSimulation],用于显示模拟的详细结果:

Image

Image

  • 视图 [VueSimulations],显示客户进行的模拟列表

Image

  • 视图 [VueSimulationsVides],用于显示客户没有或已无模拟记录:

Image

  • 视图 [VueErreur] 显示存在一个或多个错误:

Image

12.2. [web] 层的 NetBeans 项目

该版本的 NetBeans 项目将对应以下 Maven 项目:

  • 在 [1] 中,配置文件
  • 在 [2] 中,页面文件;在 JSF 中,
  • 在 [3] 中,视图的样式表和背景图像,
  • 在 [4] 中,[web] 层的类,
  • 在 [5] 中,应用程序的底层,
  • 在 [6] 中,应用程序国际化的消息文件,
  • 在 [7] 中,是项目的依赖项。

下面我们将逐一介绍其中的一些内容。

12.2.1. 配置文件

文件 [web.xml] 和 [faces-config.xml] 与上一个项目中的完全相同,但 [web.xml] 中有一个细节不同:


<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
  ...
  <welcome-file-list>
    <welcome-file>faces/saisie.xhtml</welcome-file>
  </welcome-file-list>
  ...
</web-app>
  • 第4-6行:首页是页面[saisie.xhtml]

12.2.2. 样式表

文件 [styles.css] 内容如下:


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

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

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

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

}

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

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

以下是使用这些样式的 JSF 代码示例:

模拟视图


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

生成的结果如下:

Image

错误视图


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

Image

12.2.3. 消息文件

消息文件 [messages_fr.properties] 内容如下:


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

12.2.4. [métier] 层

模拟的图层 [métier] 已按以下方式修改:


package metier;

...
public class Metier implements ImetierLocal, Serializable {
  
  // 员工列表
  private Map<String,Employe> hashEmployes=new HashMap<String,Employe>();
  private List<Employe> listEmployes;
  
  // 获取工资单
  public FeuilleSalaire calculerFeuilleSalaire(String SS,
    double nbHeuresTravaillées, int nbJoursTravaillés) {
    // 检索员工
    Employe e=hashEmployes.get(SS);
    // 若员工不存在则抛出异常
    if(e==null){
      throw new PamException(String.format("L'employé de n° SS [%s] n'existe pas",SS),1);
    }
    // 生成虚拟工资单
    return new FeuilleSalaire(e,new Cotisation(3.49,6.15,9.39,7.88),e.getIndemnite(),new ElementsSalaire(100,100,100,100,100));
  }
  
  // 员工列表
  public List<Employe> findAllEmployes() {
    if(listEmployes==null){
      // 创建包含三名员工的列表
      listEmployes=new ArrayList<Employe>();
      listEmployes.add(new Employe("254104940426058","Jouveinal","Marie","5 rue des oiseaux","St Corentin","49203",new Indemnite(2,2.1,2.1,3.1,15)));
      listEmployes.add(new Employe("260124402111742","Laverti","Justine","La brûlerie","St Marcel","49014",new Indemnite(1,1.93,2,3,12)));
      // 员工字典
      for(Employe e:listEmployes){
        hashEmployes.put(e.getSS(),e);
      }
      // 向员工名录中添加一名不存在的员工
      listEmployes.add(new Employe("X","Y","Z","La brûlerie","St Marcel","49014",new Indemnite(1,1.93,2,3,12)));
    }
    // 返回员工列表
    return listEmployes;
  }
}
  • 第 8 行:员工列表
  • 第 7 行:以员工编号为索引的字典形式的同一列表 SS
  • 第24-39行:返回员工列表的方法findAllEmployes。该方法创建一个硬编码列表,并通过第8行的字段employés进行引用。
  • 第27-33行:创建包含两名员工的列表和字典
  • 第35行:将一名员工添加到员工列表(第8行)中,但未添加到字典hashEmployes(第7行)中。 此操作旨在使其出现在员工下拉列表中,但随后不被方法 calculerFeuilleSalaire(第 14 行)识别,从而触发该方法抛出异常(第 17 行)。
  • 第 11-21 行:方法 calculerFeuilleSalaire
  • 第14行:通过员工编号SS在字典hashEmployes中搜索该员工。若未找到,则抛出异常(第16-18行)。 因此,对于编号为 SS X 的员工,系统会在第 35 行将其添加到员工列表中,但未添加到字典 hashEmployes 中,从而引发异常。
  • 第20行,创建并返回一份虚拟的工资单。

12.3. 应用程序的Bean

将包含三个作用域不同的 Bean:

  

12.3.1. Bean ApplicationData

Bean ApplicationData 的作用域为 application


package web.beans.application;

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

@Named
@ApplicationScoped
public class ApplicationData implements Serializable {

  // 业务层
  private IMetierLocal metier = new Metier();
  // 日志记录器
  private boolean logsEnabled = true;
  private final Logger logger = Logger.getLogger("pam");

  public ApplicationData() {
  }

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

  // 获取器和设置器
...
}
  • 第 11 行:@Named 注解将该类设为受管 Bean。 需要注意的是,与前一个项目不同,这里未使用 @ManagedBean 注解。原因是该类的引用必须通过 @Inject 注解注入到另一个类中,而 @Inject 注解仅能注入带有 @Named 注解的类。
  • 第 12 行:@ApplicationScoped 注解将该类设为应用程序作用域对象。需注意,该注解对应的类是 [javax.enterprise.context.ApplicationScoped](第 6 行),而非前一个项目中 Bean 所用的 [javax.faces.bean.ApplicationScoped]。

Bean ApplicationData 具有两个作用:

  • 第 16 行:维护对 [métier] 层的引用,
  • 第 18-19 行:定义一个日志器,其他 Bean 可通过它向 Glassfish 控制台写入日志。

12.3.2. Bean SessionData

SessionData Bean 的作用域为会话:


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 {

  // 应用程序
  @Inject
  private ApplicationData applicationData;
  // 模拟
  private List<Simulation> simulations = new ArrayList<Simulation>();
  private int numDerniereSimulation = 0;
  private Simulation simulation;
  // 菜单
  private boolean menuFaireSimulationIsRendered = true;
  private boolean menuEffacerSimulationIsRendered = true;
  private boolean menuEnregistrerSimulationIsRendered;
  private boolean menuVoirSimulationsIsRendered;
  private boolean menuRetourSimulateurIsRendered;
  private boolean menuTerminerSessionIsRendered = true;
  // 区域设置
  private String locale="fr_FR";
  
  // 构造函数
  public SessionData() {
  }

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

  // 菜单管理
  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);
  }

  // 获取器和设置器
  ...
}
  • 第 13 行:类 SessionData 是一个受管 Bean(@Named),可注入到其他受管 Bean 中,
  • 第 14 行:其作用域为会话 (@SessionScoped),
  • 第 18-19 行:向其注入了对 Bean ApplicationData 的引用 (@Inject),
  • 第 21-32 行:应用程序中需要在不同会话间保持的数据。
  • 第 21 行:用户执行的模拟列表,
  • 第22行:最近一次保存的模拟编号,
  • 第23行:最近一次执行的模拟,
  • 第25-30行:菜单选项,
  • 第32行:应用程序的区域设置。

第39-44行,在类(@PostConstruct)实例化后执行init方法。此处仅将其用于记录执行痕迹。 由于该类的作用域为会话,因此应能验证该方法对每个用户仅执行一次。第42行,该方法使用了在类ApplicationData中定义的日志器。正因如此,我们需要注入对该Bean的引用(第18-19行)。

12.3.3. Form Bean

Form Bean 的作用域为 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() {
  }
  // 其他 Bean
  @Inject
  private ApplicationData applicationData;
  @Inject
  private SessionData sessionData;
  // 视图模型
  private String comboEmployesValue = "";
  private String heuresTravaillées = "";
  private String joursTravaillés = "";
  private Integer numSimulationToDelete;
  private List<Erreur> erreurs = new ArrayList<Erreur>();
  private FeuilleSalaire feuilleSalaire;

  
  // 员工列表
  public List<Employe> getEmployes(){
    return applicationData.getMetier().findAllEmployes();
  }
  
  // 菜单操作
  public String faireSimulation() {
    ...
  }

  public String enregistrerSimulation() {
  ...
  }

  public String effacerSimulation() {
  ...
  }

  public String voirSimulations() {
  ...
  }

  public String retourSimulateur() {
   ...
  }

  public String terminerSession() {
  ...
  }

  public String retirerSimulation() {
 ...
  }

  private void razFormulaire() {
 ...
  }

// getter 和 setter
...
}
  • 第 17 行,该类是一个受管 Bean (@Named),
  • 第 18 行,作用域为请求 (@RequestScoped),
  • 第 24-25 行:注入对应用程序作用域 Bean ApplicationData 的引用,
  • 第26-27行:注入会话作用域Bean SessionData的引用

12.4. 应用程序的页面

  

12.4.1. [layout.xhtml]

页面 [layout.xhtml] 负责所有视图的版面设置:


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

  <f:view locale="#{sessionData.locale}">
    <h:head>
      <title><h:outputText value="#{msg['form.titre']}"/></title>
      <h:outputStylesheet library="css" name="styles.css"/>
    </h:head>
    <script type="text/javascript">
      function raz(){
        // 修改提交的值
        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">
        <!-- 页眉 -->
        <ui:include src="entete.xhtml" />
        <!-- 内容 -->
        <ui:insert name="part1" >
          Gestion des assistantes maternelles
        </ui:insert>
        <ui:insert name="part2"/>
      </h:form>
    </h:body>
  </f:view>
</html>

每个视图由以下元素组成:

  • 由片段 [entete.xhtml] 显示的页眉(第 24 行),
  • 一个由名为 part1(第 26-28 行)和 part2(第 29 行)的两个片段组成的主体,
  • 第 8 行:locale 属性负责页面的国际化。此处仅有一个:fr_FR,
  • 第14-19行:一段JavaScript代码。

页面 [layout.xhtml] 的显示效果如下:

  • 在 [1] 中,标题为 [entete.xhtml],
  • 在 [2] 中,片段 part1

12.4.2. 标题 [entete.xhtml]

页面 [entete.xhtml] 的代码如下:


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

  <ui:composition>
    <!-- 页眉 -->
    <h:panelGrid columns="2">
      <h:panelGroup>
        <h2><h:outputText value="#{msg['form.titre']}"/></h2>
      </h:panelGroup>
      <h:panelGroup>
        <h:panelGrid columns="1">
          <h:commandLink id="cmdFaireSimulation" value="#{msg['form.menu.faireSimulation']}" action="#{form.faireSimulation}" rendered="#{sessionData.menuFaireSimulationIsRendered}"/>
          <h:commandLink id="cmdEffacerSimulation"  onclick="raz()" value="#{msg['form.menu.effacerSimulation']}" action="#{form.effacerSimulation}" rendered="#{sessionData.menuEffacerSimulationIsRendered}"/>
          <h:commandLink id="cmdEnregistrerSimulation" immediate="true" value="#{msg['form.menu.enregistrerSimulation']}" action="#{form.enregistrerSimulation}" rendered="#{sessionData.menuEnregistrerSimulationIsRendered}"/>
          <h:commandLink id="cmdVoirSimulations" immediate="true" value="#{msg['form.menu.voirSimulations']}" action="#{form.voirSimulations}" rendered="#{sessionData.menuVoirSimulationsIsRendered}"/>
          <h:commandLink id="cmdRetourSimulateur" immediate="true" value="#{msg['form.menu.retourSimulateur']}" action="#{form.retourSimulateur}" rendered="#{sessionData.menuRetourSimulateurIsRendered}"/>
          <h:commandLink id="cmdTerminerSession" immediate="true" value="#{msg['form.menu.terminerSession']}" action="#{form.terminerSession}" rendered="#{sessionData.menuTerminerSessionIsRendered}"/>
        </h:panelGrid>
      </h:panelGroup>
    </h:panelGrid>
    <hr/>
  </ui:composition>
</html>
  • 第 12 行:应用程序标题,
  • 第 16-21 行:六个链接,对应用户可执行的六项操作。这些链接由 SessionData Bean 中的布尔值控制(rendered 属性),
  • 第 17 行:点击 [Effacer la simulation] 链接将触发 Javascript 函数 raz 的执行。该函数在模型 [layout.xhtml] 中已定义,
  • 第18行:immediate=true属性导致在执行方法[Form].enregistrerSimulation之前,不会验证数据的有效性。这是预期的行为。 用户可能希望保存一分钟前进行的最后一次模拟,即使此后在表单中输入(但尚未验证)的数据无效。 操作 [Voir les simulations]、[Effacer la simulation]、[Retour au simulateur] 和 [Terminer la session] 亦遵循此规则。

12.5. 应用程序的使用场景

12.5.1. 显示首页

主页是以下页面 [saisie.xhtml]:


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

  <ui:composition template="layout.xhtml">
    <ui:define name="part1">
      <ui:include src="saisie2.xhtml"/>
    </ui:define>
  </ui:composition>
</html>
  • 第 8 行:显示在页面 [layout.xhtml] 内,
  • 第 9 行:位于名为 part1 的片段位置。在此片段中,显示页面 [saisie2.xhtml]:

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

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

该代码显示以下视图:

 

问题:补全代码 XHTML 的第 13 行。员工下拉列表中的元素列表由 Bean [Form] 中的一个方法提供。编写该方法。 下拉列表中显示的条目,其 itemValue 属性将等于某位员工的编号 SS,而 itemLabel 属性将是一个由该员工名字和姓氏组成的字符串。



问题:应如何初始化 Bean [SessionData],以便在向表单发出初始请求 GET 时,页眉菜单显示为上图所示的内容?


12.5.2. 操作 [faireSimulation]

操作 [faireSimulation] 的代码 JSF 如下:


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

操作 [faireSimulation] 用于计算工资单:

Image

根据上一页的内容,得到以下结果:

模拟结果显示在以下页面 [simulation.xhtml] 中:


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

  <ui:composition template="layout.xhtml">
    <ui:define name="part1">
      <ui:include src="saisie2.xhtml"/>
    </ui:define>
    <ui:define name="part2">
      <h:outputText value="#{msg['form.infos.employé']}" styleClass="titreInfos"/>
      <br/><br/>
      <h:panelGrid columns="3" rowClasses="libelle,info">
        <h:outputText value="#{msg['form.employe.nom']}"/>
        <h:outputText value="#{msg['form.employe.prénom']}"/>
        <h:outputText value="#{msg['form.employe.adresse']}"/>
        <h:outputText value="#{form.feuilleSalaire.employe.nom}"/>
        <h:outputText value="#{form.feuilleSalaire.employe.prenom}"/>
        <h:outputText value="#{form.feuilleSalaire.employe.adresse}"/>
      </h:panelGrid>
      <h:panelGrid columns="3" rowClasses="libelle,info">
        <h:outputText value="#{msg['form.employe.ville']}"/>
        <h:outputText value="#{msg['form.employe.codePostal']}"/>
        <h:outputText value="#{msg['form.employe.indice']}"/>
        <h:outputText value="#{form.feuilleSalaire.employe.ville}"/>
        <h:outputText value="#{form.feuilleSalaire.employe.codePostal}"/>
        <h:outputText value="#{form.feuilleSalaire.employe.indemnite.indice}"/>
      </h:panelGrid>
      <br/>
      <h:outputText value="#{msg['form.infos.cotisations']}" styleClass="titreInfos"/>
      <br/><br/>
      <h:panelGrid columns="4" rowClasses="libelle,info">
        <h:outputText value="#{msg['form.cotisations.csgrds']}"/>
        <h:outputText value="#{msg['form.cotisations.csgd']}"/>
        <h:outputText value="#{msg['form.cotisations.retraite']}"/>
        <h:outputText value="#{msg['form.cotisations.secu']}"/>
        <h:outputText value="#{form.feuilleSalaire.cotisation.csgrds} %"/>
        <h:outputText value="#{form.feuilleSalaire.cotisation.csgd} %"/>
        <h:outputText value="#{form.feuilleSalaire.cotisation.retraite} %"/>
        <h:outputText value="#{form.feuilleSalaire.cotisation.secu} %"/>
      </h:panelGrid>
      <br/>
      <h:outputText value="#{msg['form.infos.indemnites']}" styleClass="titreInfos"/>
      <br/><br/>
      <h:panelGrid columns="4" rowClasses="libelle,info">
        <h:outputText value="#{msg['form.indemnites.salaireHoraire']}"/>
        <h:outputText value="#{msg['form.indemnites.entretienJour']}"/>
        <h:outputText value="#{msg['form.indemnites.repasJour']}"/>
        <h:outputText value="#{msg['form.indemnites.congésPayés']}"/>
        <h:outputFormat value="{0,number,currency}">
          <f:param value="#{form.feuilleSalaire.employe.indemnite.baseHeure}"/>
        </h:outputFormat>
        <h:outputFormat value="{0,number,currency}">
          <f:param value="#{form.feuilleSalaire.employe.indemnite.entretienJour}"/>
        </h:outputFormat>
        <h:outputFormat value="{0,number,currency}">
          <f:param value="#{form.feuilleSalaire.employe.indemnite.repasJour}"/>
        </h:outputFormat>
        <h:outputText value="#{form.feuilleSalaire.employe.indemnite.indemnitesCP} %"/>
      </h:panelGrid>
      <br/>
      <h:outputText value="#{msg['form.infos.salaire']}" styleClass="titreInfos"/>
      <br/><br/>
      <h:panelGrid columns="4" rowClasses="libelle,info">
        <h:outputText value="#{msg['form.salaire.base']}"/>
        <h:outputText value="#{msg['form.salaire.cotisationsSociales']}"/>
        <h:outputText value="#{msg['form.salaire.entretien']}"/>
        <h:outputText value="#{msg['form.salaire.repas']}"/>
        <h:outputFormat value="{0,number,currency}">
          <f:param value="#{form.feuilleSalaire.elementsSalaire.salaireBase}"/>
        </h:outputFormat>
        <h:outputFormat value="{0,number,currency}">
          <f:param value="#{form.feuilleSalaire.elementsSalaire.cotisationsSociales}"/>
        </h:outputFormat>
        <h:outputFormat value="{0,number,currency}">
          <f:param value="#{form.feuilleSalaire.elementsSalaire.indemnitesEntretien}"/>
        </h:outputFormat>
        <h:outputFormat value="{0,number,currency}">
          <f:param value="#{form.feuilleSalaire.elementsSalaire.indemnitesRepas}"/>
        </h:outputFormat>
      </h:panelGrid>
      <br/>
      <h:panelGrid columns="3" columnClasses="libelle,col2,info">
        <h:outputText value="#{msg['form.salaire.net']}"/>
        <h:panelGroup></h:panelGroup>
        <h:outputFormat value="{0,number,currency}">
          <f:param value="#{form.feuilleSalaire.elementsSalaire.salaireNet}"/>
        </h:outputFormat>
      </h:panelGrid>
    </ui:define>
  </ui:composition>
</html>
  • 第 8 行,页面 [simulation.xhtml] 插入到页面 [layout.xhtml] 中,
  • 第9-11行:输入区域显示在布局页面的part1片段中,
  • 第12-91行:在页面布局part2片段中显示模拟结果。

问题:编写类 [Form] 中的方法 [faireSimulation]。模拟结果将保存在 Bean SessionData 中。


12.5.3. 错误处理

我们需要妥善处理在计算模拟过程中可能出现的异常。为此,方法 [faireSimulation] 的代码将使用 try / catch 语句:


  // 菜单操作
  public String faireSimulation(){
    try{
      // 计算工资单
      feuilleSalaire= ...
      // 显示模拟结果
      ...
      // 更新菜单
      ...
       // 返回模拟视图
      return "simulation";
    }catch(Throwable th){
      // 清空之前的错误列表
      ...
      // 创建新的错误列表
      ...
      // 显示视图vueErreur
      ...
      // 更新菜单
      ...
       // 显示错误视图
      return "erreurs";
    }
}

第 15 行创建的错误列表如下:


  // 视图模板
  ...
  private List<Erreur> erreurs=new ArrayList<Erreur>();
...

Error 类定义如下:


package web.entities;

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

  // 构造函数
  public Erreur(String classe, String message){
    this.setClasse(classe);
    this.message=message;
  }
  
  // 获取器和设置器
...  
}

错误将作为异常处理,其类名存储在“类”字段中,消息存储在“消息”字段中。

在方法 [faireSimulation] 中构建的错误列表包含:

  • 已发生的初始异常 th(类型为 Throwable),
  • 其原因(如有)th.getCause()
  • 以及该原因的根源 h.getCause().getCause()(如果存在)。
  • ...

以下是一个错误列表的示例:

Image

在上例中,员工 [Z Y] 在模拟层 [métier] 所使用的员工字典中不存在。在此情况下,模拟层 [métier] 会抛出异常(见下文第 6 行):


  public FeuilleSalaire calculerFeuilleSalaire(String SS, double nbHeuresTravaillées, int nbJoursTravaillés) {
    // 获取员工
    Employe e=hashEmployes.get(SS);
    // 如果员工不存在则抛出异常
    if(e==null){
      throw new PamException(String.format("L'employé de n° SS [%s] n'existe pas",SS),1);
    }
...
}

所得结果如下:

Image

该视图由以下页面 [erreurs.xhtml] 显示:


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

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

问题:完善方法 [faireSimulation],使其在发生异常时显示视图 [vueErreur]。


12.5.4. 操作 [effacerSimulation]

操作 [effacerSimulation] 允许用户获取一个空表单:

链接 [effacerSimulation] 的代码 JSF 如下:


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

点击链接 [EffacerSimulation] 会首先调用 JavaScript 函数 raz()。该方法在页面 [layout.xhtml] 中定义:


    <script type="text/javascript">
      function raz(){
        // 修改提交的值
        document.forms['formulaire'].elements['formulaire:comboEmployes'].value="0";
        document.forms['formulaire'].elements['formulaire:heuresTravaillees'].value="0";
        document.forms['formulaire'].elements['formulaire:joursTravailles'].value="0";
      }
</script>

第4-6行修改了提交的值。需要注意的是

  • 提交的值是有效的,c.a.d。它们将通过输入字段的验证测试 heuresTravaillees joursTravailles
  • 函数 raz 不会提交表单。实际上,该表单将通过链接 cmdEffacerSimulation 进行提交。此 post 将在执行 JavaScript 函数 raz 之后执行。

post 过程中,提交的值将遵循常规流程:先进行验证,然后分配给模型字段。在类 [Form] 中,这些字段如下:


  // 视图模型
  private String comboEmployesValue;
  private String heuresTravaillées;
  private String joursTravaillés;
  ...

这三个字段将接收三个已提交的值 {"0","0","0"}。完成此赋值操作后,将执行方法 effacerSimulation


问题:编写类 [Form] 中的方法 [effacerSimulation]。需确保:

  • 仅显示输入区域,

  • 下拉列表框定位在其第一个选项上,

  • 输入字段 heuresTravailleesjoursTravailles 显示空字符串。


12.5.5. 操作 [enregistrerSimulation]

链接 [enregistrerSimulation] 的代码 JSF 如下:


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

与该链接关联的操作 [enregistrerSimulation] 可将当前模拟保存到由类 [SessionData] 维护的模拟列表中:


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

模拟类如下:


package web.entities;

import metier.FeuilleSalaire;

public class Simulation {
  
  public Simulation() {
  }

  // 模拟中的字段
  private Integer num;
  private FeuilleSalaire feuilleSalaire;
  private String heuresTravaillées;
  private String joursTravaillés;
  
  // 构造函数
  public Simulation(Integer num,String heuresTravaillées, String joursTravaillés, FeuilleSalaire feuilleSalaire){
    this.setNum(num);
    this.setFeuilleSalaire(feuilleSalaire);
    this.setHeuresTravaillées(heuresTravaillées);
    this.setJoursTravaillés(joursTravaillés);
  }
  
  public double getIndemnites(){
    return feuilleSalaire.getElementsSalaire().getIndemnitesEntretien()+ feuilleSalaire.getElementsSalaire().getIndemnitesRepas();
  }

  // 获取器和设置器
...
}

该类用于存储用户执行的模拟:

  • 第 11 行:模拟编号,
  • 第12行:已计算的工资单,
  • 第13行:工作小时数,
  • 第14行:工作日数。

以下是一个记录示例:

Image

根据上一页的内容,得到以下结果:

Image

模拟编号是一个随每次新记录生成而递增的数字。它属于 Bean SessionData


  // 模拟
  private List<Simulation> simulations = new ArrayList<Simulation>();
  private int numDerniereSimulation = 0;
private Simulation simulation;
  • 第2行:上次模拟的编号。

方法 [enregistrerSimulation] 可按以下方式处理:

  • 从 Bean [SessionData] 中获取上次模拟的编号并递增,
  • 将新模拟添加到由类 [SessionData] 维护的模拟列表中,
  • 显示模拟列表:

模拟列表由页面 [simulations.xhtml] 显示:


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

  <ui:composition template="layout.xhtml">
    <ui:define name="part1">
      <!-- 模拟图表 -->
      <h:dataTable value="#{sessionData.simulations}" var="simulation"
                   headerClass="simulationsHeaders" columnClasses="simuNum,simuNom,simuPrenom,simuHT,simuJT,simuSalaireBase,simuIndemnites,simuCotisationsSociales,simuSalaireNet">
        <h:column>
          <f:facet name="header">
            <h:outputText value="#{msg['simulations.headers.numero']}"/>
          </f:facet>
          <h:outputText value="#{simulation.num}"/>
        </h:column>
        <h:column>
          <f:facet name="header">
            <h:outputText value="#{msg['simulations.headers.nom']}"/>
          </f:facet>
          <h:outputText value="#{simulation.feuilleSalaire.employe.nom}"/>
        </h:column>
        <h:column>
          <f:facet name="header">
            <h:outputText value="#{msg['simulations.headers.prenom']}"/>
          </f:facet>
          <h:outputText value="#{simulation.feuilleSalaire.employe.prenom}"/>
        </h:column>
        <h:column>
          <f:facet name="header">
            <h:outputText value="#{msg['simulations.headers.heuresTravaillees']}"/>
          </f:facet>
          <h:outputText value="#{simulation.heuresTravaillées}"/>
        </h:column>
        <h:column>
          <f:facet name="header">
            <h:outputText value="#{msg['simulations.headers.joursTravailles']}"/>
          </f:facet>
          <h:outputText value="#{simulation.joursTravaillés}"/>
        </h:column>
        <h:column>
          <f:facet name="header">
            <h:outputText value="#{msg['simulations.headers.salaireBase']}"/>
          </f:facet>
          <h:outputText value="#{simulation.feuilleSalaire.elementsSalaire.salaireBase}"/>
        </h:column>
        <h:column>
          <f:facet name="header">
            <h:outputText value="#{msg['simulations.headers.indemnites']}"/>
          </f:facet>
          <h:outputText value="#{simulation.indemnites}"/>
        </h:column>
        <h:column>
          <f:facet name="header">
            <h:outputText value="#{msg['simulations.headers.cotisationsSociales']}"/>
          </f:facet>
          <h:outputText value="#{simulation.feuilleSalaire.elementsSalaire.cotisationsSociales}"/>
        </h:column>
        <h:column>
          <f:facet name="header">
            <h:outputText value="#{msg['simulations.headers.salaireNet']}"/>
          </f:facet>
          <h:outputText value="#{simulation.feuilleSalaire.elementsSalaire.salaireNet}"/>
        </h:column>
        <h:column>
          <h:commandLink value="Retirer" action="#{form.retirerSimulation}">
            <f:setPropertyActionListener target="#{form.numSimulationToDelete}" value="#{simulation.num}"/>
          </h:commandLink>
        </h:column>
      </h:dataTable>
    </ui:define>
  </ui:composition>
</html>
  • 第 8 行,页面 [simulations.xhtml] 被插入到页面 [layout.xhtml] 中,
  • 第9行,在名为part1的片段位置,
  • 第 11 行,标签 <h:dataTable> 使用字段 #{sessionData.simulations} 作为数据源,c.a.d。下一个字段:

  // 模拟
private List<Simulation> simulations;
  • 属性 var="simulation" 用于在 <h:datatable> 标签内设定代表当前模拟的变量名称

  • 属性 headerClass="simulationsHeaders" 设定表格列标题的样式。

  • 属性 columnClasses="...." 设置了表格中每一列的样式

让我们查看表格中的一列,了解其构建方式:

Nom列中的JSF代码如下:


            <h:column>
              <f:facet name="header">
                <h:outputText value="#{msg['simulations.headers.nom']}"/>
              </f:facet>
              <h:outputText value="#{simulation.feuilleSalaire.employe.nom}"/>
</h:column>
  • 第 2-4 行:标签 <f:facet name="header"> 定义了列标题
  • 第 5 行:员工姓名写为:
    • simulation 引用了 <h:dataTable ...> 标签的 var 属性:

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

simulation 指模拟列表中的当前模拟:首先是第 1 个,然后是第 2 个,……

  • (续)
    • simulation.feuilleSalaire 指向当前模拟中的字段 feuilleSalaire
    • simulation.feuilleSalaire.employe 引用了字段 feuilleSalaire 中的“员工”字段
    • simulation.feuilleSalaire.employe.nom 引用了字段 employe 中的字段 nom

对表中的所有列都重复了同样的处理。对于“Indemnités”列存在一个难题,该列是通过以下代码生成的:


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

第5行,显示simulation.indemnites的值。 但 Simulation 类中并不存在 indemnites 字段。需要注意的是,indemnites 字段并非直接使用,而是通过方法 simulation.getIndemnites() 间接调用。因此,只要该方法存在即可。而字段 indemnites 则可以不存在。 方法 getIndemnites 应返回员工的津贴总额。这需要进行中间计算,因为该总额在工资单中无法直接获取。方法 getIndemnites 见第 12.5.5 节。


问题:编写类 [Form] 中的方法 [enregistrerSimulation]。


12.5.6. 操作 [retourSimulateur]

链接 [retourSimulateur] 的代码 JSF 如下:


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

与该链接关联的操作 [retourSimulateur] 允许用户从视图 [vueSimulations] 返回至视图 [vueSaisies]:

Image

生成的结果:

Image


问题:编写类 [Form] 中的方法 [retourSimulateur]。显示的输入表单应如上所示为空。


12.5.7. 操作 [voirSimulations]

链接 [voirSimulations] 的代码 JSF 如下:


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

与该链接关联的操作 [voirSimulations] 允许用户获取模拟表格,无论其输入状态如何:

Image

所得结果:

Image


问题:编写类 [Form] 中的方法 [voirSimulations]。


需确保当模拟列表为空时,显示的视图为 [vueSimulationsVides]:

Image

要显示上述视图,将使用以下页面 [simulationsVides.xhtml]:


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

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

12.5.8. 操作 [retirerSimulation]

用户可以从列表中移除模拟:

Image

所得结果如下:

Image

如果从上述列表中移除最后一个模拟,将得到以下结果:

Image

模拟表中[Retirer]列的代码JSF如下:


          <h:dataTable value="#{form.simulations}" var="simulation"
                       headerClass="simulationsHeaders" columnClasses="simuNum,simuNom,simuPrenom,simuHT,simuJT,simuSalaireBase,simuIndemnites,simuCotisationsSociales,simuSalaireNet">
...
            <h:column>
              <h:commandLink value="Retirer" action="#{form.retirerSimulation}">
                <f:setPropertyActionListener target="#{form.numSimulationToDelete}" value="#{simulation.num}"/>
              </h:commandLink>
            </h:column>
          </h:dataTable>
  • 第 5 行:链接 [Retirer] 关联到类 [Form] 的方法 [retirerSimulation]。 该方法需要知道要删除的模拟编号。该编号由第 8 行中的 <f:setPropertyActionListener> 标签提供。该标签有两个属性:target valuetarget 属性指定模型中的一个字段,value 属性的值将被赋给该字段。 在此,待删除的模拟编号 #{simulation.num} 将被赋值给类 [Form] 中的字段 numSimulationToDelete:

  // 视图模型
  ...
  private Integer numSimulationToDelete;

当类 [Form] 的方法 [retirerSimulation] 执行时,它将能够使用之前存储在字段 numSimulationToDelete 中的值。


问题:编写类 [Form] 的方法 [retirerSimulation]。


12.5.9. 操作 [terminerSession]

链接 [Terminer la session] 的代码 JSF 如下:


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

与该链接关联的操作 [terminerSession] 允许用户退出当前会话并返回空白的输入表单:

Image

Image

如果用户之前有一份模拟列表,该列表将被清空。此外,模拟编号将重置为1。


问题:编写类 [Form] 中的方法 [terminerSession]。


12.6. 将Web层集成到三层架构中 JSF / EJB

之前的Web应用程序架构如下:

我们将模拟的 [métier] 层,替换为第 7.1 节中由 EJB 实现的 [métier, DAO, jpa] 层:


实践作业:按照第11节的方法论,完成JSF和EJB图层的集成。