Skip to content

9. Aplicación de ejemplo 05: rdvmedecins-pfm-ejb

Recordemos la estructura de la aplicación de ejemplo 01 JSF / EJB desarrollada para el servidor Glassfish:

No modificamos nada de esta arquitectura, salvo la capa web, que en este caso se implementará mediante JSF, PrimeFaces y PrimeFaces Mobile. El navegador de destino será el de un móvil.

Hemos desarrollado dos aplicaciones con Glassfish:

  • la aplicación 01 utilizaba JSF / EJB y tenía una interfaz bastante sencilla,
  • la aplicación 03 utilizaba PF / EJB y tenía una interfaz muy completa.

Debido al escaso número de componentes disponibles en PrimeFaces Mobile, volveremos a la interfaz sencilla de la aplicación 01. Además, las vistas deberán poder adaptarse al tamaño reducido de las pantallas de los móviles.

9.1. Las vistas

Para que te hagas una idea, te mostramos algunas vistas de la aplicación ejecutada en el simulador de un iPhone 4:

  • en [1], la página de inicio. Cabe señalar que esta vez ha sido necesario indicar el nombre del equipo (también se puede indicar su dirección IP), ya que con el equipo localhost no se mostraba nada,
  • en [2], la lista de médicos,
  • en [3], la fecha deseada para la cita,
  • en [4], el botón para consultar la agenda del día,
  • en [5], la nueva vista muestra los horarios disponibles del médico,
  • en [6], una serie de botones para navegar por el calendario,
  • en [7], un mensaje que recuerda el médico y el día,
  • en [8], una franja horaria para reservar. Lo hacemos,
  • en [9], la vista de selección del cliente,
  • en [10], un mensaje que recuerda el médico, el día y la franja horaria correspondientes a la cita,
  • en [11], el menú desplegable de clientes,
  • en [12], el botón de validación,
  • en [13], al validar se vuelve a la agenda,
  • en [14], la franja horaria ocupada queda ahora reservada. Ahora vamos a eliminar la reserva,
  • en [15], seguimos en la misma vista,
  • pero en [16], la cita se ha eliminado,

En la página de inicio es posible cambiar de idioma: [17], [18], [19]:

Por último, se ha incluido una vista de errores:

9.2. El proyecto de NetBeans

El proyecto de NetBeans es el siguiente:

  
  • [mv-rdvmedecins-ejb-dao-jpa]: proyecto EJB de las capas [DAO] y [JPA] del ejemplo 01,
  • [mv-rdvmedecins-ejb-metier]: proyecto EJB de la capa [métier] del ejemplo 01,
  • [mv-rdvmedecins-pfmobile]: proyecto de la capa [web] / PrimeFaces Mobile – nuevo,
  • [mv-rdvmedecins-pfmobile-app-ear]: proyecto empresarial para implementar la aplicación en el servidor Glassfish – nuevo.

9.3. El proyecto de empresa

El proyecto de empresa solo sirve para implementar los tres módulos [mv-rdvmedecins-ejb-dao-jpa], [mv-rdvmedecins-ejb-metier] y [mv-rdvmedecins-pfmobile] en el servidor Glassfish. El proyecto de NetBeans es el siguiente:

El proyecto solo existe para estas tres dependencias [1], definidas en el archivo [pom.xml] de la siguiente manera:


<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
...

  <groupId>istia.st</groupId>
  <artifactId>mv-rdvmedecins-pfmobile-app-ear</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>ear</packaging>

  <name>mv-rdvmedecins-pfmobile-app-ear</name>

...
    <dependencies>
        <dependency>
            <groupId>${project.groupId}</groupId>
            <artifactId>mv-rdvmedecins-ejb-dao-jpa</artifactId>
            <version>${project.version}</version>
            <type>ejb</type>
        </dependency>
        <dependency>
            <groupId>${project.groupId}</groupId>
            <artifactId>mv-rdvmedecins-ejb-metier</artifactId>
            <version>${project.version}</version>
            <type>ejb</type>
        </dependency>
        <dependency>
            <groupId>${project.groupId}</groupId>
            <artifactId>mv-rdvmedecins-pfmobile</artifactId>
            <version>${project.version}</version>
            <type>war</type>
        </dependency>
    </dependencies>
</project>
  • líneas 6-9: el artefacto Maven del proyecto empresarial,
  • líneas 14-33: las tres dependencias del proyecto. Fíjate bien en el tipo de estas (líneas 19, 25, 31).

Para ejecutar la aplicación web, habrá que ejecutar este proyecto empresarial.

9.4. El proyecto web PrimeFaces Mobile

El proyecto web Primefaces Mobile es el siguiente:

  • en [1], las páginas del proyecto. La página [index.xhtml] es la única página del proyecto. Consta de cinco vistas: [vue1.xhtml], [vue2.xhtml], [vue3.xhtml], [vueErreurs.xhtml] y [config.xhtml],
  • en [2], los beans de Java. El bean [Application] tiene un ámbito application, y el bean [Form] tiene un ámbito session. La clase [Erreur] encapsula un error,
  • en [3], los archivos de mensajes para la internacionalización,
  • en [4], las dependencias. El proyecto web depende del proyecto EJB de la capa [DAO], el proyecto EJB de la capa [métier] y Primefaces Mobile para la capa [web].

9.5. La configuración del proyecto

La configuración del proyecto es la misma que la de los proyectos Primefaces o JSF que hemos estudiado. A continuación enumeramos los archivos de configuración sin volver a explicarlos.

 

[web.xml]: configura la aplicación web.


<?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">
  <context-param>
    <param-name>javax.faces.PROJECT_STAGE</param-name>
    <param-value>Development</param-value>
  </context-param>
  <context-param>
    <param-name>javax.faces.FACELETS_SKIP_COMMENTS</param-name>
    <param-value>true</param-value>
  </context-param> 
  <servlet>
    <servlet-name>Faces Servlet</servlet-name>
    <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>Faces Servlet</servlet-name>
    <url-pattern>/faces/*</url-pattern>
  </servlet-mapping>
  <session-config>
    <session-timeout>
      30
    </session-timeout>
  </session-config>
  <welcome-file-list>
    <welcome-file>faces/index.xhtml</welcome-file>
  </welcome-file-list>
  <error-page>
    <error-code>500</error-code>
    <location>/faces/exception.xhtml</location>
  </error-page>
  <error-page>
    <exception-type>Exception</exception-type>
    <location>/faces/exception.xhtml</location>
  </error-page>

</web-app>

Cabe destacar, en la línea 26, que la página [index.xhtml] es la página de inicio de la aplicación.

[faces-config.xml]: configura la aplicación JSF


<?xml version='1.0' encoding='UTF-8'?>

<!-- =========== FULL CONFIGURATION FILE ================================== -->

<faces-config version="2.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-facesconfig_2_0.xsd">

  <application>
    <resource-bundle>
      <base-name>
        messages
      </base-name>
      <var>msg</var>
    </resource-bundle>
      <message-bundle>messages</message-bundle>
    <default-render-kit-id>PRIMEFACES_MOBILE</default-render-kit-id>
  </application>
</faces-config>

[beans.xml]: está vacía, pero es necesaria para la anotación @Named


<?xml version="1.0" encoding="UTF-8"?>
<beans 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/beans_1_0.xsd">
</beans>

[messages_fr.properties]: el archivo de mensajes en francés


# página
page.titre=Les M\u00e9decins Associ\u00e9s
format.date=dd/MM/yyyy
format.date_detail=dd/MM/yyyy

# vista1
vue1.header=Les M\u00e9decins Associ\u00e9s - R\u00e9servations

# formulario 1
form1.titre=R\u00e9servations
form1.medecin=M\u00e9decin
form1.jour=Jour (jj/mm/aaaa)
form1.date.requise=La date est n\u00e9cessaire
form1.date.invalide=La date est invalide
form1.date.invalide_detail=La date est invalide
form1.agenda=Agenda
form1.options=Options

# formulario 2
form2.titre={0} {1} {2}<br/>{3}
form2.titre_detail={0} {1} {2}<br/>{3}
form2.retour=Retour
form2.supprimer=Supprimer
form2.reserver=R\u00e9server
form2.precedent=Jour pr\u00e9c\u00e9dent
form2.suivant=Jour suivant
form2.today=Aujourd'hui

# formulario 3
form3.client=Client
form3.valider=Valider
form3.titre={0} {1} {2}<br/>{3}<br/>{4,number,#00}h:{5,número,#00}-{6,número,#00}h:{7,número,#00}
form3.titre_detail={0} {1} {2}<br/>{3}<br/>{4,number,#00}h:{5,número,#00}-{6,número,#00}h:{7,número,#00}
form3.retour=Retour

# error
erreur.titre=Une erreur s'est produite.

# configuración
config.retour=Retour
config.titre=Configuration
config.langue=Langue
config.langue.francais=Fran\u00e7ais
config.langue.anglais=Anglais
config.valider=Valider

#excepción
exception.titre=Application indisponible. Veuillez recommencer ult\u00e9rieurement.

[messages_en.properties]: el archivo de mensajes en inglés


# página
page.titre=The Associated Doctors
format.date=dd/MM/yyyy
format.date_detail=dd/MM/yyyy

# vista1
vue1.header=The Associated Doctors - Reservations

# formulario 1
form1.titre=Reservations
form1.medecin=Doctor
form1.jour=day (dd/mm/yyyy)
form1.date.requise=The date is necessary
form1.date.invalide=Invalid date
form1.date.invalide_detail=invalid date
form1.agenda=Diary
form1.options=Options

# formulario 2
form2.titre={0} {1} {2}<br/>{3}
form2.titre_detail={0} {1} {2}<br/>{3}
form2.retour=Back
form2.supprimer=Delete
form2.reserver=Reserve
form2.precedent=Previous Day
form2.suivant=Next Day
form2.today=Today

# formulario 3
form3.client=Patient
form3.valider=Validate
form3.titre={0} {1} {2}<br/>{3}<br/>{4,number,#00}h:{5,número,#00}-{6,número,#00}h:{7,número,#00}
form3.titre_detail={0} {1} {2}<br/>{3}<br/>{4,number,#00}h:{5,número,#00}-{6,número,#00}h:{7,número,#00}
form3.retour=Back

# error
erreur.titre=Some error happened

# configuración
config.retour=Back
config.titre=Configuration
config.langue=Language
config.langue.francais=French
config.langue.anglais=English
config.valider=Validate

#excepción
exception.titre=Application not available. Please try again later.

9.6. La página [index.xhtml]

El proyecto sigue mostrando siempre la misma página, la siguiente página [index.xhtml]:


<f:view xmlns="http://www.w3.org/1999/xhtml"
        xmlns:f="http://java.sun.com/jsf/core"
        xmlns:h="http://java.sun.com/jsf/html"
        xmlns:ui="http://java.sun.com/jsf/facelets"
        xmlns:p="http://primefaces.org/ui"
        xmlns:pm="http://primefaces.org/mobile"
        contentType="text/html"
        locale="#{form.locale}">

  <pm:page title="#{msg['page.titre']}">
    <pm:view id="vue1">
      <ui:fragment rendered="#{form.form1Rendered}">
        <ui:include src="vue1.xhtml"/>
      </ui:fragment>
      <ui:fragment rendered="#{form.erreurInit}">
        <ui:include src="vueErreurs.xhtml"/>
      </ui:fragment>
    </pm:view>
    <pm:view id="vue2">
      <ui:fragment rendered="#{form.form2Rendered}">
        <ui:include src="vue2.xhtml"/>
      </ui:fragment>
    </pm:view>
    <pm:view id="vue3">
      <ui:fragment rendered="#{form.form3Rendered}">
        <ui:include src="vue3.xhtml"/>
      </ui:fragment>
    </pm:view>
    <pm:view id="vueErreurs">
      <ui:fragment rendered="#{form.erreurRendered}">
        <ui:include src="vueErreurs.xhtml"/>
      </ui:fragment>
    </pm:view>
    <pm:view id="config">
      <ui:include src="config.xhtml"/>
    </pm:view>
  </pm:page>    
</f:view>
  • línea 8: la página está internacionalizada (atributo «locale»),
  • línea 10: la página contiene cinco vistas: vista1 en la línea 11, vista2 en la línea 19, vista3 en la línea 24, vueErreurs en la línea 29 y config en la línea 34. En un momento dado, solo es visible una de estas vistas. Al iniciar la aplicación, se muestra la vista vista1. Aquí nos encontramos con la siguiente dificultad: si la inicialización de la aplicación se ha realizado correctamente, debe mostrarse [vue1.xhtml]; de lo contrario, debe mostrarse [vueErreurs.xhtml]. Hemos resuelto el problema haciendo que el contenido de la vista «vue1» sea gestionado por el modelo, que asigna valores a los booleanos [Form].form1rendered (línea 12) y [Form].erreurInit (línea 15) para fijar el contenido de vue1 (línea 11),

En un simulador, la vista [vue1.xhtml] tiene la representación [1], la vista [vue2.xhtml] tiene la representación [2], y la vista [vue3.xhtml] tiene la representación [3]:

la vista [vueErreurs.xhtml], el resultado [4]; la vista [config.xhtml], el resultado [5]:

9.7. Los beans del proyecto

La clase del paquete [utils] ya se ha presentado: la clase [Messages] es una clase que facilita la internacionalización de los mensajes de una aplicación. Se ha analizado en el apartado 2.8.5.7.

9.7.1. El bean Application

El bean [Application.java] es un bean de ámbito application. Recordemos que este tipo de bean sirve para almacenar datos de solo lectura y disponibles para todos los usuarios de la aplicación. Este bean es el siguiente:


package beans;

import javax.ejb.EJB;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Named;
import rdvmedecins.metier.service.IMetierLocal;

@Named(value = "application")
@ApplicationScoped
public class Application {

  // capa de negocio
  @EJB
  private IMetierLocal metier;

  public Application() {
  }
  
  // getters

  public IMetierLocal getMetier() {
    return metier;
  }
  
}
  • línea 8: se le da al bean el nombre «application»,
  • línea 9: tiene alcance de aplicación,
  • líneas 13-14: el contenedor EJB del servidor de aplicaciones le inyectará una referencia a la interfaz local de la capa [métier]. Recordemos la arquitectura de la aplicación:

Las aplicaciones PFM, EJB y [Metier] se ejecutarán en la misma JVM (Máquina Virtual Java). Por lo tanto, la capa [PFM] utilizará la interfaz local de EJB. Eso es todo. El bean [Application] no contiene nada más. Para acceder a la capa [métier], los demás beans la buscarán en este bean.

9.7.2. El bean [Erreur]

La clase [Erreur] es la siguiente:


package beans;

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

  // constructor
  public Erreur(String classe, String message){
    this.setClasse(classe);
    this.message=message;
  }
  
  // getter y setter
...  
}
  • línea 9, el nombre de una clase de excepción si se ha lanzado una excepción,
  • línea 10: un mensaje de error.

9.7.3. El bean [Form]

Su código es el siguiente:


package beans;

...

@Named(value = "form")
@SessionScoped
public class Form implements Serializable {

  public Form() {
  }
  // bean de aplicación
  @Inject
  private Application application;
  private IMetierLocal metier;
  // caché de sesión
  private List<Medecin> medecins;
  private List<Client> clients;
  private Map<Long, Medecin> hMedecins = new HashMap<Long, Medecin>();
  private Map<Long, Client> hClients = new HashMap<Long, Client>();
  // modelo
  private Long idMedecin;
  private Date jour = new Date();
  private String strJour;
  private Boolean form1Rendered;
  private Boolean form2Rendered;
  private Boolean form3Rendered;
  private Boolean erreurRendered;
  private String form2Titre;
  private String form3Titre;
  private AgendaMedecinJour agendaMedecinJour;
  private Long idCreneauChoisi;
  private Medecin medecin;
  private Long idClient;
  private CreneauMedecinJour creneauChoisi;
  private List<Erreur> erreurs;
  private Boolean erreurInit = false;
  private String action;
  private String locale = "fr";
  private String msgErreurDate = "";
  private SimpleDateFormat dateFormatter;
  private Boolean erreurDate;
  
  @PostConstruct
  private void init() {
    System.out.println("init");
    // al principio no hay error
    erreurInit = false;
    // formato de las fechas
    dateFormatter = new SimpleDateFormat(Messages.getMessage(null, "format.date", null).getSummary());
    dateFormatter.setLenient(false);
    // el día actual
    strJour = dateFormatter.format(jour);
    // se recupera la capa de negocio
    metier = application.getMetier();
    // se almacenan en caché los médicos y los clientes
    try {
      medecins = metier.getAllMedecins();
      clients = metier.getAllClients();
    } catch (Throwable th) {
      // se registra el error
      erreurInit = true;
      prepareVueErreur(th);
      return;
    }
    // verificación de las listas
    if (medecins.size() == 0) {
      // se registra el error
      erreurInit = true;
      erreurs = new ArrayList<Erreur>();
      erreurs.add(new Erreur("", "La liste des médecins est vide"));
    }
    if (clients.size() == 0) {
      // se anota el error
      erreurInit = true;
      erreurs = new ArrayList<Erreur>();
      erreurs.add(new Erreur("", "La liste des clients est vide"));
    }
    // ¿Error?
    if (erreurInit) {
      // Se muestra la vista de errores
      setForms(false, false, false, true);
      return;
    }

    // los diccionarios
    for (Medecin m : medecins) {
      hMedecins.put(m.getId(), m);
    }
    for (Client c : clients) {
      hClients.put(c.getId(), c);
    }
    // la vista 1
    setForms(true, false, false, false);
  }

  // visualización de la vista
  private void setForms(Boolean form1Rendered, Boolean form2Rendered, Boolean form3Rendered, Boolean erreurRendered) {
    this.form1Rendered = form1Rendered;
    this.form2Rendered = form2Rendered;
    this.form3Rendered = form3Rendered;
    this.erreurRendered = erreurRendered;
  }

  // preparación vueErreur
  private void prepareVueErreur(Throwable th) {
    // se crea la lista de errores
    erreurs = new ArrayList<Erreur>();
    erreurs.add(new Erreur(th.getClass().getName(), th.getMessage()));
    while (th.getCause() != null) {
      th = th.getCause();
      erreurs.add(new Erreur(th.getClass().getName(), th.getMessage()));
    }
// se muestra la vista de errores
    setForms(false, false, false, true);
  }

  // getters y setters
  ..
}
  • líneas 5-7: la clase [Form] es un bean de nombre «form» y de ámbito de sesión. Recordemos que, en este caso, la clase debe ser serializable,
  • líneas 12-13: el bean «form» tiene una referencia al bean «application». Esta será inyectada por el contenedor de servlets en el que se ejecuta la aplicación (presencia de la anotación @Inject).
  • líneas 24-27: controlan la visualización de las vistas vue1 (línea 24), vue2 (línea 25), vue3 (línea 26) y vueErreurs (línea 27),
  • líneas 43-44: el método init se ejecuta justo después de la instanciación de la clase (presencia de la anotación @PostConstruct),
  • líneas 49-50: gestionan el formato de las fechas. PrimeFaces Mobile no ofrece un calendario. Además, los validadores JSF no se pueden utilizar en una página PFM. Por lo tanto, tendremos que gestionar manualmente la introducción de la fecha de la agenda,
  • línea 49: establece el formato de las fechas. Este formato se toma de los archivos internacionalizados:

format.date=dd/MM/yyyy
format.date_detail=dd/MM/yyyy
  • línea 50: se indica que no debe ser «laxiste» en lo que respecta a las fechas. Si no se hace así, una entrada como 32/12/2011, que es incorrecta, se considera la fecha válida 01/01/2012,
  • línea 54: se obtiene una referencia de la capa [métier] a través del bean [Application],
  • líneas 57-58: se solicita a la capa [métier] la lista de médicos y clientes,
  • líneas 66-91: si todo ha ido bien, se crean los diccionarios de médicos y clientes. Se indexan por su número. A continuación, se mostrará la vista [vue1.xhtml] (línea 93),
  • línea 59: en caso de error, se genera la plantilla de la página [vueErreurs.xhtml]. Esta plantilla es la lista de errores de la línea 35,
  • líneas 105-115: el método [prepareVueErreur] genera la lista de errores que se va a mostrar. A continuación, la página [index.xhtml] muestra la vista [vueErreurs.xhtml] (línea 114).

La página [erreur.xhtml] es la siguiente:


<?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:p="http://primefaces.org/ui"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:pm="http://primefaces.org/mobile"
      xmlns:ui="http://java.sun.com/jsf/facelets">

  <!-- Vista de errores -->
  <pm:header title="#{msg['page.titre']}" swatch="b">
    <f:facet name="left">
      <p:button icon="home" value=" " href="#vue1?reverse=true" />
    </f:facet>
  </pm:header>
  <pm:content>
    <div align="center">
      <h1><h:outputText value="#{msg['erreur.titre']}" style="color: blue"/></h1>
    </div>

    <p:dataList value="#{form.erreurs}" var="erreur">
      <b>#{erreur.classe}</b> : <i>#{erreur.message}</i>
    </p:dataList>
  </pm:content>
</html>

Utiliza una etiqueta <p:dataList> (líneas 21-23) para mostrar la lista de errores. El botón de la línea 13 permite volver a la vista «vista1».

  • El botón [1] se genera en la línea 13. El atributo «icon» establece el icono del botón. El botón lleva de vuelta a la vista «vue1» (atributo «href»),
  • el encabezado [2] se genera mediante las líneas 11-15,
  • el título [3] se genera en la línea 18,
  • el texto [4] se genera a partir de la plantilla #{erreur.classe} de la línea 22,
  • el texto [5] se genera a partir de la plantilla #{erreur.message} de la línea 22.

Ahora vamos a definir las diferentes fases del ciclo de vida de la aplicación. Para cada acción del usuario, analizaremos las vistas implicadas y los gestores de eventos que se producen en ellas.

9.8. Visualización de la página de inicio

Si todo va bien, la primera vista que se muestra es [vue1.xhtml]. El resultado es la siguiente vista:

El código de la vista [vue1.xhtml] es el siguiente:


<?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:p="http://primefaces.org/ui"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:pm="http://primefaces.org/mobile"
      xmlns:ui="http://java.sun.com/jsf/facelets">

  <!-- Vista 1 -->
  <pm:header title="#{msg['page.titre']}" swatch="b">
    <f:facet name="left">
      <p:button icon="gear" value=" "  href="#config" />
    </f:facet>
  </pm:header>
  <pm:content>
    <h:form id="form1">
      <div align="center">
        <h1><h:outputText value="#{msg['form1.titre']}" style="color: blue"/></h1>
      </div>
      <pm:field>
        <h:outputLabel value="#{msg['form1.medecin']}" for="choixMedecin"/>
        <h:selectOneMenu id="choixMedecin" value="#{form.idMedecin}">  
          <f:selectItems value="#{form.medecins}" var="medecin" itemLabel="#{medecin.titre} #{medecin.prenom} #{medecin.nom}" itemValue="#{medecin.id}"/>  
        </h:selectOneMenu>              
      </pm:field>
      <pm:field>
        <h:outputLabel value="#{msg['form1.jour']}" for="jour"/>
        <p:inputText id="jour" value="#{form.strJour}"/>
        <ui:fragment rendered="#{form.erreurDate}">
          <p:spacer width="50px"/>
          <h:outputText id="msgErreurDate" value="#{form.msgErreurDate}" style="color: red"/>
        </ui:fragment>
      </pm:field>
      <p:commandButton value="#{msg['form1.agenda']}" update=":form1, :vue2, :vueErreurs" action="#{form.getAgenda}" />
    </h:form>
  </pm:content>
</html>
  • líneas 11-15: generan el encabezado [1],
  • línea 13: genera el botón [2]. Al hacer clic en él, se muestra la vista de configuración (atributo href),
  • línea 19: genera el título [3],
  • líneas 21-26: generan el menú desplegable de médicos [4],
  • líneas 27-34: generan el campo de introducción de la fecha [5]. Este campo se crea con una etiqueta <p:inputText> sin validador. La validación de la fecha se realizará en el servidor. Si la fecha es incorrecta, este mostrará un mensaje de error que aparecerá en las líneas 30-34,
  • línea 35: el botón que valida el formulario. Actualiza tres campos: form1 (el formulario en vue1), vue2 y vueErreurs. De hecho, si la fecha no es válida, es form1 el que debe actualizarse. Si la fecha es correcta, es vue2 el que debe actualizarse. Por último, si se produce una excepción (por ejemplo, una conexión a la base de datos fallida), se debe mostrar vueErreurs. Podría resultar tentador sustituir form1 por vue1 (para actualizar toda la vista). En ese caso, la aplicación da un error.

Esta vista se basa en el siguiente modelo:


@Named(value = "form")
@SessionScoped
public class Form implements Serializable {

  public Form() {
  }

  // Bean de aplicación
  @Inject
  private Application application;
  private IMetierLocal metier;
  // Caché de sesión
  private List<Medecin> medecins;
  private List<Client> clients;
  // plantilla
  private Long idMedecin;
  private Date jour = new Date();
  private String strJour;
  private Boolean form1Rendered;
  private Boolean form2Rendered;
  private Boolean form3Rendered;
  private Boolean erreurRendered;
  private String msgErreurDate = "";
  private Boolean erreurDate;
  
    // Lista de médicos
  public List<Medecin> getMedecins() {
    return medecins;
  }

  // agenda
  public String getAgenda() {
...
    }
  }
  • el campo de la línea 16 alimenta, en lectura y escritura, el valor de la lista de la línea 23 de la página. Al mostrarse la página por primera vez, establece el valor seleccionado en el cuadro combinado,
  • el método de las líneas 27-29 genera los elementos del cuadro combinado de médicos (línea 24 de la vista). Cada opción generada tendrá como etiqueta (itemLabel) el cargo, el apellido y el nombre del médico, y como valor (itemValue), el ID del médico,
  • el campo de la línea 18 alimenta en lectura y escritura el campo de entrada de la línea 29 de la página,
  • líneas 32-34: el método getAgenda gestiona el clic en el botón [Agenda] de la línea 35 de la página. Su código es el siguiente:

// agenda
  public String getAgenda() {
    try {
      // se comprueba el día
      jour = dateFormatter.parse(strJour);
      // sin errores
      erreurDate=false;
      msgErreurDate = "";
      // se crea la agenda
      return getAgenda(jour);
    } catch (ParseException ex) {
      // mensaje de error
      erreurDate=true;
      msgErreurDate = Messages.getMessage(null, "form1.date.invalide", null).getSummary();
      // vista1      
      setForms(true, false, false, false);
      return "pm:vue1";
    }
  }
  • el método comienza comprobando la validez de la fecha introducida por el usuario,
  • línea 5: la fecha es parsée según el formato de fecha inicializado por el método init al instanciar el modelo,
  • línea 11: si se produce una excepción, se establece un error (línea 13), se genera un mensaje de error internacionalizado (línea 14), se prepara la vista vue1 (línea 16) y se muestra la vista vue1 (línea 17),
  • línea 10: si la fecha es correcta, se ejecuta el siguiente método:

  // agenda
  public String getAgenda(Date jour) {
    // por el momento no se ha seleccionado ninguna franja horaria
    creneauChoisi = null;
    try {
      // se recupera el médico seleccionado
      medecin = hMedecins.get(idMedecin);
      // título del formulario 2
      form2Titre = Messages.getMessage(null, "form2.titre", new Object[]{medecin.getTitre(), medecin.getPrenom(), medecin.getNom(), new SimpleDateFormat("dd MMM yyyy").format(jour)}).getSummary();
      // la agenda del médico para un día determinado
      agendaMedecinJour = metier.getAgendaMedecinJour(medecin, jour);
      // se muestra la vista 2
      setForms(false, true, false, false);
      return "pm:vue2";
    } catch (Throwable th) {
      System.out.println(th);
      // vista de errores
      prepareVueErreur(th);
      return "pm:vueErreurs";
    }
}

Aquí volvemos a encontrar un código que ya hemos visto en varias ocasiones. En la línea 9, se genera un mensaje internacionalizado para la vista vue2:


form2.titre={0} {1} {2}<br/>{3}
form2.titre_detail={0} {1} {2}<br/>{3}

Cabe destacar que hemos incluido XHTML en el mensaje. Se mostrará de la siguiente manera:

9.9. Mostrar la agenda de un médico

La agenda del médico se muestra mediante la vista [vue2.xhtml]:

El código de la vista [vue2.xhtml] es el siguiente:


<?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:p="http://primefaces.org/ui"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:pm="http://primefaces.org/mobile"
      xmlns:ui="http://java.sun.com/jsf/facelets">

  <!-- Vista 2 -->
  <pm:header title="#{msg['page.titre']}" swatch="b"/>
  <pm:content>
    <h:form id="form2">
      <div align="center">
        <pm:buttonGroup orientation="horizontal">
          <p:commandButton inline="true" icon="back" value=" " action="#{form.showVue1}" update=":vue1"/>
          <p:commandButton inline="true" icon="minus" value=" " action="#{form.getPreviousAgenda}" update=":form2"/>
          <p:commandButton inline="true" icon="home" value=" " action="#{form.getTodayAgenda}" update=":form2"/>
          <p:commandButton inline="true" icon="plus" value=" " action="#{form.getNextAgenda}" update=":form2"/>
        </pm:buttonGroup>
        <h3><h:outputText value="#{form.form2Titre}" style="color: blue" escape="false"/></h3>
      </div>

      <p:dataList id="creneaux" type="inset" value="#{form.agendaMedecinJour.creneauxMedecinJour}" var="creneauMedecinJour">
        <p:column>
          <div align="center">
            <h2>
              <h:outputFormat value="{0,number,#00}h:{1,number,#00} - {2,number,#00}h:{3,number,#00}">
                <f:param value="#{creneauMedecinJour.creneau.hdebut}" />
                <f:param value="#{creneauMedecinJour.creneau.mdebut}" />
                <f:param value="#{creneauMedecinJour.creneau.hfin}" />
                <f:param value="#{creneauMedecinJour.creneau.mfin}" />
              </h:outputFormat>
              <ui:fragment rendered="#{creneauMedecinJour.rv!=null}">
                <br/>
                <h:outputText value="#{creneauMedecinJour.rv.client.titre} #{creneauMedecinJour.rv.client.prenom} #{creneauMedecinJour.rv.client.nom}" style="color: blue"/>
              </ui:fragment>
            </h2>
          </div>
          <div align="center">
            <ui:fragment rendered="#{creneauMedecinJour.rv!=null}">
              <p:commandButton inline="true" action="#{form.action}" value="#{msg['form2.supprimer']}" icon="minus" update=":form2, :vue3, :vueErreurs">
                <f:setPropertyActionListener value="#{creneauMedecinJour.creneau.id}" target="#{form.idCreneauChoisi}"/>
              </p:commandButton>
            </ui:fragment>
            <ui:fragment rendered="#{creneauMedecinJour.rv==null}">
              <p:commandButton inline="true" action="#{form.action}" value="#{msg['form2.reserver']}" icon="plus" update=":form2, :vue3, :vueErreurs">
                <f:setPropertyActionListener value="#{creneauMedecinJour.creneau.id}" target="#{form.idCreneauChoisi}"/>
              </p:commandButton>
            </ui:fragment>
          </div>
        </p:column>
      </p:dataList>
    </h:form>
  </pm:content>
</html>
  • líneas 11: genera el encabezado [1],
  • líneas 15-20: genera el grupo de botones [2],
  • línea 21: genera el título [3]. Cabe destacar el valor del atributo «escape». Es lo que permite interpretar el código XHTML que hemos incluido en form2Titre,
  • línea 24: la visualización de los franjas horarias se realiza mediante un dataList,
  • líneas 28-33: generan el título del intervalo horario [4],
  • líneas 34-37: muestran un fragmento si hay una cita en ese intervalo horario,
  • línea 36: muestra la identidad del cliente que ha concertado la cita,
  • líneas 41-45: muestran el botón [Supprimer] si hay una cita,
  • líneas 46-50: muestran el botón [Réserver] si no hay ninguna cita.

Esta vista se alimenta principalmente del siguiente modelo:


private AgendaMedecinJour agendaMedecinJour;

que alimenta el campo dataList de la línea 24. Este campo se ha creado mediante el método getAgenda, al pasar de la vista «vista1» a la vista «vista2».

9.10. Eliminación de una cita

La eliminación de una cita corresponde a la siguiente secuencia:

La vista afectada por esta acción es la siguiente:


<ui:fragment rendered="#{creneauMedecinJour.rv!=null}">
              <p:commandButton inline="true" action="#{form.action}" value="#{msg['form2.supprimer']}" icon="minus" update=":form2, :vueErreurs">
                <f:setPropertyActionListener value="#{creneauMedecinJour.creneau.id}" target="#{form.idCreneauChoisi}"/>
              </p:commandButton>
            </ui:fragment>
  • línea 2: el botón [Supprimer] está asociado al método [Form].action (atributo «action»),
  • línea 3: el ID de la franja horaria en la que nos encontramos se enviará al modelo [Form].idCreneauChoisi,
  • línea 2: la llamada AJAX actualizará los campos de form2 (formulario de vista2) y la vista vueErreurs. De hecho, hay dos casos posibles: si todo va bien, se volverá a mostrar la vista «vista2»; de lo contrario, se mostrará la vista vueErreurs.

El método [action] es el siguiente:


  // Acción en RV
  public String action() {
    // se busca la franja horaria en la agenda
    int i = 0;
    Boolean trouvé = false;
    while (!trouvé && i < agendaMedecinJour.getCreneauxMedecinJour().length) {
      if (agendaMedecinJour.getCreneauxMedecinJour()[i].getCreneau().getId() == idCreneauChoisi) {
        trouvé = true;
      } else {
        i++;
      }
    }
    // ¿Se ha encontrado?
    if (!trouvé) {
      // Qué raro... volvemos a mostrar el formulario 2
      setForms(false, true, false, false);
      return "pm:vue2";
    } else {
      creneauChoisi = agendaMedecinJour.getCreneauxMedecinJour()[i];
    }
    // lo hemos encontrado
    // según la acción deseada
    if (creneauChoisi.getRv() == null) {
      return reserver();
    } else {
      return supprimer();
    }
  }

  // reserva
  public String reserver() {
 ...
  }

  public String supprimer() {
    try {
      // eliminación de una cita
      metier.supprimerRv(creneauChoisi.getRv());
      // se actualiza la agenda
      agendaMedecinJour = metier.getAgendaMedecinJour(medecin, jour);
      // se muestra el formulario 2
      setForms(false, true, false, false);
      return "pm:vue2";
    } catch (Throwable th) {
      // vista de errores
      prepareVueErreur(th);
      return "pm:vueErreurs";
    }
}
  • líneas 3-12: se busca la franja horaria cuyo ID se ha recibido (línea 7);
  • si no se encuentra, lo cual es anómalo, se vuelve a mostrar la vista2 (líneas 16-17),
  • línea 19: si se encuentra, se almacena el objeto [CreneauMedecinJour] correspondiente. Es este el que nos da acceso a la cita que hay que eliminar,
  • línea 26: se elimina,
  • líneas 35-48: el método «suprimir» devuelve la vista vue2 si la supresión se ha realizado correctamente (líneas 42-43) o la vista vueErreurs si ha habido algún problema (líneas 46-47).

9.11. Contratación de citas

La creación de una cita sigue la secuencia siguiente:

Se pasa de la vista «vista2» a la vista «vista3». El código relacionado con esta acción es el siguiente:


<ui:fragment rendered="#{creneauMedecinJour.rv==null}">
              <p:commandButton inline="true" action="#{form.action}" value="#{msg['form2.reserver']}" icon="plus" update=":vue3, :vueErreurs">
                <f:setPropertyActionListener value="#{creneauMedecinJour.creneau.id}" target="#{form.idCreneauChoisi}"/>
              </p:commandButton>
            </ui:fragment>
  • línea 2: el botón [Réserver] está asociado al método [Form].action (atributo action), es decir, el mismo que el del botón [Supprimer]. La llamada AJAX actualiza las vistas vue3 y vueErreurs dependiendo de si se producen o no errores durante el procesamiento de la llamada.
  • línea 3: al igual que en el botón [Supprimer], se pasa el ID de la franja horaria a la plantilla.

La plantilla que gestiona esta acción es la siguiente:


// acción en RV
  public String action() {
    ...
    // según la acción deseada
    if (creneauChoisi.getRv() == null) {
      return reserver();
    } else {
      return supprimer();
    }
  }

  // reserva
  public String reserver() {
    try {
      // título del formulario 3
      form3Titre = Messages.getMessage(null, "form3.titre", new Object[]{medecin.getTitre(), medecin.getPrenom(), medecin.getNom(), new SimpleDateFormat("dd MMM yyyy").format(jour),
                creneauChoisi.getCreneau().getHdebut(), creneauChoisi.getCreneau().getMdebut(), creneauChoisi.getCreneau().getHfin(), creneauChoisi.getCreneau().getMfin()}).getSummary();
      // se muestra el formulario 3
      setForms(false, false, true, false);
      return "pm:vue3";
    } catch (Throwable th) {
      // vista de errores
      prepareVueErreur(th);
      return "pm:vueErreurs";
    }
  }
  • líneas 2-10: el método action determinará la referencia creneauChoisi del objeto [CreneauMedecinJour] objeto de la reserva y, a continuación, llamará al método reserver,
  • línea 16: se genera un mensaje internacionalizado. Es el siguiente:
form3.titre={0} {1} {2}<br/>{3}<br/>{4,number,#00}h:{5,número,#00}-{6,número,#00}h:{7,número,#00}
form3.titre_detail={0} {1} {2}<br/>{3}<br/>{4,number,#00}h:{5,número,#00}-{6,número,#00}h:{7,número,#00}

Este será el título de la vista «vue3». Al igual que en la vista «vue2», este título incluye XML. También incluye parámetros formateados para mostrar los horarios de la franja horaria,

  • líneas 19-20: se muestra la vista vue3,
  • líneas 23-24: se muestra la vista vueErreurs si se han producido problemas.

9.12. Validación de una cita

La validación de una cita sigue la siguiente secuencia:

En [1], la vista vue3, y en [2], la vista vue2 tras añadir una cita.

El código [vue3.xhtml] de vue3 es el siguiente:


<?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:p="http://primefaces.org/ui"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:pm="http://primefaces.org/mobile"
      xmlns:ui="http://java.sun.com/jsf/facelets">

  <!-- Vista 3 -->
  <pm:header title="#{msg['page.titre']}" swatch="b"/>
  <pm:content>
    <h:form id="form3">
      <p:commandButton inline="true" value=" " icon="back" action="#{form.showVue2}" update=":vue2"/>
      <div align="center">
        <h3><h:outputText value="#{form.form3Titre}" style="color: blue" escape="false"/></h3>
      </div>
      <pm:field>
        <h:outputLabel value="#{msg['form3.client']}" for="choixClient"/>
        <h:selectOneMenu id="choixClient" value="#{form.idClient}">
          <f:selectItems value="#{form.clients}" var="client" itemLabel="#{client.titre} #{client.prenom} #{client.nom}" itemValue="#{client.id}"/>
        </h:selectOneMenu>
      </pm:field>
      <div align="center">
        <p:commandButton inline="true" value="#{msg['form3.valider']}" action="#{form.validerRv}" update=":vue2, :vueErreurs" icon="check"/>
      </div>
    </h:form>
  </pm:content>
</html>
  • línea 16: genera el título de la vista [3]. Cabe destacar el valor del atributo «escape», que permite la interpretación de los caracteres XHTML en el título,
  • líneas 18-23: generan el menú desplegable de clientes [4],
  • línea 25: genera el botón [Valider] [5]. El método [Form].validerRv está asociado a este botón:

// validación de la cita
  public String validerRv() {
    try {
      // se recupera una instancia de la franja horaria elegida
      Creneau creneau = metier.getCreneauById(idCreneauChoisi);
      // se añade la cita
      metier.ajouterRv(jour, creneau, hClients.get(idClient));
      // se actualiza la agenda
      agendaMedecinJour = metier.getAgendaMedecinJour(medecin, jour);
      // se muestra form2
      setForms(false, true, false, false);
      return "pm:vue2";
    } catch (Throwable th) {
      // vista de errores
      prepareVueErreur(th);
      return "pm:vueErreurs";
    }
  }

Este código ya aparecía en la versión 01. Solo cabe destacar la visualización de las vistas:

  • la vista «vue2» (líneas 11-12) si todo ha salido bien,
  • la vista vueErreurs (líneas 15-16) en caso contrario.

9.13. Cancelación de una cita

Esto corresponde a la siguiente secuencia:

El botón [1] de la vista [vue3.xhtml] es el siguiente:


      <p:commandButton inline="true" value=" " icon="back" action="#{form.showVue2}" update=":vue2"/>

Por lo tanto, se invoca el método [Form].showVue2. Este se limita a mostrar la vista 2:


  public String showVue2() {
    // vista 2
    setForms(false, true, false, false);
    return "pm:vue2?reverse=true";
}

9.14. Navegación por el calendario

En vista2, hay botones que permiten navegar por el calendario:

Día anterior:

Día siguiente:

Hoy:

Aunque no se muestra en las capturas de pantalla anteriores, la agenda se actualiza y muestra las citas del nuevo día seleccionado.

Las etiquetas de los tres botones en cuestión son las siguientes en [vue2.xhtml]:


<pm:buttonGroup orientation="horizontal">
          <p:commandButton inline="true" icon="back" value=" " action="#{form.showVue1}" update=":vue1"/>
          <p:commandButton inline="true" icon="minus" value=" " action="#{form.getPreviousAgenda}" update=":form2"/>
          <p:commandButton inline="true" icon="home" value=" " action="#{form.getTodayAgenda}" update=":form2"/>
          <p:commandButton inline="true" icon="plus" value=" " action="#{form.getNextAgenda}" update=":form2"/>
        </pm:buttonGroup>

Los métodos [Form].getPreviousAgenda, [Form].getNextAgenda, [Form].today se han analizado en el ejemplo 03.

9.15. Cambio del idioma de visualización

El cambio de idioma se gestiona mediante un botón de la página de inicio:

El código del botón es el siguiente:


      <p:button icon="gear" value=" "  href="#config" />

Por lo tanto, cambia a la vista de configuración [2]. La vista [config.xhtml] es la siguiente:


<?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:p="http://primefaces.org/ui"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:pm="http://primefaces.org/mobile"
      xmlns:ui="http://java.sun.com/jsf/facelets">

  <!-- Vista 1 -->
  <pm:header title="#{msg['page.titre']}" swatch="b">
    <f:facet name="left">
      <p:button icon="back" value=" " href="#vue1?reverse=true" />
    </f:facet>
  </pm:header>
  <pm:content>
    <h:form id="frmConfig">
      <div align="center">
        <h3><h:outputText value="#{msg['config.titre']}" style="color: blue"/></h3>
      </div>
      <pm:field>
        <h:outputLabel value="#{msg['config.langue']}" for="langue"/>
        <h:selectOneRadio id="langue" value="#{form.locale}">
          <f:selectItem itemLabel="#{msg['config.langue.francais']}" itemValue="fr"/>
          <f:selectItem itemLabel="#{msg['config.langue.anglais']}" itemValue="en" />
        </h:selectOneRadio>
      </pm:field>
      <p:commandButton value="#{msg['config.valider']}" action="#{form.configurer}" update=":vue1"/>
    </h:form>
  </pm:content>
</html>
  • línea 11: muestra [3],
  • línea 13: muestra el botón [4]. Este botón permite volver a la vista «vista1»,
  • línea 17: el formulario de la vista,
  • línea 19: muestra el título de la vista [5],
  • líneas 21-27: muestran los botones de opción. El valor (itemValue) del botón de opción seleccionado se enviará a la plantilla [Form].locale (atributo «value» de la línea 23),
  • línea 28: muestra el botón [Valider]. La llamada AJAX actualiza la vista vue1 (atributo «update»). El método al que se llama es [Form].configurer:

public String configurer(){
    // Tras la configuración: se vuelve a mostrar la vista 1
    redirect();
    return null;
  }
  
  private void redirect() {
    // se redirige al cliente al servlet
    ExternalContext ctx = FacesContext.getCurrentInstance().getExternalContext();
    try {
      ctx.redirect(ctx.getRequestContextPath());
    } catch (IOException ex) {
      Logger.getLogger(Form.class.getName()).log(Level.SEVERE, null, ex);
    }
  }

El método «configurer» (línea 1) se limita a redirigir el navegador del móvil hacia la página URL de la aplicación. Por lo tanto, se cargará la página [index.xhtml]:


<f:view xmlns="http://www.w3.org/1999/xhtml"
        xmlns:f="http://java.sun.com/jsf/core"
        xmlns:h="http://java.sun.com/jsf/html"
        xmlns:ui="http://java.sun.com/jsf/facelets"
        xmlns:p="http://primefaces.org/ui"
        xmlns:pm="http://primefaces.org/mobile"
        contentType="text/html"
        locale="#{form.locale}">

  <pm:page title="#{msg['page.titre']}">
    <pm:view id="vue1">
      ...
    </pm:view>
    ...
  </pm:page>    
</f:view>
  • línea 8: la vista utilizará el idioma que se acaba de cambiar (atributo «locale») y mostrará «vue1» (línea 11).

9.16. Conclusion

Recordemos la arquitectura de la aplicación que acabamos de crear:

El cambio a una interfaz para móviles ha requerido reescribir las páginas XHTML. El modelo, por su parte, apenas ha cambiado. Las capas inferiores [métier], [DAO] y [JPA] no han cambiado en absoluto.

9.17. Pruebas con Eclipse

Al igual que hicimos con las versiones anteriores de la aplicación de ejemplo, mostramos cómo probar esta versión 05 con Eclipse. En primer lugar, importamos a Eclipse los proyectos Maven del ejemplo 05 [1]:

  • [mv-rdvmedecins-ejb-dao-jpa]: las capas [DAO] y [JPA],
  • [mv-rdvmedecins-ejb-metier]: la capa [métier],
  • [mv-rdvmedecins-pfmobile]: la capa [web] implementada por PrimeFaces Mobile,
  • [mv-rdvmedecins-pfmobile-app]: el proyecto principal [mv-rdvmedecins-pfmobile-app-ear]. Al importar el proyecto principal, el proyecto secundario se importa automáticamente,
  • en [2], se ejecuta el proyecto empresarial [mv-rdvmedecins-pfmobile-app-ear],
  • en [3], se selecciona el servidor Glassfish,
  • en [4], en la pestaña [Servers], la aplicación se ha desplegado. No se ejecuta por sí sola. Hay que abrirla en un navegador o en un simulador de móvil: