Schicker Standard

Moderne Webanwendungen mit JSF 2.3 und PrimeFaces 7.0

Philip Riecks

© Stokkete/Shutterstock.com

Modern und JSF? Auf den ersten Blick mag das paradox klingen, ist jedoch mit Java EE 8 und dem neuen PrimeFaces-7.0-Release möglich. Anhand eines simplen Beispiels (Tabellenkomponente mit allen wichtigen Features) zeigt dieser Artikel, wie auch heute noch der JSF-Standard für Modernität und Produktivität sorgen kann. Verstaubte Erinnerungen an alte J2EE-Zeiten solltet ihr während des Lesens dieses Artikels ausblenden.

Das 150 Megabyte große (leere) Angular „HelloWorld“-Projekt ist nach einwöchiger Evaluierungsphase nun endlich aufgesetzt und die Entwicklung kann beginnen. Als erstes Feature steht die tabellarische Darstellung von Kundendaten an. Da die Standard-HTML-<table> für die meisten Anwendungsfälle wohl nicht ausreichend ist, beginnt nun eine erneute Evaluierungsphase und die Suche nach der passenden Tabellenbibliothek X beginnt. Kaum wurden die passende Komponente in die package.json aufgenommen und erste Lizenzkosten überwiesen, fällt nach zweiwöchiger Entwicklung plötzlich auf, dass die Tabelle X zwar gut aussieht, die Daten sich filtern und gruppieren lassen, aber das Kontextmenüfeature für diese Tabellenkomponente X sich noch in der Betaphase befindet und für den geforderten Anwendungsfall nicht geeignet ist. Die Suche beginnt also erneut und schnell entscheidet man sich für die Tabellenkomponente Y. Die Integration ist schnell erledigt, jedoch wurde aufgrund des Termindrucks bei der Tabellensuche nicht beachtet, dass diese Komponente Y (noch) nicht vollständig auf die neueste Angular-Version ausgelegt ist, und die Entwicklung steht ein zweites Mal still …

Warum nicht einfach auf einen langjährigen Standard setzen, der darüber hinaus folgende Vorteile für Java-Entwickler bietet?

  • Voller Java-Fokus bei der Entwicklung
  • Lediglich grundlegende HTML-, CSS- und JavaScript-Kenntnisse nötig
  • Alle Komponenten werden von einer Bibliothek bereitgestellt, die auf eine langjährige Entwicklung (sprich: „Reife“) zurückblicken kann
  • Schickes Aussehen dank moderner Themes
  • Vollständige Integration mit anderen Java-EE-Standards (z. B. CDI, Java EE Security, …)

All das lässt sich mit dem JSF-(JavaServer-Faces-)Standard und PrimeFaces erreichen, denn mit dem Release PrimeFaces 7.0 wurden im März dieses Jahres neben 500 Verbesserungen weitere Komponenten eingeführt. Zusätzlich gibt es neue und kostenlose Themes, die mit der Modernität von bekannten CSS-Bibliotheken wie Bootstrap oder Semantic UI definitiv mithalten können.

Als Beispiel dient eine klassische Tabelle zur Anzeige von Kundendaten, die zusätzlich folgende Features enthalten soll: Paginierung, Filterung, Sortierung, Detailansicht, Gruppierung, Selektion von Spalten und ein Kontextmenü; außerdem soll sie in weniger als 30 Minuten einsatzbereit sein.

Getting started (Backend)

Für die initiale Projekterstellung wird der Maven Archetype de.rieckpil.archetypes:javaee8-jsf verwendet, der ein Maven-Projekt mit allen benötigten Dateien und Konfigurationen erstellt (Listing 1).

mvn archetype:generate -DarchetypeGroupId=de.rieckpil.archetypes \
 -DarchetypeArtifactId=javaee8-jsf \
 -DarchetypeVersion=1.0.1 \
 -DgroupId=de.rieckpil.blog \
 -DartifactId=java-magazin-jsf-primefaces \
 -DinteractiveMode=false

Die pom.xml des Projekts enthält neben dem Java EE 8 API und der PrimeFaces-7.0-Bibliothek inklusive der Themes auch noch die Abhängigkeit zu Java Faker, um valide Zufallsdaten für die Tabelle zu erzeugen.

W-JAX 2019 Java-Dossier für Software-Architekten

Kostenlos: Java-Dossier für Software-Architekten 2019

Auf über 30 Seiten vermitteln Experten praktisches Know-how zu den neuen Valuetypes in Java 12, dem Einsatz von Service Meshes in Microservices-Projekten, der erfolgreichen Einführung von DevOps-Praktiken im Unternehmen und der nachhaltigen JavaScript-Entwicklung mit Angular und dem WebComponents-Standard.

 

Unsere zentrale Backing Bean ist die Klasse CustomerListBean, die durch die Annotation @Named registriert wird und von nun an via Expression Language (EL) unter #{customerListBean} im Frontend verfügbar ist. Die Aufgabe der CustomerListBean besteht darin, Zugriff auf die Kundenliste zu erhalten, diese zu verändern und den Zustand der Sortierung beziehungsweise Filterung zu halten (Listing 2).

@Named
@ViewScoped
public class CustomerListBean implements Serializable {

  private List<Customer> customers;
  private List<Customer> filteredCustomerList;
  private List<Customer> selectedCustomerList;

  @Inject
  private CustomerService customerService;

  @PostConstruct
  public void init() {
    customers = customerService.getCustomers();
  }

  public String getTotalRevenue() {
    if (this.customers == null) {
      return "0";
    }

    Long totalRevenue = customers.stream().mapToLong(Customer::getBilledRevenue).sum();
    return new DecimalFormat("###,###.###").format(totalRevenue);
  }

  public void deleteCustomers() {
    for (Customer customer : selectedCustomerList) {
      this.customerService.deleteCustomer(customer);

      if (filteredCustomerList != null) {
        this.filteredCustomerList.remove(customer);
      }

      this.customers = customerService.getCustomers();
    }
  }
  // getters & setters
}

Für Klassenattribute, die nach außen hin veröffentlicht werden sollen, werden die jeweiligen Getter und Setter vorausgesetzt, um mittels EL (z. B. #{customerListBean.customers}) den Zugriff im Frontend zu ermöglichen.

Für den Scope der Backing Bean wird @ViewScoped verwendet, da dadurch der Lebenszyklus der Bean an den Zeitraum gebunden ist, in dem der Nutzer mit der gleichen JSF View arbeitet. Das ist notwendig, um den Zustand der sortierten bzw. gefilterten Kunden über mehrere Requests aufrechtzuerhalten (mit @RequestScoped wäre das nicht der Fall).

Als Datengrundlage dient die Klasse CustomerService, die in einer Liste die zufällig generierten Kunden speichert (Listing 3). Eine Interaktion mit einer relationalen Datenbank via JPA wäre hier ebenso gut möglich.

@Startup
@Singleton
public class CustomerService {
  private List<Customer> customers;

  @PostConstruct
  public void init() {
    this.customers = createRandomCustomers();
  }

  public List<Customer> getCustomers() {
    return customers;
  }

  public void deleteCustomer(Customer customer) {
    this.customers.remove(customer);
  }

  private List<Customer> createRandomCustomers() {
    List<Customer> result = new ArrayList<>();
    Faker faker = new Faker();
    for (int i = 0; i < 100; i++) {
      result.add(new Customer(
        faker.name().firstName(), 
        faker.name().lastName(), 
        faker.idNumber().valid(),
        ThreadLocalRandom.current().nextLong(1_000_000)));
    }
    return result;
  }
}

Getting started (Frontend)

Für unser Beispiel genügt eine View, die die PrimeFaces-DataTable-Komponente enthält. Der Zugriff auf die verschiedenen Komponenten von PrimeFaces erfolgt über den Namespace p, der zu Beginn der index.xhtml View definiert ist:

<html xmlns="http://www.w3.org/1999/xhtml"
  xmlns:h="http://java.sun.com/jsf/html"
  xmlns:f="http://java.sun.com/jsf/core"
  xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
  xmlns:p="http://primefaces.org/ui" >

Einbinden lässt sich die Komponente nun an beliebiger Stelle im Dokument (Listing 4)

<h:form id="customerListForm">
    <p:dataTable widgetVar="customerList" 
      id="customerList"
      value="#{customerListBean.customers}" 
      var="customer"
      filteredValue="#{customerListBean.filteredCustomerList}"
      rowKey="#{customer.customerId}"
      selection="#{customerListBean.selectedCustomerList}"
      resizableColumns="true" rows="10" paginator="true"
      paginatorTemplate="{CurrentPageReport} {FirstPageLink} {PreviousPageLink} {PageLinks} {NextPageLink} {LastPageLink} {RowsPerPageDropdown}"
      rowsPerPageTemplate="5,10,15">
    <!-- … -->
</h:form>

Das value-Attribut dieser Komponente verweist auf die Kundenliste unserer Backing Bean CustomerListBean und über die Variable customer kann nun innerhalb der Tabelle auf das Kundenobjekt zugegriffen werden. Ebenso definieren wir hier die Listen der Backing Bean zum Speichern der gefilterten und sortieren Kunden. Paginierung lässt sich in diesem Fall über verschiedene HTML-Attribute der Komponente definieren.

Die einzelnen Spalten der Tabelle können nun folgendermaßen erstellt werden:

<p:column filterBy="#{customer.customerId}"
  headerText="Customer ID" 
  filterMatchMode="contains">
  <h:outputText value="#{customer.customerId}" />
</p:column>

Dazu werden neben dem Namen der Spalte zusätzlich der Filtermodus und das zu filternde Attribut bestimmt. Das Kontextmenü wird ebenso innerhalb der Tabellenkomponente definiert und ermöglicht in unserem Fall eine Detailansicht des Kunden in einem Dialogfenster und das Löschen des ausgewählten Kunden (Listing 5)

<p:contextMenu for="customerList">
  <p:menuitem value="View selected" 
    update="multiCustomerDetail"
    icon="pi pi-search"
    oncomplete="PF('multiCustomerDialog').show()" />
  <p:menuitem value="Delete selected"
    update="customerList"
    icon="pi pi-times"
    action="#{customerListBean.deleteCustomers}" />
</p:contextMenu>

Das Dialogfenster wird separat erstellt und verwendet die PrimeFaces-Dialogkomponente zum Anzeigen von Detailinformationen von einem oder mehreren ausgewählten Kunden (Listing 6). Zum Schluss sieht das Ganze aus wie in Abbildung 1.

Abb. 1: Das Ergebnis: Anzeige von Kundendaten in einer Tabelle

Abb. 1: Das Ergebnis: Anzeige von Kundendaten in einer Tabelle

<p:dialog header="Selected Customers"
  widgetVar="multiCustomerDialog" 
  modal="true" showEffect="fade"
  hideEffect="fade" resizable="true" width="450">
  <p:outputPanel id="multiCustomerDetail" style="text-align:center;">
    <ui:repeat value="#{customerListBean.selectedCustomerList}" var="customer">
      <h:outputText value="#{customer.customerId} - #{customer.firstName} - #{customer.lastName}" style="display:block" />
      <h:outputText value="#{customer.billedRevenue}">
      <f:convertNumber type="currency" currencySymbol="$" />
      </h:outputText>
    </ui:repeat>
  </p:outputPanel>
</p:dialog>

Abschließende Gedanken

Der Artikel soll auf keinen Fall die aktuellen SPAs (Single Page Applications) verteufeln, ich möchte lediglich zeigen, dass vieles (besonders bei interner Anwendung) möglicherweise produktiver mit dem (für viele bereits verstaubten) JSF-Standard möglich ist. Schließlich lassen sich in Kombination mit PrimeFaces und OmniFaces (Utility-Bibliothek für JSF) auch heute noch schicke und moderne Webanwendungen mit JSF erstellen.

Solltet ihr bisher keine Erfahrungen mit JSF haben, kann ich für den Einstieg das Buch „The Definitive Guide to JSF in Java EE 8“ von Bauke Scholtz und Arjan Tijms empfehlen. Weitere Beispiele zu JSF und rund um Java/Jakarta EE und Spring findet ihr auf meinem Blog. Der gesamten Sourcecode für dieses Beispiel ist auf GitHub veröffentlicht und kann lokal gestartet werden. Eine Livedemo ist ebenso verfügbar.

Verwandte Themen:

Geschrieben von
Philip Riecks
Philip Riecks
Philip Riecks zeigt unter dem Motto „Enterprise Development with Java made simple” auf seinem Blog, wie Java/Jakarta EE und Spring effektiv eingesetzt werden können und arbeitet als Senior Java Software Engineer bei ImmobilienScout24 in Berlin. Web: https://rieckpil.de Twitter: @rieckpil
Kommentare

Hinterlasse einen Kommentar

3 Kommentare auf "Moderne Webanwendungen mit JSF 2.3 und PrimeFaces 7.0"

avatar
4000
  Subscribe  
Benachrichtige mich zu:
Entwickler
Gast

Aus welchem Grund ist JSF/Primefaces besonders für interne Anwendungen geeignet oder ist die Aussage blind aus diversen anderen Beiträgen übernommen worden?
Wir setzen JSF/Primafaces ausschließlich für externe Anwendungen ein, was nicht an Komponenten existiert wird gebaut und unsere Kunden scheinen es zu mögen bzw. es scheint ihnen egal zu sein, solange das Produkt hält was es verspricht. Neben den bereits genannten Enterprise Spezifikationen die zum Einsatz kommen, ist besonders die Beständigkeit hervorzuheben. Endlich wieder Zeit für die Business Logik.

Hans
Gast

Ihre Schilderung deckt sich mit meinen Erfahrungen.
Modern, performant, (ausreichend) skalierbar – alles problemlos möglich.

Es ist halt nicht „new shit“, sondern über Jahre konsequent weiterentwickelt und spezifiziert…

Jenson
Gast

Die Einleitung ist ein bisschen zu sehr populistisch oder es wurde nicht sauber recherchiert. Vom selben Entwicklerteam von Primefaces bekommt man mit PrimeNg auch eine Komponenten Bibliothek für Angular, die dem Jsf-Pendant in nichts nachsteht. Deswegen ergibt die Einleitung keinen Sinn. Die gleiche Einleitung könnte ich über Jsf schreiben in dem ich irgendwelche Noname Bibliotheken aufzähle.