6. برنامه نمونه-۰۳: rdvmedecins-pf-ejb
بیایید ساختار برنامه نمونه توسعه یافته برای سرور GlassFish را به یاد بیاوریم:
![]() |
ما در این معماری هیچ تغییری ایجاد نمیکنیم، به جز لایه وب که در اینجا با استفاده از JSF و PrimeFaces پیادهسازی خواهد شد.
![]() |
6.1. پروژه NetBeans
در بالا، لایههای [métier] و [DAO] از مثال ۰۱ (JSF / EJB / GlassFish) هستند. ما از آنها مجدداً استفاده میکنیم.
![]() |
- [mv-rdvmedecins-ejb-dao-jpa]: پروژه EJB شامل لایههای [DAO] و [JPA] از مثال 01،
- [mv-rdvmedecins-ejb-metier]: پروژه EJB از لایه [métier] در مثال 01،
- [mv-rdvmedecins-pf]: پروژه در لایه [web] / PrimeFaces – جدید،
- [mv-rdvmedecins-app-ear]: پروژه سازمانی برای استقرار برنامه روی سرور GlassFish – جدید.
6.2. پروژه سازمانی
پروژه سازمانی صرفاً برای استقرار سه ماژول [mv-rdvmedecins-ejb-dao-jpa]، [mv-rdvmedecins-ejb-metier] و [mv-rdvmedecins-pf] روی سرور GlassFish استفاده میشود. پروژه NetBeans به شرح زیر است:
![]() |
این پروژه صرفاً برای این سه وابستگی، [1]، وجود دارد که در فایل [pom.xml] به شرح زیر تعریف شدهاند:
<?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>
<parent>
<artifactId>mv-rdvmedecins-app</artifactId>
<groupId>istia.st</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<groupId>istia.st</groupId>
<artifactId>mv-rdvmedecins-app-ear</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>ear</packaging>
<name>mv-rdvmedecins-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-pf</artifactId>
<version>${project.version}</version>
<type>war</type>
</dependency>
</dependencies>
</project>
- خطوط ۱۰–۱۳: آرتفکت Maven برای پروژه سازمانی،
- خطوط ۱۸–۳۷: سه وابستگی پروژه. لطفاً نوع این وابستگیها را (خطوط ۲۳، ۲۹، ۳۵) توجه کنید.
برای اجرای برنامه وب، باید این پروژه سازمانی را اجرا کنید.
6.3. پروژه وب PrimeFaces به شرح زیر است:
پروژه وب PrimeFaces به شرح زیر است:
![]() |
- در [1]، صفحات پروژه. صفحه [index.xhtml] تنها صفحه پروژه است. این صفحه شامل سه قطعه است: [form1.xhtml]، [form2.xhtml] و [erreur.xhtml]. سایر صفحات صرفاً برای اهداف قالببندی گنجانده شدهاند.
- در [2]، جاوا بینها. بین [Application] دارای دامنه application است؛ بین [Form] دارای دامنه session است. کلاس [Erreur] یک خطا را در بر میگیرد. کلاس [MyDataModel] به عنوان قالبی برای تگ PrimeFaces <dataTable> عمل میکند،
- در [3]، فایلهای پیام برای بینالمللیسازی،
- در [4]، وابستگیها. پروژه وب به پروژه EJB در لایه [DAO] وابسته است، پروژه EJB در لایه [métier]، و PrimeFaces برای لایه [web].
6.4. پیکربندی پروژه
پیکربندی پروژه مشابه پروژههای Primefaces یا JSF است که بررسی کردهایم. ما فایلهای پیکربندی را بدون توضیح مجدد فهرست میکنیم.
![]() | ![]() |
[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">
<context-param>
<param-name>javax.faces.STATE_SAVING_METHOD</param-name>
<param-value>client</param-value>
</context-param>
<context-param>
<param-name>javax.faces.PROJECT_STAGE</param-name>
<param-value>Production</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>
توجه کنید که در خط ۳۰، صفحه [index.xhtml] صفحه اصلی برنامه است.
[faces-config.xml]: برنامه 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>
</application>
</faces-config>
[beans.xml]: خالی اما برای حاشیهنویسی @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>
[styles.css]: برگهٔ سبک برنامه
.col1{
background-color: #ccccff
}
.col2{
background-color: #ffcccc
}
کتابخانه PrimeFaces دارای سبکهای صفحهآرایی (style sheets) خود است. سبک صفحهآرایی بالا تنها در صورتی استفاده میشود که صفحه در هنگام رخ دادن یک استثنا – یعنی صفحهای که توسط برنامه مدیریت نمیشود – نمایش داده شود. در این حالت، صفحه [exception.xhtml] نمایش داده میشود.
[messages_fr.properties]: فایل پیام فرانسوی
#چیدمان
layout.entete=Les M\u00e9decins Associ\u00e9s
layout.basdepage=ISTIA, universit\u00e9 d'Angers - application propuls\u00e9e par PrimeFaces et JQuery
# استثناء
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
# فرم ۱
form1.titre=R\u00e9servations
form1.medecin=M\u00e9decin
form1.jour=Jour
form1.options=Options
form1.francais=Fran\u00e7ais
form1.anglais=Anglais
form1.rafraichir=Rafra\u00eechir
form1.precedent=Jour pr\u00e9c\u00e9dent
form1.suivant=Jour suivant
form1.agenda=Affiche l'agenda du m\u00e9decin choisi pour le jour choisi
form1.today=Aujourd'hui
# فرم ۲
form2.titre=Agenda de {0} {1} {2} le {3}
form2.titre_detail=Agenda de {0} {1} {2} le {3}
form2.creneauHoraire=Cr\u00e9neau horaire
form2.client=Client
form2.accueil=Accueil
form2.supprimer=Supprimer
form2.reserver=R\u00e9server
form2.valider=Valider
form2.annuler=Annuler
form2.erreur=Erreur
form2.emtyMessage=Pas de cr\u00e9neaux entr\u00e9s dans la base
form2.suppression.confirmation=Etes-vous s\u00fbr(e) ?
form2.suppression.message=Suppression d'un rendez-vous
form2.supprimer.oui=Oui
form2.supprimer.non=Non
form2.erreurClient=Client [{0}] inconnu
form2.erreurClient_detail=Client {0} inconnu
form2.erreurAction=Action non autoris\u00e9e
form2.erreurAction_detail=Action non autoris\u00e9e
# خطا
erreur.titre=Une erreur s'est produite.
erreur.exceptions=Cha\u00eene des exceptions
erreur.type=Type de l'exception
erreur.message=Message associ\u00e9
erreur.accueil=Accueil
[messages_en.properties]: فایل پیام انگلیسی
# قالببندی
layout.entete=Associated Doctors
layout.basdepage=ISTIA, Angers university - Application powered by PrimeFaces and JQuery
# استثناء
exception.header=The following exceptions occurred
exception.httpCode=Error HTTP code
exception.message=Exception message
exception.requestUri=Url targeted when error occurred
exception.servletName=Servlet targeted's name when error occurred
# فرم ۱
form1.titre=Reservations
form1.medecin=Doctor
form1.jour=Date
form1.options=Options
form1.francais=French
form1.anglais=English
form1.rafraichir=Refresh
form1.precedent=Previous Day
form1.suivant=Next day
form1.agenda=Show the doctor's diary for the chosen doctor and the chosen day
form1.today=Today
# فرم ۲
form2.titre={0} {1} {2}'' diary on {3}
form2.titre_detail={0} {1} {2}'' diary on {3}
form2.creneauHoraire=Time Period
form2.client=Client
form2.accueil=Welcome Page
form2.supprimer=Delete
form2.reserver=Reserve
form2.valider=Submit
form2.annuler=Cancel
form2.erreur=Error
form2.emtyMessage=No Time periods in the database
form2.suppression.confirmation=Are-you sure ?
form2.suppression.message=Booking deletion
form2.supprimer.oui=Yes
form2.supprimer.non=No
form2.erreurClient=Unknown Client {0}
form2.erreurClient_detail=Unknown Client [{0}]
form2.erreurAction=Unauthorized action
form2.erreurAction_detail=Action non autoris\u00e9e
# خطا
erreur.titre=The following exceptions occurred
erreur.exceptions=Exceptions' chain
erreur.type=Exception type
erreur.message=Associated Message
erreur.accueil=Welcome
6.5. قالب صفحه [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:p="http://primefaces.org/ui"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:ui="http://java.sun.com/jsf/facelets">
<f:view locale="#{form.locale}">
<h:head>
<title>JSF</title>
<h:outputStylesheet library="css" name="styles.css"/>
</h:head>
<h:body style="background-image: url('#{request.contextPath}/resources/images/standard.jpg');">
<h:form id="formulaire">
<table style="width: 1200px">
<tr>
<td colspan="2" bgcolor="#ccccff">
<ui:include src="entete.xhtml"/>
</td>
</tr>
<tr>
<td style="width: 10px;" bgcolor="#ffcccc">
<ui:include src="menu.xhtml"/>
</td>
<td>
<p:outputPanel id="contenu">
<ui:insert name="contenu">
<h2>Contenu</h2>
</ui:insert>
</p:outputPanel>
</td>
</tr>
<tr bgcolor="#ffcc66">
<td colspan="2">
<ui:include src="basdepage.xhtml"/>
</td>
</tr>
</table>
</h:form>
</h:body>
</f:view>
</html>
تنها بخش متغیر این قالب، ناحیه پوششدهنده خطوط ۲۸ تا ۳۰ است. این ناحیه در داخل شناسه ':form:content' (خط ۲۷) قرار دارد. این نکته را مد نظر داشته باشید. فراخوانیهای AJAX که این ناحیه را بهروزرسانی میکنند، دارای ویژگی 'update=":form:content"' خواهند بود. علاوه بر این، فرم از خط ۱۵ آغاز میشود. بنابراین، قطعهٔ واردشده در خطوط ۲۸–۳۰ در این فرم قرار میگیرد.
ظاهر تولید شده توسط این قالب به شرح زیر است:
![]() |
بخش پویا صفحه در ناحیهٔ کادردار نشاندادهشده در بالا قرار خواهد گرفت.
6.6. صفحه [index.xhtml]
![]() |
پروژه همیشه همان صفحه را نمایش میدهد، صفحه زیر: [index.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:p="http://primefaces.org/ui"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:ui="http://java.sun.com/jsf/facelets">
<ui:composition template="layout.xhtml">
<ui:define name="contenu">
<ui:fragment rendered="#{form.form1Rendered}">
<ui:include src="form1.xhtml"/>
</ui:fragment>
<ui:fragment rendered="#{form.form2Rendered}">
<ui:include src="form2.xhtml"/>
</ui:fragment>
<ui:fragment rendered="#{form.erreurRendered}">
<ui:include src="erreur.xhtml"/>
</ui:fragment>
</ui:define>
</ui:composition>
</html>
- خطوط ۸–۹: این قطعه XHTML در ناحیه پویا از قالب [layout.xhtml] درج خواهد شد،
- این صفحه شامل سه زیرقطعه است:
- [form1.xhtml]، خطوط ۱۰–۱۲؛
- [form2.xhtml]، خطوط ۱۳–۱۵؛
- [erreur.xhtml]، خطوط 16–18.
حضور این قطعات در [index.xhtml] توسط مقادیر بولی در قالب [Form.java] مرتبط با صفحه کنترل میشود. بنابراین با تنظیم این مقادیر، صفحه رندر شده تغییر میکند.
قطعه [form1.xhtml] به صورت زیر رندر میشود:
قطعه [form2.xhtml] به صورت زیر رندر میشود:
![]() |
قطعه [erreur.xhtml] به صورت زیر رندر میشود:
![]() |
6.7. بیانهای پروژه
![]() |
کلاس در بسته [utils] قبلاً مورد بحث قرار گرفته است: کلاس [Messages] بینالمللیسازی پیامهای یک برنامه را تسهیل میکند. این کلاس در بخش 2.8.5.7 بررسی شد.
6.7.1. بیان Application
بیان [Application.java] یک بیان در محدوده application است. همانطور که قبلاً اشاره شد، این نوع بیان برای ذخیره دادههای فقط-خواندنی که برای همه کاربران برنامه در دسترس است، استفاده میشود. این بیان به شرح زیر است:
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 {
// لایه کسبوکار
@EJB
private IMetierLocal metier;
public Application() {
}
// گیرندهها
public IMetierLocal getMetier() {
return metier;
}
}
- خط ۸: به بین نام «application» داده شده است،
- خط ۹: این از نوع دامنهٔ application است،
- خطوط ۱۳–۱۴: یک مرجع به رابط محلی لایه [métier] توسط کانتینر EJB در سرور برنامه به آن تزریق خواهد شد. بیایید معماری برنامه را به یاد بیاوریم:
![]() |
برنامههای JSF، EJB و [Metier] در همان JVM (ماشین مجازی جاوای) اجرا خواهند شد. بنابراین، لایه [JSF] از رابط محلی EJB استفاده خواهد کرد. همین. بیان [Application] هیچ چیز دیگری ندارد. برای دسترسی به لایه [métier]، سایر بیانها آن را از این بیان بازیابی میکنند.
6.7.2. بیان [Erreur]
کلاس [Erreur] به شرح زیر است:
- package beans;
public class Erreur {public Erreur() {// fieldprivate String classe;private String message;// سازندهpublic Erreur(String classe, String message){this.setClasse(classe);this.message=message;// گیرنده و تنظیمکننده
- خط ۹: نام یک کلاس استثنا در صورتی که یک استثنا پرتاب شده باشد،
- خط ۱۰: یک پیام خطا.
6.7.3. بیان [Form]
کد آن به شرح زیر است:
package beans;
import java.io.IOException;
...
@Named(value = "form")
@SessionScoped
public class Form implements Serializable {
public Form() {
}
// بیان کاربردی
@Inject
private Application application;
// کش جلسه
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>();
private Map<String, Client> hIdentitesClients = new HashMap<String, Client>();
// مدل
private Long idMedecin;
private Date jour = new Date();
private Boolean form1Rendered = true;
private Boolean form2Rendered = false;
private Boolean erreurRendered = false;
private String form2Titre;
private AgendaMedecinJour agendaMedecinJour;
private Long idCreneauChoisi;
private Medecin medecin;
private Long idClient;
private CreneauMedecinJour creneauChoisi;
private List<Erreur> erreurs;
private Boolean erreur = false;
private String identiteClient;
private String action;
private String msgErreurClient;
private Boolean erreurClient;
private String msgErreurAction;
private Boolean erreurAction;
private String locale = "fr";
@PostConstruct
private void init() {
// کش کردن پزشکان و مشتریان
try {
medecins = application.getMetier().getAllMedecins();
clients = application.getMetier().getAllClients();
} catch (Throwable th) {
//ثبت خطا
prepareVueErreur(th);
return;
}
// فرهنگها
for (Medecin m : medecins) {
hMedecins.put(m.getId(), m);
}
for (Client c : clients) {
hClients.put(c.getId(), c);
hIdentitesClients.put(identite(c), c);
}
}
...
// نمایش نما
private void setForms(Boolean form1Rendered, Boolean form2Rendered, Boolean erreurRendered) {
this.form1Rendered = form1Rendered;
this.form2Rendered = form2Rendered;
this.erreurRendered = erreurRendered;
}
//آمادهسازی vueErreur
private void prepareVueErreur(Throwable th) {
//فهرست خطاها ایجاد میشود
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()));
}
//نمای خطا نمایش داده میشود
setForms(true, false, true);
}
// گیرنده و تنظیمکننده
...
}
- خطوط ۶–۸: کلاس [Form] یک بین به نام «form» با دامنه جلسه (session scope) است. توجه داشته باشید که بنابراین این کلاس باید قابل سریالسازی (serialisable) باشد،
- خطوط 14–15: بین «form» مرجعی به بین «application» نگه میدارد. این مرجع توسط کانتینر سرولت که برنامه در آن در حال اجرا است تزریق میشود (همانطور که توسط anotation @Inject نشان داده شده است).
- خطوط 17–44: قالب صفحات [form1.xhtml, form2.xhtml, erreur.xhtml]. نمایش این صفحات توسط متغیرهای بولی در خطوط 27–29 کنترل میشود. توجه داشته باشید که به طور پیشفرض، صفحه [form1.xhtml] رندر میشود (خط 27)،
- خطوط ۴۶–۴۷: متد init بلافاصله پس از instantiate شدن کلاس اجرا میشود (وجود annotation @PostConstruct)،
- خطوط ۵۰–۵۱: از لایه [métier] برای دریافت لیست پزشکان و مشتریان پرسوجو میشود،
- خطوط 59–65: اگر همه چیز به درستی پیش برود، فرهنگهای لغات پزشکان و مراجعین ساخته میشوند. این فرهنگها بر اساس شماره آنها نمایه میشوند. سپس صفحه [form1.xhtml] نمایش داده میشود (خط 27)،
- خط ۵۴: در صورت بروز خطا، قالب صفحه [erreur.xhtml] ایجاد میشود. این قالب، فهرست خطاهای موجود در خط ۳۶ است،
- خطوط ۷۸–۸۸: متد [prepareVueErreur] لیست خطاهایی را که باید نمایش داده شوند، ایجاد میکند. سپس صفحه [index.xhtml] قطعات [form1.xhtml] و [erreur.xhtml] (خط 87) را نمایش میدهد.
صفحه [erreur.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:p="http://primefaces.org/ui"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:ui="http://java.sun.com/jsf/facelets">
<body>
<p:panel header="#{msg['erreur.titre']}" closable="true" >
<hr/>
<p:dataTable value="#{form.erreurs}" var="erreur">
<f:facet name="header">
<h:outputText value="#{msg['erreur.exceptions']}"/>
</f:facet>
<p:column>
<f:facet name="header">
<h:outputText value="#{msg['erreur.type']}"/>
</f:facet>
<h:outputText value="#{erreur.classe}"/>
</p:column>
<p:column>
<f:facet name="header">
<h:outputText value="#{msg['erreur.message']}"/>
</f:facet>
<h:outputText value="#{erreur.message}"/>
</p:column>
</p:dataTable>
</p:panel>
</body>
</html>
این صفحه از تگ <p:dataTable> (خطوط ۱۲–۲۸) برای نمایش فهرست خطاها استفاده میکند. این کار صفحهای با خطا تولید میکند که مشابه شکل زیر است:
![]() |
اکنون مراحل مختلف چرخهٔ عمر برنامه را تعریف خواهیم کرد. برای هر اقدام کاربر، نماها و دستگیرکنندههای رویداد مرتبط را بررسی خواهیم کرد.
6.8. نمایش صفحهٔ اصلی
اگر همه چیز به درستی پیش برود، اولین صفحهای که نمایش داده میشود [form1.xhtml] است. این صفحه نمای زیر را ایجاد میکند:
![]() |
صفحه [form1.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:p="http://primefaces.org/ui"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:ui="http://java.sun.com/jsf/facelets">
<p:toolbar>
<p:toolbarGroup align="left">
...
</p:toolbarGroup>
<p:toolbarGroup align="right">
...
</p:toolbarGroup>
</p:toolbar>
</html>
ابزارکمیابی که در تصویر اسکرینشات برجسته شده، کامپوننت PrimeFaces با شناسه Toolbar است. این کامپوننت در خطوط ۸ تا ۱۴ تعریف شده است. این کامپوننت شامل دو گروه است که هر کدام با یک تگ <toolbarGroup> در خطوط ۹ تا ۱۱ و ۱۲ تا ۱۴ تعریف شدهاند. یکی از گروهها در سمت چپ نوار ابزار (خط 9) و دیگری در سمت راست (خط 12) قرار دارد.
بیایید برخی از اجزای گروه سمت چپ را بررسی کنیم:
<p:toolbar>
<p:toolbarGroup align="left">
<h:outputText value="#{msg['form1.medecin']}"/>
<p:selectOneMenu value="#{form.idMedecin}" effect="fade">
<f:selectItems value="#{form.medecins}" var="medecin" itemLabel="#{medecin.titre} #{medecin.prenom} #{medecin.nom}" itemValue="#{medecin.id}"/>
<p:ajax update=":formulaire:contenu" listener="#{form.hideAgenda}" />
</p:selectOneMenu>
<p:separator/>
<h:outputText value="#{msg['form1.jour']}"/>
<p:calendar id="calendrier" value="#{form.jour}" readOnlyInputText="true">
<p:ajax event="dateSelect" listener="#{form.hideAgenda}" update=":formulaire:contenu"/>
</p:calendar>
<p:separator/>
<p:commandButton id="resa-agenda" icon="ui-icon-check" actionListener="#{form.getAgenda}" update=":formulaire:contenu"/>
<p:tooltip for="resa-agenda" value="#{msg['form1.agenda']}"/>
...
</p:toolbarGroup>
...
- خطوط ۴–۷: لیست کشویی پزشکان، که یک افکت به آن اضافه شده است (effect="fade")،
- خط ۶: یک رفتار AJAX. وقتی در فهرست کشویی تغییری رخ میدهد، متد [Form] اجرا میشود.hideAgenda (listener="#{form.hideAgenda}") اجرا خواهد شد و ناحیه پویا :form:content (update=":formulaire:contenu") بهروزرسانی خواهد شد،
- خط ۸: شامل یک جداکننده در نوار ابزار است،
- خطوط ۱۰–۱۲: فیلد ورودی تاریخ. در اینجا از تقویم PrimeFaces استفاده میشود. فیلد ورودی فقط-خواندنی است (readOnlyInputText="true")،
- خط ۱۱: یک رفتار AJAX. هنگامی که تاریخ تغییر میکند، متد [Form].hideAgenda اجرا میشود و فیلد پویا :form:content بهروزرسانی میشود،
- خط ۱۴: یک دکمه. کلیک بر روی آن باعث فراخوانی AJAX میشود که متد [Form].getAgenda (); را فراخوانی میکند. سپس قالب اصلاح شده و از پاسخ سرور برای بهروزرسانی ناحیه پویا :form:content استفاده خواهد شد،
- خط ۱۵: تگ <tooltip> امکان اختصاص یک ابزارنویس (tooltip) به یک مؤلفه را فراهم میکند. شناسهٔ مؤلفه با ویژگی «for» در ابزارنویس مشخص میشود. در اینجا، «for="resa-agenda"» به دکمهٔ موجود در خط ۱۴ اشاره دارد:
![]() | ![]() |
این صفحه با استفاده از قالب زیر ایجاد شده است:
@Named(value = "form")
@SessionScoped
public class Form implements Serializable {
public Form() {
}
// کش جلسه
private List<Medecin> medecins;
private List<Client> clients;
// مدل
private Long idMedecin;
private Date jour = new Date();
// فهرست پزشکان
public List<Medecin> getMedecins() {
return medecins;
}
// فهرست مشتریان
public List<Client> getClients() {
return clients;
}
// تقویم
public void getAgenda() {
...
}
- میدان در خط ۱۲ دسترسی خواندن و نوشتن به مقدار موجود در لیست در خط ۴ صفحه را فراهم میکند. هنگامی که صفحه برای اولین بار نمایش داده میشود، مقدار انتخابشده در لیست کشویی را تعیین میکند. در نمایش اولیه، idMedecin برابر با null است، بنابراین اولین پزشک انتخاب خواهد شد،
- متد در خطوط 16–18 آیتمهای لیست کشویی پزشکان (خط 5 صفحه) را تولید میکند. هر گزینه تولید شده دارای برچسب (itemLabel) شامل عنوان، نام خانوادگی و نام پزشک، و دارای مقدار (itemValue) شامل شناسه پزشک خواهد بود،
- میدان در خط ۱۳ دسترسی خواندن/نوشتن را به میدان ورودی در خط ۱۰ صفحه فراهم میکند. بنابراین، در نمایش اولیه، تاریخ فعلی نمایش داده میشود،
- خطوط ۲۶–۲۸: متد getAgenda کلیک روی دکمه [Agenda] در خط ۱۴ صفحه را مدیریت میکند. این متد تقریباً با آنچه در نسخه JSF بود یکسان است:
// بیان برنامه
@Inject
private Application application;
// کش جلسه
private List<Medecin> medecins;
private Map<Long, Medecin> hMedecins = new HashMap<Long, Medecin>();
// قالب
private Long idMedecin;
private Date jour = new Date();
private Boolean form1Rendered = true;
private Boolean form2Rendered = false;
private Boolean erreurRendered = false;
private AgendaMedecinJour agendaMedecinJour;
private Long idCreneauChoisi;
private CreneauMedecinJour creneauChoisi;
private List<Erreur> erreurs;
private Boolean erreur = false;
public void getAgenda() {
try {
// بازیابی پزشک
medecin = hMedecins.get(idMedecin);
// دفترچه یادداشت پزشک برای یک روز معین
agendaMedecinJour = application.getMetier().getAgendaMedecinJour(medecin, jour);
//نمایش فرم ۲
setForms(true, true, false);
} catch (Throwable th) {
// نمایش خطا
prepareVueErreur(th);
}
// در حال حاضر هیچ بازه زمانی انتخاب نشده است
creneauChoisi = null;
}
ما در مورد این کد نظری نخواهیم داد. این کار قبلاً انجام شده است.
6.9. مشاهده دفترچه یادداشت پزشک
6.9.1. نمای کلی دفترچه یادداشت
این مورد استفاده به شرح زیر است:
![]() |
- در [1]، یک پزشک ([1]) و یک روز ([2]) را انتخاب میکنید، سپس دفترچه یادداشت پزشک را برای روز انتخابشده فراخوانی میکنید ([3])،
- در [4]، این زیر نوار ابزار ظاهر میشود.
کد صفحه [form2.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:p="http://primefaces.org/ui"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:c="http://java.sun.com/jsp/jstl/core">
<body>
<!-- منوی زمینه -->
<p:contextMenu for="agenda">
...
</p:contextMenu>
<!-- تقویم -->
<p:dataTable id="agenda" value="#{form.myDataModel}" var="creneauMedecinJour" style="width: 800px"
selectionMode="single" selection="#{form.creneauChoisi}" emptyMessage="#{msg['form2.emtyMessage']}">
<!--ستون بازه زمانی -->
<p:column style="width: 100px">
...
</p:column>
<!-- ستون مشتریان -->
<p:column style="width: 300px">
...
</p:column>
</p:dataTable>
<!--تأیید حذف RV -->
<p:confirmDialog id="confirmDialog" message="#{msg['form2.suppression.confirmation']}"
header="#{msg['form2.suppression.message']}" severity="alert" widgetVar="confirmation">
...
</p:confirmDialog>
<!-- پیام خطا -->
<p:dialog header="#{msg['form2.erreur']}" widgetVar="dlgErreur" height="100" >
...
</p:dialog>
<!-- پردازش پاسخ سرور -->
<script type="text/javascript">
...
}
</script>
</body>
</html>
- ردههای ۱۶–۲۶: عنصر اصلی صفحه جدول <dataTable> است که برنامه زمانی ملاقاتهای دکتر را نمایش میدهد،
![]() |
- خطوط ۱۲–۱۴: ما از منوی زمینه برای افزودن یا حذف یک قرار ملاقات استفاده خواهیم کرد:
![]() |
- خطوط ۲۹–۳۲: هنگامی که کاربر بخواهد یک قرار ملاقات را حذف کند، یک کادر تأیید نمایش داده خواهد شد:
![]() |
- خطوط ۳۵–۳۷: یک کادر محاورهای برای گزارش خطا استفاده خواهد شد:
![]() |
- خطوط ۴۰–۴۳: ما باید مقداری جاوااسکریپت اضافه کنیم.
6.9.2. جدول قرار ملاقات
در اینجا به مدل جدول داده مطابق با بحث در بخش 5.15، صفحه 327 میپردازیم.
بیایید عنصر اصلی صفحه، جدول نمایش تقویم را بررسی کنیم:
<p:dataTable id="agenda" value="#{form.myDataModel}" var="creneauMedecinJour" style="width: 800px"
selectionMode="single" selection="#{form.creneauChoisi}" emptyMessage="#{msg['form2.emtyMessage']}">
<!-- ستون جدول زمانی -->
<p:column style="width: 100px">
...
</p:column>
<!-- ستون مشتری -->
<p:column style="width: 300px">
...
</p:column>
</p:dataTable>
نتیجه به این صورت است:
![]() |
این یک جدول دو ستونی است (ردههای ۴–۶ و ۸–۱۰) که از منبع [Form].getMyDataModel() (value="#{form.myDataModel}") پر شده است. در هر زمان تنها یک سطر را میتوان انتخاب کرد (selectionMode="single"). هر بار که POST فراخوانی میشود، یک مرجع به عنصر انتخابشده به [Form].creneauChoisi (selection="#{form.creneauChoisi}") اختصاص داده میشود.
به یاد داشته باشید که متد getAgenda فیلد زیر را در مدل مقداردهی اولیه کرد:
// قالب
private AgendaMedecinJour agendaMedecinJour;
قالب جدول با فراخوانی متد زیر به دست میآید: [Form].getMyDataModel (ویژگی 'value' تگ <dataTable>):
//قالب برای dataTable
public MyDataModel getMyDataModel() {
return new MyDataModel(agendaMedecinJour.getCreneauxMedecinJour());
}
بیایید کلاس [MyDataModel] را که بهعنوان قالب برای تگ <p:dataTable> عمل میکند، بررسی کنیم:
package beans;
import javax.faces.model.ArrayDataModel;
import org.primefaces.model.SelectableDataModel;
import rdvmedecins.metier.entites.CreneauMedecinJour;
public class MyDataModel extends ArrayDataModel<CreneauMedecinJour> implements SelectableDataModel<CreneauMedecinJour> {
// تولیدکنندگان
public MyDataModel() {
}
public MyDataModel(CreneauMedecinJour[] creneauxMedecinJour) {
super(creneauxMedecinJour);
}
@Override
public Object getRowKey(CreneauMedecinJour creneauMedecinJour) {
return creneauMedecinJour.getCreneau().getId();
}
@Override
public CreneauMedecinJour getRowData(String rowKey) {
// فهرست بازههای زمانی
CreneauMedecinJour[] creneauxMedecinJour = (CreneauMedecinJour[]) getWrappedData();
// کلید یک عدد صحیح بلند است
long key = Long.parseLong(rowKey);
// جستجو برای اسلات انتخابشده
for (CreneauMedecinJour creneauMedecinJour : creneauxMedecinJour) {
if (creneauMedecinJour.getCreneau().getId().longValue() == key) {
return creneauMedecinJour;
}
}
// هیچ چیز
return null;
}
}
- خط ۷: کلاس [MyDataModel] قالب برای تگ <p:dataTable> است. هدف این کلاس، مرتبط کردن عنصر rowkey که ارسال شده است با عنصر مرتبط با آن سطر است،
- خط ۷: این کلاس رابط [SelectableDataModel] را از طریق کلاس [ArrayDataModel] پیادهسازی میکند. این بدان معناست که پارامتر سازنده یک آرایه است. این آرایه است که تگ <dataTable> را پر میکند. در اینجا، هر سطر از آرایه با یک عنصر از نوع [CreneauMedecinJour] مرتبط خواهد شد،
- خطوط ۱۳–۱۵: سازنده پارامتر خود را به کلاس والد خود پاس میدهد،
- خطوط ۱۸–۲۰: هر سطر از آرایه با یک بازه زمانی مطابقت دارد و با شناسه بازه زمانی (خط ۱۹) شناسایی میشود. این شناسه است که به سرور ارسال خواهد شد،
- خط ۲۳: کدی که هنگام ارسال شناسه بازه زمانی در سرور اجرا میشود. هدف این متد بازگرداندن مرجع شیء [CreneauMedecinJour] مرتبط با این شناسه است. این مرجع به مقدار ویژگی «selection» تگ <dataTable> اختصاص داده خواهد شد:
<p:dataTable id="agenda" value="#{form.myDataModel}" var="creneauMedecinJour" style="width: 800px"
selectionMode="single" selection="#{form.creneauChoisi}" emptyMessage="#{msg['form2.emtyMessage']}">
بنابراین فیلد [Form].creneauChoisi حاوی مرجع شیء [CreneauMedecinJour] خواهد بود که میخواهید اضافه یا حذف کنید.
6.9.3. ستون بازه زمانی
![]() |
ستون بازه زمانی با استفاده از کد زیر تولید میشود:
<p:dataTable id="agenda" value="#{form.myDataModel}" var="creneauMedecinJour" style="width: 800px"
selectionMode="single" selection="#{form.creneauChoisi}" emptyMessage="#{msg['form2.emtyMessage']}">
<!--ستون بازه زمانی -->
<p:column style="width: 100px">
<f:facet name="header">
<h:outputText value="#{msg['form2.creneauHoraire']}"/>
</f:facet>
<div align="center">
<h:outputFormat value="{0,number,#00}:{1,number,#00} - {2,number,#00}:{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>
</div>
</p:column>
<!-- ستون مشتری -->
<p:column style="width: 300px">
...
</p:column>
</p:dataTable>
- خطوط ۵–۷: سربرگ ستون،
- خطوط ۸–۱۵: عنصر جاری ستون. به خط ۹ توجه کنید، جایی که تگ <h:outputFormat> برای قالببندی عناصری که باید نمایش داده شوند استفاده میشود. پارامتر «value» رشتهای را که باید نمایش داده شود، مشخص میکند. نشانه {i,type,format} به شماره پارامتر i، نوع آن پارامتر و فرمت آن اشاره دارد. در اینجا، چهار پارامتر با شمارههای 0 تا 3 وجود دارد؛ نوع آنها عددی است و با دو رقم نمایش داده خواهند شد،
- خطوط ۱۰–۱۳: چهار پارامتر مورد نیاز تگ <h:outputFormat>.
6.9.4. ستون مشتریان
![]() |
ستون مشتری با استفاده از کد زیر تولید میشود:
<!-- تقویم -->
<p:dataTable id="agenda" value="#{form.myDataModel}" var="creneauMedecinJour" style="width: 800px"
selectionMode="single" selection="#{form.creneauChoisi}" emptyMessage="#{msg['form2.emtyMessage']}">
<!--ستون زمان -->
...
<!-- ستون مشتریان -->
<p:column style="width: 300px">
<f:facet name="header">
<h:outputText value="#{msg['form2.client']}"/>
</f:facet>
<ui:fragment rendered="#{creneauMedecinJour.rv!=null}">
<h:outputText value="#{creneauMedecinJour.rv.client.titre} #{creneauMedecinJour.rv.client.prenom} #{creneauMedecinJour.rv.client.nom}" />
</ui:fragment>
<ui:fragment rendered="#{creneauMedecinJour.rv==null and form.creneauChoisi!=null and form.creneauChoisi.creneau.id==creneauMedecinJour.creneau.id}">
...
</ui:fragment>
</p:column>
</p:dataTable>
- خطوط ۸–۱۰: سربرگ ستون،
- خطوط ۱۱–۱۳: عنصر فعلی زمانی که برای آن بازه زمانی قرار ملاقاتی وجود دارد. در این حالت، عنوان، نام و نام خانوادگی مشتری که برای او این قرار ملاقات تنظیم شده است نمایش داده میشود،
- خطوط 14–16: بخش دیگری که بعداً به آن باز خواهیم گشت.
6.10. حذف یک قرار ملاقات
حذف یک قرار ملاقات شامل توالی زیر است:
![]() |
![]() |
نمای تحت تأثیر این اقدام به شرح زیر است:
<!-- منوی زمینه -->
<p:contextMenu for="agenda">
...
<p:menuitem value="#{msg['form2.supprimer']}" onclick="confirmation.show()"/>
</p:contextMenu>
<!-- تقویم -->
<p:dataTable id="agenda" value="#{form.myDataModel}" var="creneauMedecinJour" style="width: 800px"
selectionMode="single" selection="#{form.creneauChoisi}" emptyMessage="#{msg['form2.emtyMessage']}">
...
</p:dataTable>
<!--تأیید حذف RV -->
<p:confirmDialog id="confirmDialog" message="#{msg['form2.suppression.confirmation']}"
header="#{msg['form2.suppression.message']}" severity="alert" widgetVar="confirmation">
<p:commandButton value="#{msg['form2.supprimer.oui']}" update=":formulaire:contenu" action="#{form.action}"
oncomplete="handleRequest(xhr, status, args); confirmation.hide()">
<f:setPropertyActionListener value="supprimer" target="#{form.action}"/>
</p:commandButton>
<p:commandButton value="#{msg['form2.supprimer.non']}" onclick="confirmation.hide()" type="button" />
</p:confirmDialog>
- ردههای ۲–۵: منوی زمینه متصل به جدول داده (برای ویژگی). این منو دو گزینه دارد [1]:
![]() |
- خط ۴: گزینه [Supprimer] نمایش کادر محاورهای [2] را در خطوط ۱۳–۲۰ راهاندازی میکند،
- خط ۱۵: کلیک بر روی [Oui] باعث اجرای [Form.action] میشود که قرار ملاقات را حذف میکند. به طور معمول، منوی زمینه نباید گزینه [Supprimer] را در صورتی که آیتم انتخابشده قرار ملاقاتی نداشته باشد، و همچنین گزینه [Réserver] را در صورتی که آیتم انتخابشده قرار ملاقاتی داشته باشد، ارائه دهد. ما نتوانستیم منوی زمینه را تا این حد هوشمند بسازیم. این منو برای اولین آیتم انتخابشده کار میکند، اما سپس متوجه شدیم که منوی زمینه، پیکربندی تنظیمشده برای آن انتخاب اول را حفظ میکند. در این صورت، منو نادرست میشود. بنابراین، ما هر دو گزینه را حفظ کردیم و تصمیم گرفتیم در صورتی که کاربر آیتمی را بدون قرار ملاقات حذف کند، به او بازخورد بدهیم،
- خط ۱۶: ویژگی oncomplete، که به شما امکان میدهد کد جاوااسکریپتی را تعریف کنید تا پس از اجرای فراخوانی AJAX اجرا شود. کد در اینجا به شرح زیر خواهد بود:
<!-- پیام خطا -->
<p:dialog header="#{msg['form2.erreur']}" widgetVar="dlgErreur" height="100" >
<h:outputText value="#{form.msgErreur}" />
</p:dialog>
<!-- پردازش پاسخ سرور -->
<script type="text/javascript">
function handleRequest(xhr, status, args) {
// خطا؟
if(args.erreur) {
dlgErreur.show();
}
}
</script>
- خط ۱۰: کد جاوا اسکریپت بررسی میکند که آیا دیکشنری args دارای ویژگی erreur است یا خیر. در صورت مثبت بودن، پنجرهٔ گفتگوی خط ۲ (ویژگی widgetVar) را نمایش میدهد. این کادر محاورهای قالب [Form].msgErreur را نمایش میدهد.
بیایید نگاهی به کدی که برای مدیریت حذف یک قرار ملاقات اجرا میشود، بیندازیم:
<p:confirmDialog ...>
<p:commandButton value="#{msg['form2.supprimer.oui']}" update=":formulaire:contenu" action="#{form.action}"
...>
<f:setPropertyActionListener value="supprimer" target="#{form.action}"/>
</p:commandButton>
...
</p:confirmDialog>
- خط ۲: متد [Form].action اجرا خواهد شد،
- خط ۴: قبل از اجرای آن، فیلد action روی مقدار 'supprimer' تنظیم شده است.
متد [action] به شرح زیر است:
// اقدام روی RV
public void action() {
// بسته به اقدام مورد نظر
if (action.equals("supprimer")) {
supprimer();
}
...
}
public void supprimer() {
// آیا باید کاری انجام دهم؟
Rv rv = creneauChoisi.getRv();
if (rv == null) {
signalerActionIncorrecte();
return;
}
try {
// حذف یک قرار ملاقات
application.getMetier().supprimerRv(rv);
// بهروزرسانی دفترچه یادداشت
agendaMedecinJour = application.getMetier().getAgendaMedecinJour(medecin, jour);
//نمایش فرم 2
setForms(true, true, false);
} catch (Throwable th) {
// نمایش خطا
prepareVueErreur(th);
}
// حذف بازه زمانی انتخابشده
creneauChoisi = null;
}
- خط ۴: اگر عمل 'حذف' باشد، متد [supprimer] اجرا میشود،
- خط ۱۲: قرار ملاقات برای بازه زمانی انتخابشده بازیابی میشود. توجه داشته باشید که [creneauChoisi] توسط مرجع عنصر انتخابشده [CreneauMedecinJour] مقداردهی اولیه شده است؛
- اگر این قرار ملاقات وجود داشته باشد، حذف میشود (خط ۱۹)، تقویم مجدداً تولید میشود (خط ۲۱) و سپس دوباره نمایش داده میشود (خط ۲۳)،
- اگر حذف با موفقیت انجام نشود، صفحه خطا نمایش داده میشود (خط ۲۶)،
- اگر عنصر انتخابشده هیچ قرار ملاقاتی نداشته باشد (خط ۱۳)، این نشان میدهد که کاربر روی یک بازه زمانی کلیک کرده است که هیچ قرار ملاقاتی ندارد. این خطا گزارش میشود:
![]() |
متد [signalerActionIncorrecte] به شرح زیر است:
// گزارش یک اقدام نادرست
private void signalerActionIncorrecte() {
// بازنشانی بازه زمانی انتخابشده
creneauChoisi = null;
// خطا
msgErreur = Messages.getMessage(null, "form2.erreurAction", null).getSummary();
RequestContext.getCurrentInstance().addCallbackParam("erreur", true);
}
- خط ۴: انتخاب حذف میشود،
- خط ۶: تولید یک پیام خطای بینالمللیسازیشده،
- خط ۷: ویژگی ('error', true) را به دیکشنری args فراخوانی AJAX اضافه کنید.
بیایید به کد مربوط به XHTML که با دکمه [Oui] مرتبط است، بازگردیم:
<p:commandButton value="#{msg['form2.supprimer.oui']}" update=":formulaire:contenu" action="#{form.action}"
oncomplete="handleRequest(xhr, status, args); confirmation.hide()">
- خط ۲: پس از اجرای متد [Form].action، متد جاوااسکریپتی handleRequest اجرا میشود:
<!-- پیام خطا -->
<p:dialog header="#{msg['form2.erreur']}" widgetVar="dlgErreur" height="100" >
<h:outputText value="#{form.msgErreur}" />
</p:dialog>
<!-- پردازش پاسخ سرور -->
<script type="text/javascript">
function handleRequest(xhr, status, args) {
// خطا؟
if(args.erreur) {
dlgErreur.show();
}
}
</script>
- خط ۱۰: سیستم بررسی میکند که آیا دیکشنری args دارای ویژگیای با نام 'erreur' است یا خیر. در صورت مثبت بودن، کادر محاورهای از خط ۲ نمایش داده میشود،
- خط ۳: پیام خطای تولیدشده توسط قالب را نمایش میدهد.
6.11. رزرو وقت ملاقات
رزرو وقت ملاقات به این ترتیب انجام میشود:
![]() |
نما درگیر در این اقدام به شرح زیر است:
<!-- منوی زمینه -->
<p:contextMenu for="agenda">
<p:menuitem value="#{msg['form2.reserver']}" update=":formulaire:contenu" action="#{form.action}" oncomplete="handleRequest(xhr, status, args)">
<f:setPropertyActionListener value="reserver" target="#{form.action}"/>
</p:menuitem>
...
</p:contextMenu>
<!-- تقویم -->
<p:dataTable id="agenda" value="#{form.myDataModel}" var="creneauMedecinJour" style="width: 800px"
selectionMode="single" selection="#{form.creneauChoisi}" emptyMessage="#{msg['form2.emtyMessage']}">
<!-- ستون جدول زمانی -->
<p:column style="width: 100px">
...
</p:column>
<!-- ستون مشتری -->
<p:column style="width: 300px">
<f:facet name="header">
<h:outputText value="#{msg['form2.client']}"/>
</f:facet>
...
<ui:fragment rendered="#{creneauMedecinJour.rv==null and form.creneauChoisi!=null and form.creneauChoisi.creneau.id==creneauMedecinJour.creneau.id}">
<p:autoComplete completeMethod="#{form.completeClients}" value="#{form.identiteClient}" size="30"/>
<p:spacer width="50px"/>
<p:commandLink action="#{form.action()}" value="#{msg['form2.valider']}" update=":formulaire:contenu" oncomplete="handleRequest(xhr, status, args)">
<f:setPropertyActionListener value="valider" target="#{form.action}"/>
</p:commandLink>
<p:spacer width="50px"/>
<p:commandLink action="#{form.action()}" value="#{msg['form2.annuler']}" update=":formulaire:contenu">
<f:setPropertyActionListener value="annuler" target="#{form.action}"/>
</p:commandLink>
</ui:fragment>
</p:column>
</p:dataTable>
...
- سطور 21–31: موارد زیر را نمایش دهید:
![]() |
- خط ۲۱: این در صورتی نمایش داده میشود که قرار ملاقاتی وجود نداشته باشد، یک انتخاب انجام شده باشد و شناسه بازه زمانی انتخابشده با شناسه ردیف فعلی در جدول مطابقت داشته باشد. اگر این شرط لحاظ نشود، قطعه برای تمام بازههای زمانی نمایش داده میشود،
- خط ۲۲: فیلد ورودی یک فیلد ورودی با کمک خواهد بود. در اینجا فرض میکنیم که ممکن است تعداد زیادی مشتری وجود داشته باشد،
- خطوط ۲۴–۲۶: لینک [Valider]،
- خطوط ۲۸–۳۰: لینک [Annuler].
میدان ورودی کمکی با کد زیر تولید میشود:
<p:autoComplete completeMethod="#{form.completeClients}" value="#{form.identiteClient}" size="30"/>
متد [Form].completeClients مسئول ارائه پیشنهادها به کاربر بر اساس حروف تایپشده در فیلد ورودی است:
![]() |
پیشنهادها در قالب [Nom prénom titre] هستند. کد روش [Form].completeClients به شرح زیر است:
// روش متن تکمیل خودکار
public List<String> completeClients(String query) {
List<String> identites = new ArrayList<String>();
// جستجوی مشتریان مطابقتدار
for (Client c : clients) {
String identite = identite(c);
if (identite.toLowerCase().startsWith(query.toLowerCase())) {
identites.add(identite);
}
}
return identites;
}
private String identite(Client c) {
return c.getNom() + " " + c.getPrenom() + " " + c.getTitre();
}
- خط ۲: query رشتهای است که توسط کاربر وارد شده،
- خط ۳: لیست پیشنهادها. در ابتدا یک لیست خالی است،
- خطوط ۵–۱۰: شناسههای [Nom prénom titre] مشتریان تولید میشوند. اگر شناسهای با query (خط ۷) شروع شود، در لیست پیشنهادها (خط ۸) نگه داشته میشود.
6.12. تأیید قرار ملاقات
تأیید قرار ملاقات شامل توالی زیر است:
![]() |
کد لینک [Valider] به شرح زیر است:
<p:commandLink action="#{form.action()}" value="#{msg['form2.valider']}" update=":formulaire:contenu" oncomplete="handleRequest(xhr, status, args)">
<f:setPropertyActionListener value="valider" target="#{form.action}"/>
</p:commandLink>
بنابراین متد [Form].action() این رویداد را مدیریت خواهد کرد. در همین حال، مدل [Form].action مقدار رشته 'validate' را دریافت خواهد کرد. کد به شرح زیر است:
// بیان برنامه
@Inject
private Application application;
// کش جلسه
...
private Map<String, Client> hIdentitesClients = new HashMap<String, Client>();
// مدل
private Date jour = new Date();
private Boolean form1Rendered = true;
private Boolean form2Rendered = false;
private Boolean erreurRendered = false;
private AgendaMedecinJour agendaMedecinJour;
private CreneauMedecinJour creneauChoisi;
private List<Erreur> erreurs;
private Boolean erreur = false;
private String identiteClient;
private String action;
private String msgErreur;
@PostConstruct
private void init() {
...
for (Client c : clients) {
hClients.put(c.getId(), c);
hIdentitesClients.put(identite(c), c);
}
}
//اقدام بر روی RV
public void action() {
//بسته به اقدام مورد نظر
...
if (action.equals("valider")) {
validerResa();
}
}
//تأیید قرار ملاقات
public void validerResa() {
//تأیید رزرو
try {
//آیا مشتری وجود دارد؟
Boolean erreur = !hIdentitesClients.containsKey(identiteClient);
if (erreur) {
msgErreur = Messages.getMessage(null, "form2.erreurClient", new Object[]{identiteClient}).getSummary();
RequestContext.getCurrentInstance().addCallbackParam("erreur", true);
return;
}
// افزودن قرار ملاقات
application.getMetier().ajouterRv(jour, creneauChoisi.getCreneau(), hIdentitesClients.get(identiteClient));
// بهروزرسانی تقویم
agendaMedecinJour = application.getMetier().getAgendaMedecinJour(medecin, jour);
//نمایش فرم 2
setForms(true, true, false);
} catch (Throwable th) {
// بررسی خطاها
prepareVueErreur(th);
}
// حذف بازه زمانی انتخابشده
creneauChoisi = null;
//پاک کردن مشتری
identiteClient = null;
}
- خطوط ۳۳–۳۵: به دلیل مقدار فیلد action، متد [validerResa] اجرا خواهد شد،
- خط ۴۳: ابتدا بررسی میکنیم که آیا مشتری وجود دارد یا خیر. این به آن دلیل است که در فیلد ورودی کمکی، کاربر ممکن است بدون استفاده از پیشنهادهای ارائهشده، دادهها را بهصورت دستی وارد کرده باشد. ورودی کمکی با مدل [Form].identiteClient مرتبط است. بنابراین ما بررسی میکنیم که آیا این شناسه در دیکشنری identitesClients وجود دارد که هنگام نمونهسازی مدل (خط ۲۰) ایجاد شده است. این دیکشنری خود مشتری را با یک شناسه مشتری از نوع [Nom prénom titre] مرتبط میسازد (خط ۲۵)،
- خط ۴۴: اگر کلاینت وجود نداشته باشد، یک خطا به مرورگر بازگردانده میشود،
- خط ۴۵: یک پیام خطای بینالمللیشده،
- خط ۴۶: ویژگی ('error', true) به دیکشنری args فراخوانی AJAX اضافه میشود. فراخوانی AJAX به صورت زیر تعریف شده است:
<p:commandLink action="#{form.action()}" value="#{msg['form2.valider']}" update=":formulaire:contenu" oncomplete="handleRequest(xhr, status, args)">
<f:setPropertyActionListener value="valider" target="#{form.action}"/>
</p:commandLink>
در خط ۳ بالا، میبینیم که لینک [Valider] دارای ویژگی oncomplete است. این ویژگی باعث نمایش پیام خطا با استفاده از تکنیکی میشود که قبلاً با آن مواجه شدهایم.
- خط ۵۰: به لایه [métier] دستور داده میشود تا یک قرار ملاقات برای روز انتخابشده (jour) اضافه کند، فاصله زمانی انتخابشده (creneauChoisi.getCreneau()) و بیمار انتخابشده (hIdentitesClients.get(identiteClient))،
- خط ۵۲: به لایه [métier] دستور داده میشود که دفترچه یادداشت پزشک را تازه کند. قرار ملاقات اضافه شده به همراه هرگونه تغییری که ممکن است سایر کاربران برنامه ایجاد کرده باشند، نمایش داده خواهد شد،
- خط ۵۴: تقویم [form2.xhtml] مجدداً نمایش داده میشود،
- خط ۵۷: در صورت بروز خطا، صفحه خطا نمایش داده میشود.
6.13. لغو قرار ملاقات
این با توالی زیر مطابقت دارد:
![]() |
دکمه [Annuler] در صفحه [form2.xhtml] به شرح زیر است:
<p:commandLink action="#{form.action()}" value="#{msg['form2.annuler']}" update=":formulaire:contenu">
<f:setPropertyActionListener value="annuler" target="#{form.action}"/>
</p:commandLink>
بنابراین متد [Form].action فراخوانی میشود:
// اقدام روی RV
public void action() {
// بسته به اقدام مورد نظر
...
if (action.equals("annuler")) {
annulerRv();
}
}
//لغو قرار ملاقات
public void annulerRv() {
// نمایش فرم2
setForms(true, true, false);
// حذف بازه زمانی انتخابشده
creneauChoisi = null;
//پاک کردن جزئیات مشتری
identiteClient = null;
}
6.14. پیمایش تقویم
ابزارکال به شما امکان میدهد در تقویم پیمایش کنید:
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
اگرچه در اسکرینشاتهای بالا نشان داده نشده است، تقویم با قرارهای روز تازه انتخابشده بهروزرسانی میشود.
برچسبهای سه دکمه مرتبط در [form1.xhtml] به شرح زیر است:
<p:toolbar>
<p:toolbarGroup align="left">
...
<h:outputText value="#{msg['form1.jour']}"/>
<p:calendar id="calendrier" value="#{form.jour}" readOnlyInputText="true">
<p:ajax event="dateSelect" listener="#{form.hideAgenda}" update=":formulaire:contenu"/>
</p:calendar>
<p:separator/>
<p:commandButton id="resa-agenda" icon="ui-icon-check" actionListener="#{form.getAgenda}" update=":formulaire:contenu"/>
<p:tooltip for="resa-agenda" value="#{msg['form1.agenda']}"/>
<p:commandButton id="resa-precedent" icon="ui-icon-seek-prev" actionListener="#{form.getPreviousAgenda}" update=":formulaire:contenu"/>
<p:tooltip for="resa-precedent" value="#{msg['form1.precedent']}"/>
<p:commandButton id="resa-suivant" icon="ui-icon-seek-next" actionListener="#{form.getNextAgenda}" update=":formulaire:contenu"/>
<p:tooltip for="resa-suivant" value="#{msg['form1.suivant']}"/>
<p:commandButton id="resa-today" icon="ui-icon-home" actionListener="#{form.today}" update=":formulaire:contenu"/>
<p:tooltip for="resa-today" value="#{msg['form1.today']}"/>
</p:toolbarGroup>
<p:toolbarGroup align="right">
...
</p:toolbarGroup>
</p:toolbar>
متدهای [Form].getPreviousAgenda، [Form].getNextAgenda، [Form].today به شرح زیر هستند:
private Date jour = new Date();
public void getPreviousAgenda() {
// رفتن به روز قبل
Calendar cal = Calendar.getInstance();
cal.setTime(jour);
cal.add(Calendar.DAY_OF_YEAR, -1);
jour = cal.getTime();
// تقویم
if (form2Rendered) {
getAgenda();
}
}
public void getNextAgenda() {
// حرکت به روز بعد
Calendar cal = Calendar.getInstance();
cal.setTime(jour);
cal.add(Calendar.DAY_OF_YEAR, 1);
jour = cal.getTime();
// تقویم
if (form2Rendered) {
getAgenda();
}
}
// تقویم امروز
public void today() {
jour = new Date();
// تقویم
if (form2Rendered) {
getAgenda();
}
}
- خط ۱: روزی که تقویم نمایش داده میشود،
- خط ۵: از یک تقویم استفاده میشود،
- خط ۶: که با روز جاری در دفترچه یادداشت مقداردهی اولیه میشود،
- خط ۷: یک روز از تقویم کم میکنیم،
- خط ۸: و آن را به تاریخ نمایش تقویم بازنشانی میکنیم،
- خط ۱۱: اگر تقویم در حال حاضر قابل مشاهده باشد، دوباره نمایش داده میشود. این به این دلیل است که کاربر میتواند حتی زمانی که تقویم قابل مشاهده نیست، از نوار ابزار استفاده کند.
سایر روشها مشابه هستند.
6.15. تغییر زبان نمایش
زبان را میتوان با استفاده از دکمه منو در نوار ابزار تغییر داد:
![]() |
![]() |
تگهای دکمه منو به شرح زیر است:
<p:toolbar>
<p:toolbarGroup align="left">
...
</p:toolbarGroup>
<p:toolbarGroup align="right">
<p:menuButton value="#{msg['form1.options']}">
<p:menuitem id="menuitem-francais" value="#{msg['form1.francais']}" actionListener="#{form.setFrenchLocale}" update=":formulaire"/>
<p:menuitem id="menuitem-anglais" value="#{msg['form1.anglais']}" actionListener="#{form.setEnglishLocale}" update=":formulaire"/>
<p:menuitem id="menuitem-rafraichir" value="#{msg['form1.rafraichir']}" actionListener="#{form.refresh}" update=":formulaire:contenu"/>
</p:menuButton>
</p:toolbarGroup>
</p:toolbar>
روشهای اجرا شده در قالب به شرح زیر است:
private String locale = "fr";
public void setFrenchLocale() {
locale = "fr";
// صفحه را تازه کنید
redirect();
}
public void setEnglishLocale() {
locale = "en";
// بارگذاری مجدد صفحه
redirect();
}
private void redirect() {
// ارسال مجدد مشتری به سرولت
ExternalContext ctx = FacesContext.getCurrentInstance().getExternalContext();
try {
ctx.redirect(ctx.getRequestContextPath());
} catch (IOException ex) {
Logger.getLogger(Form.class.getName()).log(Level.SEVERE, null, ex);
}
}
متدهای روی خطوط ۳ و ۹ به سادگی فیلد locale از خط ۱ را مقداردهی اولیه میکنند و سپس مرورگر کلاینت را به همان صفحه هدایت میکنند. هدایت (Redirect) پاسخی است که در آن سرور به مرورگر دستور میدهد صفحه دیگری را بارگذاری کند. سپس مرورگر یک درخواست GET به این صفحه جدید ارسال میکند.
- خط ۱۷: [ExternalContext] کلاسی از JSF است که دسترسی به سرولتی را که در حال حاضر در حال اجراست، فراهم میکند،
- خط ۱۹: هدایت انجام میشود. پارامتر متد redirect، URL صفحهای است که مرورگر مشتری باید به آن هدایت شود. در اینجا، میخواهیم به [/mv-rdvmedecins-pf] هدایت کنیم که نام برنامه ماست:
![]() |
متد [getRequestContextPath] این نام را بازیابی میکند. بنابراین، صفحه اصلی برنامه ما، [index.xhtml]، بارگذاری خواهد شد. این صفحه با قالب با دامنه جلسه [Form] مرتبط است. این قالب سه مقدار بولی را مدیریت میکند که ظاهر صفحه [index.xhtml] را کنترل میکنند:
private Boolean form1Rendered = true;
private Boolean form2Rendered = false;
private Boolean erreurRendered = false;
از آنجایی که این قالب در محدوده جلسه قرار دارد، این سه متغیر بولی مقادیر خود را حفظ کردهاند. بنابراین صفحه [index.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:p="http://primefaces.org/ui"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:ui="http://java.sun.com/jsf/facelets">
<f:view locale="#{form.locale}">
....
</f:view>
</html>
تگ در خط ۹ زبان نمایش صفحه را با استفاده از ویژگی locale آن تنظیم میکند. بنابراین صفحه بسته به شرایط به فرانسوی یا انگلیسی تغییر خواهد کرد. حالا، چرا هدایت (redirect)؟ بیایید به تگهای مربوط به گزینههای تغییر زبان بازگردیم:
<p:toolbar>
<p:toolbarGroup align="left">
...
</p:toolbarGroup>
<p:toolbarGroup align="right">
<p:menuButton value="#{msg['form1.options']}">
<p:menuitem id="menuitem-francais" value="#{msg['form1.francais']}" actionListener="#{form.setFrenchLocale}" update=":formulaire"/>
<p:menuitem id="menuitem-anglais" value="#{msg['form1.anglais']}" actionListener="#{form.setEnglishLocale}" update=":formulaire"/>
<p:menuitem id="menuitem-rafraichir" value="#{msg['form1.rafraichir']}" actionListener="#{form.refresh}" update=":formulaire:contenu"/>
</p:menuButton>
</p:toolbarGroup>
</p:toolbar>
این تگها در اصل برای بهروزرسانی فیلد شناسه فرم (attribute 'update' در خطوط ۷ و ۸) از طریق فراخوانی AJAX نوشته شده بودند. با این حال، در حین آزمایش، تغییر زبان همیشه کار نمیکرد. از این رو برای حل این مشکل به یک تغییر مسیر (redirect) متوسل شده است. شاید میتوانستیم ویژگی «ajax» را در تگها روی «false» تنظیم کنیم تا صفحه دوباره بارگذاری شود. این کار باعث میشد از هدایت مجدد جلوگیری شود.
6.16. تازه کردن لیستها
این با اقدام زیر مطابقت دارد:
![]() |
تگ مرتبط با گزینه [Rafraîchir] به شرح زیر است:
<p:menuitem id="menuitem-rafraichir" value="#{msg['form1.rafraichir']}" actionListener="#{form.refresh}" update=":formulaire:contenu"/>
متد [Form].refresh به شرح زیر است:
public void refresh() {
// تازه کردن فهرستها
init();
}
متد init متدی است که بلافاصله پس از ساخت bean [Form] اجرا میشود. هدف آن کش کردن دادهها از پایگاه داده در مدل است:
// bean برنامه
@Inject
private Application application;
// کش جلسه
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>();
private Map<String, Client> hIdentitesClients = new HashMap<String, Client>();
...
@PostConstruct
private void init() {
// پزشکان و مشتریان در حافظه پنهان ذخیره میشوند
try {
medecins = application.getMetier().getAllMedecins();
clients = application.getMetier().getAllClients();
} catch (Throwable th) {
...
}
...
// واژهنامهها
for (Medecin m : medecins) {
hMedecins.put(m.getId(), m);
}
for (Client c : clients) {
hClients.put(c.getId(), c);
hIdentitesClients.put(identite(c), c);
}
}
متد init لیستها و فرهنگلغتها را در خطوط 5 تا 9 ایجاد میکند. عیب این تکنیک این است که این عناصر دیگر تغییرات موجود در پایگاه داده (مانند افزودن یک مشتری یا پزشک و غیره) را منعکس نمیکنند. متد refresh باعث بازسازی این فهرستها و فرهنگلغتها میشود. بنابراین باید هر زمان که تغییری در پایگاه داده ایجاد شود، مانند افزودن یک مشتری جدید، از آن استفاده شود.
6.17. Conclusion
بیایید معماری برنامهای را که به تازگی ساختهایم به یاد بیاوریم:
![]() |
ما به شدت به نسخه از پیش ساخته شده JSF2 تکیه کردهایم:
- لایههای [métier]، [DAO] و [JPA] حفظ شدهاند،
- بیانهای [Application] و [Form] از لایه وب حفظ شدند، اما به دلیل بهبود رابط کاربری، ویژگیهای جدیدی به آنها اضافه شد،
- رابط کاربری بهطور قابلتوجهی بازطراحی شده است. بهویژه اکنون از نظر امکانات غنیتر و کاربرپسندتر است.
تغییر از JSF به PrimeFaces برای ساخت رابط کاربری وب به مقدار مشخصی تجربه نیاز دارد، زیرا در ابتدا ممکن است فرد از تعداد زیاد کامپوننتهای موجود کمی سردرگم شود و در نهایت مطمئن نباشد که کدام یک را باید استفاده کند. بنابراین لازم است قابلیت استفاده مطلوب رابط کاربری در نظر گرفته شود.
6.18. آزمایش با اکلیپس
همانطور که برای نسخههای قبلی اپلیکیشن نمونه انجام دادیم، نشان خواهیم داد که چگونه این نسخه ۰۳ را با استفاده از Eclipse تست کنیم. ابتدا، پروژههای Maven مربوط به مثال ۰۳، [1]، را وارد Eclipse میکنیم:
![]() |
- [mv-rdvmedecins-ejb-dao-jpa]: لایههای [DAO] و [JPA]،
- [mv-rdvmedecins-ejb-metier]: لایه [métier]،
- [mv-rdvmedecins-pf]: لایه [web] که با JSF و PrimeFaces پیادهسازی شده است،
- [mv-rdvmedecins-app]: پروژهٔ والد پروژهٔ سازمانی [mv-rdvmedecins-app-ear]. هنگامی که پروژهٔ والد وارد میشود، پروژهٔ فرزند به طور خودکار وارد میشود،
- در [2]، پروژه سازمانی [mv-rdvmedecins-app-ear] اجرا میشود،
![]() |
- در [3]، سرور GlassFish انتخاب شده است،
- در [4]، در زبانه [Servers]، برنامه کاربردی مستقر شده است. به طور خودکار اجرا نمیشود. شما باید آن را در یک مرورگر درخواست کنید: [5] [http://localhost:8080/mv-rdvmedecins-pf/] URL:
![]() |
















































