26.01.2013

JSF-Templates

In diesem Artikel wird erklärt, was JSF-Templates sind und wie man sie verwendet, um Web-Anwendungen einfacher und wartbarer zu gestalten. Außerdem wird eine Möglichkeit vorgestellt, wie sich Servlets und JSP-Seiten in JSF-Anwendungen einbetten lassen, so dass "Legacy"-Seiten, bis zu ihrer Ablösung, auch in neu entwickelten JSF-Webanwendungen verwendet werden können.

In den Artikeln zu Servlets, JSP-Seiten und JSF-Seiten wurden jeweils WebClients mit unterschiedlichen Technologien erstellt. Hier sollen jetzt alle diese Web-Clients in einer großen Shopping-Cart-Anwendung vereinigt werden. Der folgende Screenshot zeigt die Einstiegsseite der fertigen Anwendung im Browser:

Create New Project

Auf der linken Seite läßt sich über ein Menü eine der Implementierungen auswählen. Wird beispielsweise der JSP+JSTL-Menüpunkt ausgewählt, dann wird der entsprechende Warenkorb angezeigt:

Create New Project

Die Gesamtseite wird mit Hilfe von <div>-Elementen strukturiert. Auf der folgenden Abbildung sind die <div>-Elemente eingezeichnet.

Create New Project

Als erstes wird nun für jedes dieser <div>-Elemente eine Datei angelegt. Die Dateien werden im web-Ordner des Projekts unter WEB-INF/templates gespeichert, wie der folgende Screenshot zeigt:

Create New Project

Anmerkung: Auf diesem Ordner templates hat der Benutzer von "außen" keinen Zugriff. D.h. vom Browser aus, kann auf keine dieser Seiten zugegriffen werden. Nur die JSF-Seiten, die wir weiter unter noch erstellen, können die Template-Dateien lesen und verwenden.

Als erstes legen wir die Datei top.xhtml an.

<?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">
    <h:head>
        <title>Shopping Carts</title>
    </h:head>
    <h:body>
        <h:panelGrid columns="2">
            <h:graphicImage library="images" name="cart.png" height="120px"/>
            <h:panelGroup>
                <h1>Shopping-Carts</h1>
                <h2>#{subtitle}</h2>         
            </h:panelGroup>
        </h:panelGrid>
    </h:body>
</html>

Das Top-Element besteht aus zwei Spalten. Links wird ein Bild cart.png mit Hilfe des Elements graphicImage eingefügt und rechts verwenden wir eine panelGroup, um die Überschrift anzuzeigen. Besonderheit ist der Platzhalten #{subtitle}. Durch ihn wird es möglich, dass der Benutzer des Templates in die Lage versetzt wird, einen zweiten kleineren Text anzuzeigen. Wenn die Variable subtitle leer ist, wird das Element einfach ignoriert.

Ein subtitle-Beispiel ist auf dem ersten Screenshot, der die Hauptseite zeigt, zu sehen. Dort wird kein Untertitel angezeigt. Auf dem zweiten Screenshot, der den JSP-JSTL-Warenkorb anzeigt, ist ein Untertitel zu sehen.

Die Datei footer.xhtml ist ähnlich aufgebaut, wobei auf zusätzliche JSF-Komponenten verzichtet wurde:

<?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">
    <h:head>
        <title>Shopping Carts</title>
    </h:head>
    <h:body>
        Copyright by Jörn Hameister - <a href="http://www.hameister.org">http://www.hameister.org</a>
    </h:body>
</html>

Die Datei navigation.xhtml enthält, wie der Name schon sagt, das Navigationsmenü auf der linken Seite der Anwendung:

<?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">
    <h:head>
        <title>Shopping Carts</title>
    </h:head>
    <h:body>
        <h:form>
            <h:panelGrid columns="1">
                <h:commandLink value="Übersicht" action="index"/>
                <h:commandLink value="Servlet" action="servlet-page"/>
                <h:commandLink value="JSP" action="jsp-page"/>
                <h:commandLink value="JSP+JSTL" action="jspjstl-page"/>
                <h:commandLink value="JSF" action="jsf-page"/>
                <h:commandLink value="JSF+Ajax" action="jsfajax-page"/>
            </h:panelGrid>
        </h:form>
    </h:body>
</html>

Die Links zu den Unterseiten sind mit commandLink-Elementen realisiert. Zum Formatieren wird ein panelGrid mit der Spaltenbreite 1 verwendet. Was auffällt ist, dass in dem action-Attribut die Dateiendung der Zielseite fehlt. Der Grund dafür ist, dass auf diese Weise, sowohl Dateien mit xhtml-Endung verwendet werden können, aber später auch eine faces-config-Datei erstellt werden kann, die die Navigationsregeln enthält.

Der Vollständigkeit halber und als "Fallback" wird noch eine Seite angelegt, die keinen Inhalt enthält. Die Datei bekommt den Namen content.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">
    <h:head>
        <title>Shopping Carts</title>
    </h:head>
    <h:body>
        NO Content!
    </h:body>
</html>

Nachdem jetzt eine ganze Reihe von einzelnen Seiten angelegt wurden, wird noch ein Haupttemplate benötigt, welches die Unterseiten einbettet. Dazu wird die Datei main.xhtml erstellt:

<?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:ui="http://java.sun.com/jsf/facelets">
    <h:head>
        <title>Shopping Carts</title>
        <h:outputStylesheet library="css" name="cart.css"/>
    </h:head>
    <h:body>
        <div id="wrapper">
            <div id="top" class="top">
                <ui:insert name="top">
                    <ui:include src="top.xhtml"/>
                </ui:insert>
            </div>

            <div>
                <div id="navigation" class="navigation">
                    <ui:insert name="navigation">
                        <ui:include src="navigation.xhtml"/>
                    </ui:insert>
                </div>
                <div id="content" class="content">
                    <ui:insert name="content">
                        <ui:include src="content.xhtml"/>
                    </ui:insert>
                </div>
            </div>
            <div id="footer" class="footer">
                <ui:insert name="footer">
                    <ui:include src="footer.xhtml"/>
                </ui:insert>
            </div>
        </div>
    </h:body>
</html>

Das Haupttemplate verwendet das Element ui:insert, um die einzelnen Unterseiten einzubetten. Die Platzierung der Unterseiten wird durch <div>-Elemente und durch die mit h:outputStylesheet inkludierte css-Datei cart.css erreicht.

#wrapper {
  background: white;
  color: black;
  width: 1200px;
  margin: 10px auto;
}

#navigation {
   background-color: #97ff83;
  float: left;
  width: 110px;
  padding-left: 20px;
  padding-top: 20px;
}

#navigation a {
    display: block;
    text-decoration: none;
    color: black;
    background-color: #2aff00;
    padding: 4px;
    border-left: 3px solid #2d8f1a;
}

#navigation a:hover,
#navigation a:focus {
    color: black;
    background-color: white;
    border-left-color: #d90000;
    border-bottom: none;
}
#navigation a:active {
    color: black;
    background-color: #d9d9d9;
}  
  
#top {
  position: relative;
  background: #baffac;
  color: black;
  padding: 10px 20px 10px 20px;
}

#content {
    padding: 50px 50px 50px 50px;
    padding-left: 160px;
    background-color: #2aff00;
}

#footer {
  clear:both;
  background-color: #baffac;
  color: black;
  padding: 10px 20px 20px 20px;
  border-top: 1px solid #8c8c8c;
  margin-top: 0;
  text-align: right;
}

TABLE.cartTable {
    width: 1000px;
}

.cartSelect {
    width: 400px;
}

.cartHeader{
    text-align:center;
    font-size: 16px;
    font-weight: bold;
}
  
.cartRows{
    text-align:center;
    background:none repeat scroll 0 0 #97ff83;
    border-top:1px solid #BBBBBB;
}

Nachdem alle Templates erstellt wurden, wird eine JSF-Hauptseite benötigt, die das Template main.xhtml verwendet. Die Hauptseite ist auf dem ersten Screenshot oben zu sehen ist. Der folgende Quellcode zeigt die Datei 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:ui="http://java.sun.com/jsf/facelets">
    <h:head>
        <title>Shopping Carts</title>
    </h:head>
    <h:body>
        <ui:composition template="WEB-INF/templates/main.xhtml">
            <ui:define name="content">
                Hier findet man Beispiele, wie ein Einkaufswagen mit Java EE umgesetzt werden kann. 
                <ul>
                    <li>Als Servlet</li>
                    <li>Als JSP-Seite</li>
                    <li>Als JSP-Seite, die JSTL vervendet</li>
                    <li>Als JSF-Seite</li>
                    <li>Als JSP-Seite mit Ajax-Unterstützung</li>
                </ul> 
            </ui:define>
        </ui:composition>
    </h:body>
</html>

Durch das Element ui:composition wird das Template main.xhtml eingebunden. Mit ui:define kann das Untertemplate content mit Inhalt gefüllt werden. Wenn man den ui:define-Block weglassen würde, dann wäre das Default-Template für content (content.xhtml) zum Einsatz gekommen.

Nun können die einzelnen Implementierungen der Einkaufswagen integriert werden. In dem Template navigation.xhtml wurde das schon durch die action-Attribute vorbereitet.

Die Integration der reinen JSF-Seiten ist sehr einfach, denn die Seite muss nur um die Template-Elemente erweitert werden. Analog zu der Seite index.xhtml.

Eine zusätzliche Randbedingung ist, dass die JSF-Seiten im Web-Ordner liegen und der Name im action-Attribut mit dem Dateinamen übereinstimmt. Vergleiche dazu navigation.xhtml und den Screenshot der Verzeichnisstruktur des Projekts.

Für die Datei jsf-page.xml sieht das folgendermaßen aus:

<?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:ui="http://java.sun.com/jsf/facelets"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:c="http://java.sun.com/jsp/jstl/core">
    <h:head>
        <title>Shopping Carts</title>
    </h:head>
    <h:body>
        <ui:composition template="WEB-INF/templates/main.xhtml">
            <ui:define name="top">
                <ui:include src="WEB-INF/templates/top.xhtml">
                    <ui:param name="subtitle" value="JSF-Beispiel"/>
                </ui:include>
            </ui:define>

            <ui:define name="content">
                <h:form>
                    <c:choose>
                        <c:when test="${not empty cartJSF.items}">
                            <h2>Artikel im Warenkorb:</h2>
                        </c:when>
                    </c:choose>
                    <h:dataTable value="#{cartJSF.items}" var="item">
                        <h:column>
                            &dot; #{item}
                        </h:column>
                    </h:dataTable>
                    <strong>Artikel zum JSF-Warenkorb hinzufügen:</strong><br/><br/>
                    <h:inputText value="#{cartJSF.item}"/>
                    <h:selectOneListbox size="1" value="#{cartJSF.quantity}">
                        <f:selectItem itemLabel="1" itemValue="1" />
                        <f:selectItem itemLabel="2" itemValue="2" />
                        <f:selectItem itemLabel="3" itemValue="3" />
                        <f:selectItem itemLabel="4" itemValue="4" />
                        <f:selectItem itemLabel="5" itemValue="5" />
                    </h:selectOneListbox>
                    <h:commandButton value="Hinzufügen" action="#{cartJSF.add()}"/><br/>
                    <h:commandButton value="Warenkorb leeren" action="#{cartJSF.reset()}"/>
                </h:form>
            </ui:define>
        </ui:composition>
    </h:body>
</html>

Analog wird auch die Seite jsfajax-page.xhtml angepaßt:

<?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:ui="http://java.sun.com/jsf/facelets"
      xmlns:f="http://java.sun.com/jsf/core">
    <h:head>
        <title>Shopping Carts</title>
    </h:head>
    <h:body>
        <ui:composition template="WEB-INF/templates/main.xhtml">
            <ui:define name="top">
                <ui:include src="WEB-INF/templates/top.xhtml">
                    <ui:param name="subtitle" value="JSF+Ajax-Beispiel"/>
                </ui:include>
            </ui:define>

            <ui:define name="content">
                <h:form id="shoppingCartForm">
                    <h:panelGrid styleClass="cartTable" columns="3" rowClasses="cartRows">
                        <h:outputLabel value="Artikel Liste" styleClass="cartHeader"/>
                        <h:outputLabel value="" styleClass="cartHeader"/>
                        <h:outputLabel value="Warenkorb" styleClass="cartHeader"/>

                        <h:selectManyListbox id="itemList" value="#{cartJSFAjax.selectedItems}" styleClass="cartSelect" size="4">
                            <f:selectItems value="#{cartJSFAjax.items}"/>
                        </h:selectManyListbox>  

                        <h:panelGroup id="buttons">
                            <h:commandButton id="removeButton" value="←" actionListener="#{cartJSFAjax.removeFromShoppingCart(e)}">
                                <!-- execute contains informations for the server; selected items of the selectManyListbox with the id cartItemList; call actionlistener -->
                                <!-- render contains all elements which must be rendered after the change -->
                                <f:ajax execute="shoppingCartForm:cartItemList" render="shoppingCartForm:itemList shoppingCartForm:cartItemList"/>
                            </h:commandButton>
                            <h:commandButton id="addButton" value="→" actionListener="#{cartJSFAjax.putInShoppingCart(e)}">
                                <f:ajax execute="shoppingCartForm:itemList" render="shoppingCartForm:itemList shoppingCartForm:cartItemList"/>
                            </h:commandButton>
                        </h:panelGroup>

                        <h:selectManyListbox id="cartItemList" value="#{cartJSFAjax.selectedCartItems}" styleClass="cartSelect" size="4">
                            <f:selectItems value="#{cartJSFAjax.shoppingCart}"/>
                        </h:selectManyListbox>  

                        <h:outputLabel value=""/>
                        <h:outputLabel value=""/>
                        <h:commandButton id="resetButton" value="Warenkorb leeren" actionListener="#{cartJSFAjax.reset}">
                            <f:ajax execute="shoppingCartForm:cartItemList shoppingCartForm:itemList" render="shoppingCartForm:itemList shoppingCartForm:cartItemList"/>
                        </h:commandButton>
                    </h:panelGrid>

                </h:form>

            </ui:define>
        </ui:composition>
    </h:body>
</html>

Komplizierter wird es bei der Integration des Servlets und der JSP-Seiten. Das Servlet hat überhaupt keine Seite, die integriert werden kann und die JSP-Seiten können nicht auf direkten Wege eingebunden werden.

Um dieses Problem zu lösen, gibt es mehrere Möglichkeiten. In dem Artikel Facelets and legacy JSP wird eine Lösung mit Custom-Components beschrieben, bei noch etwas Arbeit notwendig ist, damit die JSP-Seiten eingebettet werden können. Deshalb wird in diesem Artikel die Hilfsbibliothek OmniFaces verwendet, um die Seiten zu inkludieren. Um die Bibliothek zu verwenden, muss einfach die Datei omnifaces.jar im lib-Verzeichnis abgelegt werden, so dass die Datei vom ApplicationServer/WebContainer gefunden wird.

Für das Servlet wird eine JSF-Seite servlet-page.xhtml mit folgendem Inhalt angelegt:

<?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:ui="http://java.sun.com/jsf/facelets"
      xmlns:o="http://omnifaces.org/ui">
    <h:head>
        <title>Shopping Carts</title>
    </h:head>
    <h:body>
        <ui:composition template="WEB-INF/templates/main.xhtml">
            <ui:define name="top">
                <ui:include src="WEB-INF/templates/top.xhtml">
                    <ui:param name="subtitle" value="Servlet-Beispiel"/>
                </ui:include>
            </ui:define>
            
            <ui:define name="content">
                <o:resourceInclude path="/CartServlet" />
            </ui:define>
        </ui:composition>
    </h:body>
</html>

Zu beachten ist dabei das OmniFaces-Element <o:resourceInclude>. Es referenziert über das Attribut path direkt das Servlet.

Das referenziert Servlet ist in dem Artikel Servlets - Java EE6 zu finden.

Die JSP-Seiten werden auf ähnliche Weise integriert. Die XHTML-Seite jsp-page.xhtml sieht folgendermaßen aus:

<?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:ui="http://java.sun.com/jsf/facelets"
      xmlns:o="http://omnifaces.org/ui" >
    <h:head>
        <title>Shopping Carts</title>
    </h:head>
    <h:body>
        <ui:composition template="WEB-INF/templates/main.xhtml">
            <ui:define name="top">
                <ui:include src="WEB-INF/templates/top.xhtml">
                    <ui:param name="subtitle" value="JSP-Beispiel"/>
                </ui:include>
            </ui:define>
            
            <ui:define name="content">
                <o:resourceInclude path="/jspcart.jsp" />
                
            </ui:define>
        </ui:composition>
    </h:body>
</html>

Auch hier wird wieder das OmniFaces-Element <o:resourceInclude> verwendet. Diesmal wird allerdings die JSP-Seite über das Attribut path referenziert.

Die referenziert JSP-Seite ist in dem Artikel Java Server Pages - JSPs zu finden.

Bei der JSP-Seite, die noch die JSTL verwendet, geht man analog vor. Es wird eine Datei jspjstl-page.xhtml angelegt, die die JSP-Seite referenziert:

<?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:ui="http://java.sun.com/jsf/facelets"
      xmlns:o="http://omnifaces.org/ui" >
    <h:head>
        <title>Shopping Carts</title>
    </h:head>
    <h:body>
        <ui:composition template="WEB-INF/templates/main.xhtml">
            <ui:define name="top">
                <ui:include src="WEB-INF/templates/top.xhtml">
                    <ui:param name="subtitle" value="JSP+JSTL-Beispiel"/>
                </ui:include>
            </ui:define>
            
            <ui:define name="content">
                <o:resourceInclude path="/jspjstlcart.jsp" />
            </ui:define>
        </ui:composition>
    </h:body>
</html>

Die referenziert JSP-Seite ist in dem Artikel Java Server Pages mit JSTL zu finden.

Als letztes müssen noch die Java-Klasse der Servlets und der beiden JSP-Seiten angepaßt werden. In den vorherigen Artikeln wurde jeweils nach der Abarbeitung der Methode protected void processRequest(HttpServletRequest request, HttpServletResponse response) auf eine JSP-Seite, bzw. beim Servlet überhaupt nicht weitergeleitet. Beim Einbetten in die JSF-Anwendung muss allerdings wieder auf die JSF-Seite umgeleitet werden.

Für die JSP-Seite sieht das folgendermaßen aus:

protected void processRequest(HttpServletRequest request, HttpServletResponse response) {
...
//RequestDispatcher requestDispatcher = request.getRequestDispatcher("jspcart.jsp");
//requestDispatcher.forward(request, response);
    
//Change    
response.sendRedirect("jsp-page.xhtml");
...
}

Für das Servlet und die andere JSP-Seite muss die Umleitung auch eingebaut werden, so dass nach der Abarbeitung der Methode wieder zurück auf die JSF-Seite gesprungen wird. (Servlet -> servlet-page.xhtml; JSP-JSTL -> jspjstl-page.xhtml; JSP -> jsp-page.xhtml).

Weitere Informationen

Weiter Informationen zu Web-Clients findet man in den folgenden Artikeln:

  1. Einleitung - Teil 1
  2. Servlets - Teil 2
  3. JSP-Seiten - Teil 3
  4. JSP-Seiten mit JSTL - Teil 4
  5. JSF-Seiten - Teil 5
  6. JSF-Seiten mit Ajax - Teil 6
  7. JSF-Templates - Teil 7