chore(quarkus-327): bump to Quarkus 3.27.3 LTS, rename deprecated config keys
This commit is contained in:
776
PRIMEFACES_BEST_PRACTICES_OPTIMIZATION.md
Normal file
776
PRIMEFACES_BEST_PRACTICES_OPTIMIZATION.md
Normal file
@@ -0,0 +1,776 @@
|
||||
# 🚀 Optimisation BTPXpress - Meilleures Pratiques PrimeFaces
|
||||
|
||||
## 📋 Table des Matières
|
||||
1. [Analyse du Projet Actuel](#analyse-du-projet-actuel)
|
||||
2. [Optimisations DataTable & Lazy Loading](#optimisations-datatable--lazy-loading)
|
||||
3. [Performance Ajax & Partial Rendering](#performance-ajax--partial-rendering)
|
||||
4. [Composants Réutilisables](#composants-réutilisables)
|
||||
5. [Gestion d'État & ViewScoped](#gestion-détat--viewscoped)
|
||||
6. [Validation & Messages](#validation--messages)
|
||||
7. [Plan d'Implémentation](#plan-dimplémentation)
|
||||
|
||||
---
|
||||
|
||||
## 🔍 Analyse du Projet Actuel
|
||||
|
||||
### Points Forts ✅
|
||||
- ✅ Utilisation de `@ViewScoped` pour les beans (bonne pratique)
|
||||
- ✅ Architecture BaseListView pour la réutilisation du code
|
||||
- ✅ Séparation des concerns (Service, View, Model)
|
||||
- ✅ Utilisation de composants réutilisables (`liste-table.xhtml`, `liste-filters.xhtml`)
|
||||
|
||||
### Points à Améliorer 🔧
|
||||
- ⚠️ **Pas de Lazy Loading** : Toutes les données sont chargées en mémoire
|
||||
- ⚠️ **Filtrage côté client** : Le filtrage se fait en Java après récupération complète
|
||||
- ⚠️ **Pas de cache** : Rechargement complet à chaque fois
|
||||
- ⚠️ **Updates Ajax trop larges** : Risque de re-rendering inutile
|
||||
- ⚠️ **Pas de composants composites Freya** : Utilisation directe de PrimeFaces
|
||||
|
||||
---
|
||||
|
||||
## 📊 Optimisations DataTable & Lazy Loading
|
||||
|
||||
### 1. Implémenter LazyDataModel
|
||||
|
||||
**Problème actuel** : Dans `FactureView.java`, toutes les factures sont chargées :
|
||||
```java
|
||||
List<Map<String, Object>> facturesData = factureService.getAllFactures();
|
||||
```
|
||||
|
||||
**Solution** : Utiliser `LazyDataModel` de PrimeFaces
|
||||
|
||||
#### Créer un LazyDataModel personnalisé
|
||||
|
||||
```java
|
||||
package dev.lions.btpxpress.view.model;
|
||||
|
||||
import org.primefaces.model.FilterMeta;
|
||||
import org.primefaces.model.LazyDataModel;
|
||||
import org.primefaces.model.SortMeta;
|
||||
import dev.lions.btpxpress.service.FactureService;
|
||||
import dev.lions.btpxpress.view.FactureView.Facture;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class FactureLazyDataModel extends LazyDataModel<Facture> {
|
||||
|
||||
private final FactureService factureService;
|
||||
|
||||
public FactureLazyDataModel(FactureService factureService) {
|
||||
this.factureService = factureService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int count(Map<String, FilterMeta> filterBy) {
|
||||
// Appel API pour compter le nombre total avec filtres
|
||||
return factureService.countFactures(buildFilterParams(filterBy));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Facture> load(int first, int pageSize,
|
||||
Map<String, SortMeta> sortBy,
|
||||
Map<String, FilterMeta> filterBy) {
|
||||
// Appel API avec pagination, tri et filtres
|
||||
Map<String, Object> params = new HashMap<>();
|
||||
params.put("offset", first);
|
||||
params.put("limit", pageSize);
|
||||
params.putAll(buildSortParams(sortBy));
|
||||
params.putAll(buildFilterParams(filterBy));
|
||||
|
||||
return factureService.getFacturesLazy(params);
|
||||
}
|
||||
|
||||
private Map<String, Object> buildSortParams(Map<String, SortMeta> sortBy) {
|
||||
Map<String, Object> params = new HashMap<>();
|
||||
if (sortBy != null && !sortBy.isEmpty()) {
|
||||
SortMeta sort = sortBy.values().iterator().next();
|
||||
params.put("sortField", sort.getField());
|
||||
params.put("sortOrder", sort.getOrder().name());
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
private Map<String, Object> buildFilterParams(Map<String, FilterMeta> filterBy) {
|
||||
Map<String, Object> params = new HashMap<>();
|
||||
if (filterBy != null) {
|
||||
filterBy.forEach((key, filter) -> {
|
||||
if (filter.getFilterValue() != null) {
|
||||
params.put("filter_" + key, filter.getFilterValue());
|
||||
}
|
||||
});
|
||||
}
|
||||
return params;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Modifier FactureView pour utiliser LazyDataModel
|
||||
|
||||
```java
|
||||
@Named("factureView")
|
||||
@ViewScoped
|
||||
public class FactureView implements Serializable {
|
||||
|
||||
@Inject
|
||||
FactureService factureService;
|
||||
|
||||
private LazyDataModel<Facture> lazyModel;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
lazyModel = new FactureLazyDataModel(factureService);
|
||||
}
|
||||
|
||||
public LazyDataModel<Facture> getLazyModel() {
|
||||
return lazyModel;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Modifier factures.xhtml pour utiliser lazy loading
|
||||
|
||||
```xml
|
||||
<p:dataTable id="facturesTable"
|
||||
value="#{factureView.lazyModel}"
|
||||
var="facture"
|
||||
lazy="true"
|
||||
paginator="true"
|
||||
rows="10"
|
||||
rowsPerPageTemplate="10,20,50"
|
||||
paginatorPosition="both"
|
||||
paginatorTemplate="{CurrentPageReport} {FirstPageLink} {PreviousPageLink} {PageLinks} {NextPageLink} {LastPageLink} {RowsPerPageDropdown}"
|
||||
currentPageReportTemplate="Affichage {startRecord}-{endRecord} sur {totalRecords}"
|
||||
filterDelay="500"
|
||||
emptyMessage="Aucune facture trouvée">
|
||||
|
||||
<!-- Colonnes avec filtres intégrés -->
|
||||
<p:column headerText="Numéro"
|
||||
sortBy="#{facture.numero}"
|
||||
filterBy="#{facture.numero}"
|
||||
filterMatchMode="contains">
|
||||
<h:outputText value="#{facture.numero}"/>
|
||||
</p:column>
|
||||
|
||||
<!-- ... autres colonnes ... -->
|
||||
</p:dataTable>
|
||||
```
|
||||
|
||||
### 2. Optimiser le Service Backend
|
||||
|
||||
**Ajouter des endpoints paginés dans BtpXpressApiClient** :
|
||||
|
||||
```java
|
||||
@Path("/api/factures")
|
||||
public interface BtpXpressApiClient {
|
||||
|
||||
@GET
|
||||
@Path("/lazy")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
Response getFacturesLazy(
|
||||
@QueryParam("offset") int offset,
|
||||
@QueryParam("limit") int limit,
|
||||
@QueryParam("sortField") String sortField,
|
||||
@QueryParam("sortOrder") String sortOrder,
|
||||
@QueryParam("filter_numero") String filtreNumero,
|
||||
@QueryParam("filter_client") String filtreClient,
|
||||
@QueryParam("filter_statut") String filtreStatut
|
||||
);
|
||||
|
||||
@GET
|
||||
@Path("/count")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
int countFactures(
|
||||
@QueryParam("filter_numero") String filtreNumero,
|
||||
@QueryParam("filter_client") String filtreClient,
|
||||
@QueryParam("filter_statut") String filtreStatut
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⚡ Performance Ajax & Partial Rendering
|
||||
|
||||
### 1. Optimiser les Updates Ajax
|
||||
|
||||
**Problème** : Updates trop larges qui re-rendent des composants inutilement
|
||||
|
||||
**Mauvaise pratique** ❌ :
|
||||
```xml
|
||||
<p:commandButton value="Filtrer"
|
||||
update="@form"
|
||||
action="#{factureView.applyFilters}"/>
|
||||
```
|
||||
|
||||
**Bonne pratique** ✅ :
|
||||
```xml
|
||||
<p:commandButton value="Filtrer"
|
||||
update="facturesTable messages"
|
||||
process="@this filtresPanel"
|
||||
action="#{factureView.applyFilters}"/>
|
||||
```
|
||||
|
||||
### 2. Utiliser process et update de manière ciblée
|
||||
|
||||
**Règles d'or** :
|
||||
- `process` : Spécifie quels composants doivent être traités (validation, conversion, update model)
|
||||
- `update` : Spécifie quels composants doivent être re-rendus
|
||||
- Toujours utiliser les IDs spécifiques plutôt que `@form` ou `@all`
|
||||
|
||||
**Exemple optimisé** :
|
||||
```xml
|
||||
<h:form id="factureForm">
|
||||
<p:panel id="filtresPanel">
|
||||
<p:inputText id="filtreNumero" value="#{factureView.filtreNumero}"/>
|
||||
<p:inputText id="filtreClient" value="#{factureView.filtreClient}"/>
|
||||
|
||||
<p:commandButton value="Rechercher"
|
||||
process="@this filtresPanel"
|
||||
update="facturesTable messages"
|
||||
action="#{factureView.search}"/>
|
||||
</p:panel>
|
||||
|
||||
<p:messages id="messages"/>
|
||||
|
||||
<p:dataTable id="facturesTable" value="#{factureView.lazyModel}" var="facture">
|
||||
<!-- colonnes -->
|
||||
</p:dataTable>
|
||||
</h:form>
|
||||
```
|
||||
|
||||
### 3. Utiliser p:ajax pour les événements
|
||||
|
||||
**Pour les changements de filtres en temps réel** :
|
||||
```xml
|
||||
<p:inputText id="filtreNumero" value="#{factureView.filtreNumero}">
|
||||
<p:ajax event="keyup"
|
||||
delay="500"
|
||||
update="facturesTable"
|
||||
process="@this"
|
||||
listener="#{factureView.onFilterChange}"/>
|
||||
</p:inputText>
|
||||
```
|
||||
|
||||
### 4. Désactiver les auto-updates inutiles
|
||||
|
||||
**Éviter** :
|
||||
```xml
|
||||
<p:dataTable autoUpdate="true"> <!-- ❌ Mauvaise pratique -->
|
||||
```
|
||||
|
||||
**Préférer** :
|
||||
```xml
|
||||
<p:dataTable id="table">
|
||||
<p:ajax event="rowSelect" update="detailPanel" listener="#{bean.onRowSelect}"/>
|
||||
</p:dataTable>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🧩 Composants Réutilisables
|
||||
|
||||
### 1. Migrer vers Freya Extension
|
||||
|
||||
**Avantages** :
|
||||
- Composants pré-stylés cohérents
|
||||
- Moins de code boilerplate
|
||||
- Meilleure maintenabilité
|
||||
|
||||
#### Exemple : Remplacer p:dataTable par fr:dataTable
|
||||
|
||||
**Avant** :
|
||||
```xml
|
||||
<p:dataTable id="facturesTable"
|
||||
value="#{factureView.items}"
|
||||
var="facture"
|
||||
paginator="true"
|
||||
rows="10"
|
||||
styleClass="p-datatable-striped"
|
||||
emptyMessage="Aucune facture">
|
||||
<p:column headerText="Numéro">
|
||||
<h:outputText value="#{facture.numero}"/>
|
||||
</p:column>
|
||||
</p:dataTable>
|
||||
```
|
||||
|
||||
**Après** :
|
||||
```xml
|
||||
<fr:dataTable value="#{factureView.lazyModel}"
|
||||
var="facture"
|
||||
paginator="true"
|
||||
rows="10"
|
||||
lazy="true"
|
||||
stripedRows="true">
|
||||
<p:column headerText="Numéro">
|
||||
<h:outputText value="#{facture.numero}"/>
|
||||
</p:column>
|
||||
</fr:dataTable>
|
||||
```
|
||||
|
||||
### 2. Créer des Composants Composites Métier
|
||||
|
||||
#### Créer un composant pour les badges de statut
|
||||
|
||||
**Fichier** : `/WEB-INF/components/facture-statut-badge.xhtml`
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
|
||||
xmlns:h="http://xmlns.jcp.org/jsf/html"
|
||||
xmlns:f="http://xmlns.jcp.org/jsf/core"
|
||||
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
|
||||
xmlns:cc="http://xmlns.jcp.org/jsf/composite"
|
||||
xmlns:p="http://primefaces.org/ui">
|
||||
|
||||
<cc:interface>
|
||||
<cc:attribute name="statut" required="true" type="java.lang.String"/>
|
||||
<cc:attribute name="enRetard" type="java.lang.Boolean" default="false"/>
|
||||
</cc:interface>
|
||||
|
||||
<cc:implementation>
|
||||
<p:tag value="#{cc.attrs.statut}"
|
||||
severity="#{cc.attrs.statut == 'PAYEE' ? 'success' :
|
||||
(cc.attrs.statut == 'ANNULEE' ? 'danger' :
|
||||
(cc.attrs.enRetard ? 'danger' : 'warning'))}"
|
||||
icon="#{cc.attrs.statut == 'PAYEE' ? 'pi pi-check' :
|
||||
(cc.attrs.statut == 'ANNULEE' ? 'pi pi-times' :
|
||||
(cc.attrs.enRetard ? 'pi pi-exclamation-triangle' : 'pi pi-clock'))}"/>
|
||||
</cc:implementation>
|
||||
</ui:composition>
|
||||
```
|
||||
|
||||
**Utilisation** :
|
||||
```xml
|
||||
<p:column headerText="Statut">
|
||||
<btpx:facture-statut-badge statut="#{facture.statut}"
|
||||
enRetard="#{factureView.isEnRetard(facture)}"/>
|
||||
</p:column>
|
||||
```
|
||||
|
||||
#### Créer un composant pour les montants
|
||||
|
||||
**Fichier** : `/WEB-INF/components/montant-display.xhtml`
|
||||
```xml
|
||||
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
|
||||
xmlns:h="http://xmlns.jcp.org/jsf/html"
|
||||
xmlns:f="http://xmlns.jcp.org/jsf/core"
|
||||
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
|
||||
xmlns:cc="http://xmlns.jcp.org/jsf/composite">
|
||||
|
||||
<cc:interface>
|
||||
<cc:attribute name="montant" required="true" type="java.lang.Double"/>
|
||||
<cc:attribute name="devise" default="Fcfa"/>
|
||||
<cc:attribute name="highlight" type="java.lang.Boolean" default="false"/>
|
||||
<cc:attribute name="highlightColor" default="red"/>
|
||||
</cc:interface>
|
||||
|
||||
<cc:implementation>
|
||||
<span style="#{cc.attrs.highlight ? 'color: ' + cc.attrs.highlightColor + '; font-weight: bold;' : ''}">
|
||||
<h:outputText value="#{cc.attrs.montant}">
|
||||
<f:converter converterId="fcfaConverter"/>
|
||||
</h:outputText>
|
||||
<h:outputText value=" #{cc.attrs.devise}"/>
|
||||
</span>
|
||||
</cc:implementation>
|
||||
</ui:composition>
|
||||
```
|
||||
|
||||
**Utilisation** :
|
||||
```xml
|
||||
<p:column headerText="Reste à payer">
|
||||
<btpx:montant-display montant="#{factureView.getMontantRestant(facture)}"
|
||||
highlight="#{factureView.getMontantRestant(facture) > 0}"/>
|
||||
</p:column>
|
||||
```
|
||||
|
||||
### 3. Créer un Composant de Filtre Réutilisable
|
||||
|
||||
**Fichier** : `/WEB-INF/components/search-filter-panel.xhtml`
|
||||
```xml
|
||||
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
|
||||
xmlns:h="http://xmlns.jcp.org/jsf/html"
|
||||
xmlns:f="http://xmlns.jcp.org/jsf/core"
|
||||
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
|
||||
xmlns:cc="http://xmlns.jcp.org/jsf/composite"
|
||||
xmlns:p="http://primefaces.org/ui"
|
||||
xmlns:fr="http://primefaces.org/freya">
|
||||
|
||||
<cc:interface>
|
||||
<cc:attribute name="bean" required="true"/>
|
||||
<cc:attribute name="tableId" required="true"/>
|
||||
<cc:facet name="filters" required="true"/>
|
||||
</cc:interface>
|
||||
|
||||
<cc:implementation>
|
||||
<div class="card mb-3">
|
||||
<div class="flex align-items-center justify-content-between mb-3">
|
||||
<h3 class="m-0">
|
||||
<i class="pi pi-filter mr-2"></i>
|
||||
Filtres de recherche
|
||||
</h3>
|
||||
<div class="flex gap-2">
|
||||
<fr:commandButton value="Rechercher"
|
||||
icon="pi pi-search"
|
||||
severity="primary"
|
||||
process="@this @parent"
|
||||
update="#{cc.attrs.tableId} messages"
|
||||
action="#{cc.attrs.bean.applyFilters}"/>
|
||||
|
||||
<fr:commandButton value="Réinitialiser"
|
||||
icon="pi pi-refresh"
|
||||
severity="secondary"
|
||||
outlined="true"
|
||||
process="@this"
|
||||
update="@parent #{cc.attrs.tableId}"
|
||||
action="#{cc.attrs.bean.resetFilters}"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<cc:renderFacet name="filters"/>
|
||||
</div>
|
||||
</cc:implementation>
|
||||
</ui:composition>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Gestion d'État & ViewScoped
|
||||
|
||||
### 1. Optimiser BaseListView
|
||||
|
||||
**Problème actuel** : Rechargement complet à chaque filtre
|
||||
|
||||
**Solution** : Ajouter un cache intelligent
|
||||
|
||||
```java
|
||||
@Getter
|
||||
@Setter
|
||||
public abstract class BaseListView<T, ID> implements Serializable {
|
||||
|
||||
protected LazyDataModel<T> lazyModel;
|
||||
protected T selectedItem;
|
||||
protected boolean loading;
|
||||
|
||||
// Cache pour éviter les rechargements inutiles
|
||||
private transient Map<String, Object> lastFilterParams;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
initializeLazyModel();
|
||||
}
|
||||
|
||||
protected abstract void initializeLazyModel();
|
||||
|
||||
public void applyFilters() {
|
||||
Map<String, Object> currentParams = buildFilterParams();
|
||||
|
||||
// Vérifier si les filtres ont changé
|
||||
if (!Objects.equals(lastFilterParams, currentParams)) {
|
||||
lastFilterParams = new HashMap<>(currentParams);
|
||||
// Le LazyDataModel se rechargera automatiquement
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract Map<String, Object> buildFilterParams();
|
||||
|
||||
public void resetFilters() {
|
||||
resetFilterFields();
|
||||
lastFilterParams = null;
|
||||
applyFilters();
|
||||
}
|
||||
|
||||
protected abstract void resetFilterFields();
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Utiliser @CacheResult pour les données statiques
|
||||
|
||||
**Pour les listes de référence (statuts, types, etc.)** :
|
||||
|
||||
```java
|
||||
@ApplicationScoped
|
||||
public class ReferenceDataService {
|
||||
|
||||
@CacheResult(cacheName = "statuts-facture")
|
||||
public List<SelectItem> getStatutsFacture() {
|
||||
return Arrays.asList(
|
||||
new SelectItem("BROUILLON", "Brouillon"),
|
||||
new SelectItem("EMISE", "Émise"),
|
||||
new SelectItem("PAYEE", "Payée"),
|
||||
// ...
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Utilisation dans le bean** :
|
||||
```java
|
||||
@Named("factureView")
|
||||
@ViewScoped
|
||||
public class FactureView implements Serializable {
|
||||
|
||||
@Inject
|
||||
ReferenceDataService refDataService;
|
||||
|
||||
public List<SelectItem> getStatutsFacture() {
|
||||
return refDataService.getStatutsFacture(); // Mis en cache
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ Validation & Messages
|
||||
|
||||
### 1. Validation côté client avec PrimeFaces
|
||||
|
||||
**Activer la validation client** dans `application.properties` :
|
||||
```properties
|
||||
primefaces.CLIENT_SIDE_VALIDATION=true
|
||||
primefaces.CSV_ENABLED=true
|
||||
```
|
||||
|
||||
**Exemple de formulaire avec validation** :
|
||||
```xml
|
||||
<h:form id="factureForm">
|
||||
<p:messages id="messages" showDetail="true" closable="true"/>
|
||||
|
||||
<fr:fieldInput label="Numéro de facture"
|
||||
value="#{factureView.entity.numero}"
|
||||
required="true"
|
||||
requiredMessage="Le numéro est obligatoire">
|
||||
<f:validateLength minimum="3" maximum="20"/>
|
||||
</fr:fieldInput>
|
||||
|
||||
<fr:fieldCalendar label="Date d'émission"
|
||||
value="#{factureView.entity.dateEmission}"
|
||||
required="true"
|
||||
showIcon="true">
|
||||
<f:validator validatorId="dateValidator"/>
|
||||
</fr:fieldCalendar>
|
||||
|
||||
<fr:commandButton value="Enregistrer"
|
||||
icon="pi pi-save"
|
||||
action="#{factureView.save}"
|
||||
update="messages"
|
||||
process="@form"
|
||||
validateClient="true"/>
|
||||
</h:form>
|
||||
```
|
||||
|
||||
### 2. Messages d'erreur personnalisés
|
||||
|
||||
**Créer un validateur personnalisé** :
|
||||
```java
|
||||
@FacesValidator("dateValidator")
|
||||
public class DateValidator implements Validator<LocalDate> {
|
||||
|
||||
@Override
|
||||
public void validate(FacesContext context, UIComponent component, LocalDate value)
|
||||
throws ValidatorException {
|
||||
if (value != null && value.isBefore(LocalDate.now())) {
|
||||
FacesMessage msg = new FacesMessage(
|
||||
FacesMessage.SEVERITY_ERROR,
|
||||
"Date invalide",
|
||||
"La date ne peut pas être dans le passé"
|
||||
);
|
||||
throw new ValidatorException(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Utiliser p:growl pour les notifications
|
||||
|
||||
**Ajouter dans le template** :
|
||||
```xml
|
||||
<p:growl id="growl"
|
||||
life="3000"
|
||||
sticky="false"
|
||||
showDetail="true"/>
|
||||
```
|
||||
|
||||
**Dans le bean** :
|
||||
```java
|
||||
public void save() {
|
||||
try {
|
||||
factureService.save(selectedItem);
|
||||
|
||||
FacesContext.getCurrentInstance().addMessage(null,
|
||||
new FacesMessage(FacesMessage.SEVERITY_INFO,
|
||||
"Succès",
|
||||
"La facture a été enregistrée avec succès"));
|
||||
} catch (Exception e) {
|
||||
FacesContext.getCurrentInstance().addMessage(null,
|
||||
new FacesMessage(FacesMessage.SEVERITY_ERROR,
|
||||
"Erreur",
|
||||
"Impossible d'enregistrer la facture: " + e.getMessage()));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📅 Plan d'Implémentation
|
||||
|
||||
### Phase 1 : Optimisation des DataTables (Semaine 1)
|
||||
- [ ] Créer `FactureLazyDataModel`
|
||||
- [ ] Modifier `FactureView` pour utiliser LazyDataModel
|
||||
- [ ] Ajouter endpoints paginés dans le backend
|
||||
- [ ] Tester la pagination et le tri
|
||||
- [ ] Répliquer pour Devis, Clients, Chantiers
|
||||
|
||||
### Phase 2 : Optimisation Ajax (Semaine 2)
|
||||
- [ ] Auditer tous les `update="@form"` et les remplacer
|
||||
- [ ] Ajouter `process` spécifiques sur tous les commandButton
|
||||
- [ ] Implémenter `p:ajax` pour les filtres en temps réel
|
||||
- [ ] Tester les performances
|
||||
|
||||
### Phase 3 : Composants Réutilisables (Semaine 3)
|
||||
- [ ] Créer `facture-statut-badge.xhtml`
|
||||
- [ ] Créer `montant-display.xhtml`
|
||||
- [ ] Créer `search-filter-panel.xhtml`
|
||||
- [ ] Migrer vers `fr:dataTable` pour toutes les tables
|
||||
- [ ] Créer composants métier supplémentaires
|
||||
|
||||
### Phase 4 : Validation & UX (Semaine 4)
|
||||
- [ ] Activer validation côté client
|
||||
- [ ] Créer validateurs personnalisés
|
||||
- [ ] Implémenter p:growl pour notifications
|
||||
- [ ] Ajouter confirmations pour actions critiques
|
||||
- [ ] Tests utilisateurs
|
||||
|
||||
### Phase 5 : Cache & Performance (Semaine 5)
|
||||
- [ ] Implémenter cache pour données de référence
|
||||
- [ ] Optimiser BaseListView avec cache intelligent
|
||||
- [ ] Profiler et identifier les bottlenecks
|
||||
- [ ] Optimiser les requêtes backend
|
||||
|
||||
---
|
||||
|
||||
## 📊 Métriques de Succès
|
||||
|
||||
### Avant Optimisation
|
||||
- ⏱️ Temps de chargement liste factures : ~2-3s (100+ factures)
|
||||
- 📦 Données transférées : Toutes les factures à chaque fois
|
||||
- 🔄 Re-rendering : Formulaire complet à chaque action
|
||||
- 💾 Mémoire : Toutes les données en mémoire
|
||||
|
||||
### Après Optimisation (Objectifs)
|
||||
- ⏱️ Temps de chargement : <500ms (pagination)
|
||||
- 📦 Données transférées : 10-50 factures par page
|
||||
- 🔄 Re-rendering : Composants ciblés uniquement
|
||||
- 💾 Mémoire : Données paginées + cache intelligent
|
||||
- 🎯 Score Lighthouse : >90
|
||||
|
||||
---
|
||||
|
||||
## 🔗 Ressources
|
||||
|
||||
### Documentation PrimeFaces
|
||||
- [PrimeFaces Showcase](https://www.primefaces.org/showcase/)
|
||||
- [LazyDataModel Guide](https://www.primefaces.org/docs/guide/primefaces_user_guide_14_0_0.pdf)
|
||||
- [Ajax Best Practices](https://www.primefaces.org/showcase/ui/ajax/basic.xhtml)
|
||||
|
||||
### Exemples de Code
|
||||
- [PrimeFaces GitHub](https://github.com/primefaces/primefaces)
|
||||
- [PrimeFaces Showcase Source](https://github.com/primefaces/primefaces/tree/master/primefaces-showcase)
|
||||
|
||||
### Articles & Tutoriels
|
||||
- [PrimeFaces DataTable Lazy Loading with JPA](https://www.javacodegeeks.com/2014/01/primefaces-datatable-lazy-loading-with-pagination-filtering-and-sorting-using-jpa-criteria-viewscoped.html)
|
||||
- [Optimizing JSF Performance](https://www.baeldung.com/jsf-primefaces-performance)
|
||||
|
||||
---
|
||||
|
||||
## 🎓 Bonnes Pratiques Générales
|
||||
|
||||
### DO ✅
|
||||
- ✅ Utiliser LazyDataModel pour les grandes listes
|
||||
- ✅ Spécifier `process` et `update` de manière ciblée
|
||||
- ✅ Utiliser `@ViewScoped` pour les beans de vue
|
||||
- ✅ Créer des composants réutilisables
|
||||
- ✅ Valider côté client ET serveur
|
||||
- ✅ Utiliser le cache pour les données statiques
|
||||
- ✅ Tester les performances régulièrement
|
||||
|
||||
### DON'T ❌
|
||||
- ❌ Charger toutes les données en mémoire
|
||||
- ❌ Utiliser `update="@all"` ou `update="@form"` systématiquement
|
||||
- ❌ Oublier `process` sur les commandButton
|
||||
- ❌ Dupliquer le code de composants
|
||||
- ❌ Ignorer la validation côté client
|
||||
- ❌ Recharger les données de référence à chaque fois
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Prochaines Étapes
|
||||
|
||||
1. **Commencer par Phase 1** : Lazy Loading pour Factures
|
||||
2. **Mesurer les performances** avant/après
|
||||
3. **Itérer** sur les autres modules
|
||||
4. **Documenter** les patterns réutilisables
|
||||
5. **Former l'équipe** aux nouvelles pratiques
|
||||
|
||||
---
|
||||
|
||||
**Créé le** : 2025-12-29
|
||||
**Auteur** : Équipe BTPXpress
|
||||
**Version** : 1.0
|
||||
|
||||
### 2. Optimiser le Service Backend
|
||||
|
||||
**Ajouter des endpoints paginés dans BtpXpressApiClient** :
|
||||
|
||||
```java
|
||||
@Path("/api/factures")
|
||||
public interface BtpXpressApiClient {
|
||||
|
||||
@GET
|
||||
@Path("/lazy")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
Response getFacturesLazy(
|
||||
@QueryParam("offset") int offset,
|
||||
@QueryParam("limit") int limit,
|
||||
@QueryParam("sortField") String sortField,
|
||||
@QueryParam("sortOrder") String sortOrder,
|
||||
@QueryParam("filter_numero") String filtreNumero,
|
||||
@QueryParam("filter_client") String filtreClient,
|
||||
@QueryParam("filter_statut") String filtreStatut
|
||||
);
|
||||
|
||||
@GET
|
||||
@Path("/count")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
int countFactures(
|
||||
@QueryParam("filter_numero") String filtreNumero,
|
||||
@QueryParam("filter_client") String filtreClient,
|
||||
@QueryParam("filter_statut") String filtreStatut
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⚡ Performance Ajax & Partial Rendering
|
||||
|
||||
### 1. Optimiser les Updates Ajax
|
||||
|
||||
**Problème** : Updates trop larges qui re-rendent des composants inutilement
|
||||
|
||||
**Mauvaise pratique** ❌ :
|
||||
```xml
|
||||
<p:commandButton value="Filtrer"
|
||||
update="@form"
|
||||
action="#{factureView.applyFilters}"/>
|
||||
```
|
||||
|
||||
**Bonne pratique** ✅ :
|
||||
```xml
|
||||
<p:commandButton value="Filtrer"
|
||||
update="facturesTable messages"
|
||||
process="@this filtresPanel"
|
||||
action="#{factureView.applyFilters}"/>
|
||||
```
|
||||
|
||||
### 2. Utiliser process et update de manière ciblée
|
||||
|
||||
|
||||
Reference in New Issue
Block a user