Schablonen

Im voherigen Teil dieses Tutorials haben wir eine zweite Seite erstellt, die in ihrem Aufbau der ersten gleicht. Nun geht es darum, die gemeinsamen Teile auch gemeinsam zu nutzen. Wir werden daher eine Schablone (template) erstellen, welche die gemeinsamen Elemente enthält und in die von den einzelnen Seiten einfach der individuelle Teil hinein kopiert wird.

Zur Erinnerung, hier nochmals der Quelltext der beiden Seiten:

<?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>Tiny calculator</title>
    <h:outputStylesheet library="css" name="CalculatorStyle.css"/>
  </h:head>
  <h:body>
    <h1>Tiny calculator</h1>
    <h:graphicImage alt="BasicArithmetics"
                    library="images"
                    name="BasicArithmetics.png"/>
    <h:form id="calculator">
      <h:outputLabel styleClass="label" for="param1" value="Param 1:"/>
      <h:inputText id="param1" value="#{calculatorBean.param1}"/>
      <br/>
      <h:outputLabel styleClass="label" for="param2" value="Param 2:"/>
      <h:inputText id="param2" value="#{calculatorBean.param2}"/>
      <br/>
      <h:commandButton styleClass="button"
                       action="#{calculatorBean.add}"
                       value="add"/>
      <h:commandButton styleClass="button"
                       action="#{calculatorBean.substract}"
                       value="substract"/>
      <h:commandButton styleClass="button"
                       action="#{calculatorBean.multiply}"
                       value="multiply"/>
      <h:commandButton styleClass="button"
                       action="#{calculatorBean.divide}"
                       value="divide"/>
      <br/>
      <h:outputLabel styleClass="label" for="result" value="Result:"/>
      <h:outputText id="result" value="#{calculatorBean.result}"/>
    </h:form>
  </h:body>
</html>

(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">
  <h:head >
    <title>Tiny calculator</title>
    <h:outputStylesheet library="css" name="CalculatorStyle.css"/>
  </h:head>
  <h:body>
    <h:graphicImage alt="BasicArithmetics"
                    library="images"
                    name="BasicArithmetics.png"/>
    <h1>Tiny calculator</h1>
    <h:form id="calculator">
      <h:outputLabel styleClass="label" for="result" value="Result"/>
      <h:outputText id="result"
         value="#{calculatorBean.param1} +
                #{calculatorBean.param2} =
                #{calculatorBean.result}"/>
      <br/>
       <br/>
      <h:commandButton value="Back" action="index"/>
    </h:form>
  </h:body>
</html>

(result.xhtml)

Wenn Sie diese Seiten genau betrachten, werden Sie feststellen, dass diese sich lediglich innerhalb des Tags <h:form…> unterscheiden. Der größte Teil ist gleich, und so können wir eine der Seiten als Ausgangspunkt für das Template nutzen. Ein Template selbst ist nichts weiter als eine JSF-Seite, in der an bestimmten Stellen spezifischer Inhalt eingesetzt wird.

Legen Sie also eine neue JSF-Seite an, die Sie CalculatorTemplate.xhtml nennen und ersetzen den Quellcode durch den einer der vorhandenen Seiten. Oder Sie kopieren einfach eine der vorhandenen Seiten in der Baumansicht und nennen diese um.

Damit diese Seite nun zu einem Template wird, muss der individuelle Teil entfernt und durch eine Einfüge-Marke ersetzt werden. Ersetzen Sie einfach den gesamten Teil innerhalb des Formulars durch

 <ui:insert name="content">Content</ui:insert>

Der Namespace ui ist an dieser Stelle noch unbekannt. Dieser ist noch im Kopf zu deklarieren.

xmlns:ui="http://java.sun.com/jsf/facelets"

Der gesamte Quelltext von CalculatorTemplate.xhtml sieht nun so 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">
   <h:head >
       <title>Tiny calculator</title>
       <h:outputStylesheet library="css" name="CalculatorStyle.css"/>
   </h:head>
   <h:body>
       <h:graphicImage alt="BasicArithmetics"
                                       library="images"
                                       name="BasicArithmetics.png"/>
       <h1>Tiny calculator</h1>
       <h:form id="calculator">
           <ui:insert name="content">Content</ui:insert>
       </h:form>
   </h:body>
</html>

Nun sind noch unsere beiden Seiten so anzupassen, dass sie diese Schablone nutzen. Der Server muss dazu ein paar Dinge wissen, nämlich

  • Welches Template wird genutzt?
  • Welche Elemente sollen an Stelle der Einfügemarke eingefügt werden?

Für beide Aufgaben stellt JSF einen Tag bereit.

<ui:composition template="./CalculatorTemplate.xhtml">
<ui:define name="content">

Diese umklammern den individuellen Bereich der Seite und erfordern natürlich zusätzlich die Deklaration des Namensraums sowie jeweils das schließende Gegenstück. Schön zu sehen ist die Angabe der Template-Datei sowieder Name der Einfügemarke. Aber warum sind zwei Tags erforderlich? Hätten die Macher von JSF das nicht mit einem Tag erledigen können? Nun die Antwort ist recht einfach: In größeren Anwendungen könnte es vorkommen, dass Sie in einem Templkate an unterschiedlichen Stellen individuellen Inhalt einfügen möchten. Sie benötigen dann mehrere Einfügemarken, die jeweils über unterschiedliche Namen definiert werden. Es wird aber nachwie vor das selbe Template genutzt. Nutzung des Templates und Definition der Bereiche sind also unterschiedliche Aufgaben. Und jede Aufgabe hat ihr Tag.

Nach dieser Änderung sieht result.xhtml so 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">
   <h:head >
       <title>Tiny calculator</title>
       <h:outputStylesheet library="css" name="CalculatorStyle.css"/>
   </h:head>
   <h:body>
       <h:graphicImage alt="BasicArithmetics"
                                       library="images"
                                       name="BasicArithmetics.png"/>
       <h1>Tiny calculator</h1>
       <h:form id="calculator">
           <ui:composition template="./CalculatorTemplate.xhtml">
               <ui:define name="content">
                   <h:outputLabel styleClass="label" for="result" value="Result"/>
                   <h:outputText id="result"
                                               value="#{calculatorBean.param1} +
                                               #{calculatorBean.param2} =
                                               #{calculatorBean.result}"/>
                 <br/>
                 <br/>
                 <h:commandButton value="Back" action="index"/>
             </ui:define>
         </ui:composition>
       </h:form>
   </h:body>
</html>

Lassen Sie das Projekt laufen – es sollte sich genauso verhalten wie bisher.

Sollte dies nicht auf Anhieb der Fall sein, z.B. eine Meldung wie “Seite nicht gefunden” erscheinen, so führen Sie für Ihr Projekt ein “Clean” aus (z.B. In der Baumansicht über das Kontextmenü).

Nun, die Seite sieht nach dieser Änderung noch größer aus als bisher. Wozu also dieser Aufwand, wenn dies die Quelltexte noch voller macht? Nun, dies hat sich einfach aus der Historie unserer Anwendung ergeben. Bisher haben wir nur ein paar Tags zugefügt. Sinn der Sache war es aber, kleine Dateien zu erhalten, die möglichst nur den individuellen Teil umfassen. Und der Server nutzt auch nur den individuellen Teil! Fast alles außerhalb des composition Tags wird schlichtweg ignoriert. Insofern können Sie dies auf ein rudimentär gültiges HTML-Dokument verkürzen, ohne dass sich die Funktionalität ändert.

  <?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:body>
       <ui:composition template="./CalculatorTemplate.xhtml">
           <ui:define name="content">
               <h:outputLabel styleClass="label" for="result" value="Result"/>
               <h:outputText id="result"
                                           value="#{calculatorBean.param1} +
                                           #{calculatorBean.param2} =
                                           #{calculatorBean.result}"/>
               <br/>
               <br/>
               <h:commandButton value="Back" action="index"/>
           </ui:define>
       </ui:composition>
   </h:body>
</html>

Noch kürzer wird es als reines XML-Dokument mit dem Tag composition als Wurzel. Dazu müssen Sie dann die Namespace-Deklaration in dieses Element verschieben.

  <?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">
<ui:composition xmlns:h="http://java.sun.com/jsf/html"
                               xmlns:ui="http://java.sun.com/jsf/facelets"
                               template="./CalculatorTemplate.xhtml">
       <ui:define name="content">
               <h:outputLabel styleClass="label" for="result" value="Result"/>
               <h:outputText id="result"
                                           value="#{calculatorBean.param1} +
                                           #{calculatorBean.param2} =
                                           #{calculatorBean.result}"/>
               <br/>
               <br/>
               <h:commandButton value="Back" action="index"/>
       </ui:define>
</ui:composition>

Obwohl dieses Fragment in das Template eingefügt wird und somit die gesamte Seite ein korrektes HTML ergibt, liefert der Server beim ersten Aufruf Warnungen, für den Namespace “br” existiere keine Taglib. Die Breaks (<br/>) werden aber korrekt dargestellt. Möglicherweise liegt dies daran, dass der Server dieses Fragment interpretiert, bevor es in das Template eingefügt wird. Bei laufendem Server führen Folgeaufrufe der Seite dann nicht mehr zu diesen Warnungen. Erst nach erneutem Deployment, Serverstart etc. erscheinen diese Warnungen wieder. Bei Nutzung der rudimentären HTML-Syntax liefert GlassFish keine derartigen Warnungen, also werde ich im Folgenden dieses Format nutzen. Es sollte Ihnen nun nicht schwerfallen, auch die Seite index.xhtml entsprechend zu reduzieren. Versuchen Sie dies ruhig zu Ãœbungszwecken, bevor Sie einen Blick auf den folgenden Code werfen! So sollte es dann aussehen:

  <?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">
   <body>
       <ui:composition template="./CalculatorTemplate.xhtml">
           <ui:define name="content">
               <h:outputLabel styleClass="label" for="param1" value="Param 1:"/>
               <h:inputText id="param1" value="#{calculatorBean.param1}"/>
               <br/>
               <h:outputLabel styleClass="label" for="param2" value="Param 2:"/>
               <h:inputText id="param2" value="#{calculatorBean.param2}"/>
               <br/>
               <h:commandButton styleClass="button" 
                                                 action="#{calculatorBean.add}" 
                                                 value="add"/>
               <h:commandButton styleClass="button" 
                                                 action="#{calculatorBean.substract}" 
                                                 value="substract"/>
               <h:commandButton styleClass="button" 
                                                 action="#{calculatorBean.multiply}" 
                                                 value="multiply"/>
               <h:commandButton styleClass="button" 
                                                 action="#{calculatorBean.divide}" 
                                                 value="divide"/>
               <br/>
               <h:outputLabel styleClass="label" for="result" value="Result:"/>
                   <h:outputText id="result" value="#{calculatorBean.result}"/>
               </ui:define>
       </ui:composition>
   </body>
</html>

Naja, der Name index.xhtml passt nicht so ganz zu unserer Applikation. Wo kommt der Name eigentlich her? Nun, wenn Sie im Web eine Homepage ansteuern, geben Sie dann den vollen Dateinamen an? Oder nicht häufig einfach die URL der entsprechenden Domäne?. So erreichen Sie beispielsweise meine Homepage unter http://mueller.bruehl.de. Dies verweist auf meinen WebServer in ein bestimmtes Verzeichnis. Da aber nicht der Verzeichnisinhalt gelistet, sondern der Inhalt der Site angezeigt werden soll, sucht der HTTP-Server nach einer Datei wie index.html oder home.html oder ähnlich. Entsprechend hat NetBeans bei Erstellung unseres Projekts auch eine Startseite festgelegt, die bei Anwahl unserer Applikation angezeigt wird – ohne dass Sie den vollen Dateinamen angeben müssen. Um welche Datei es sich dabei handelt wird in der Konfiguration festgelegt.

Lassen Sie uns dies kurz durchspielen.

Benennen Sie im ersten Schritt die Seite index.xhtml um in calculator.xhtml. Falls Sie nun das Projekt starten sollten, so teilt Ihnen der Server mit, dass er die Seite nicht gefunden habe. Wie auch? GlassFish sucht noch immer nach index.xhtml, aber das gibt es nicht mehr…

Öffnen Sie nun im Inhaltsbaum den Ordner Configuration Files und doppelklicken dann web.xml. Im Editor wählen Sie dann den Bereich Pages. Nun können Sie im Feld Welcome Files den neuen Namen angeben – oder mittels [Browse…] sich auf die Suche begeben (und ggf. Index.xhtml anschließend entfernen).

Nachdem Sie dies angepasst haben, sollte Ihre Applikation wieder einwandfrei funktionieren. Der Editor ist nur ein nettes Frontend für die Konfigurationsdatei web.xml. Ein Klick auf den Bereich XML zeigt Ihnen die Datei im Original.

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" 
   xmlns="http://java.sun.com/xml/ns/javaee" 
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
   xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
       <context-param>
               <param-name>javax.faces.PROJECT_STAGE</param-name>
               <param-value>Development</param-value>
       </context-param>
       <context-param>
               <param-name>javax.faces.STATE_SAVING_METHOD</param-name>
               <param-value>client</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>*.xhtml</url-pattern>
       </servlet-mapping>
       <session-config>
               <session-timeout>
                       30
               </session-timeout>
       </session-config>
       <welcome-file-list>
               <welcome-file>calculator.xhtml</welcome-file>
       </welcome-file-list>
</web-app>

Im Abschnitt <welcome-file-list> finden Sie die eingestellte Startseite. Und wenn Sie genau hinschauen, finden Sie auch die bereits früher über den Assistenten eingestellte Endung *.xhtml als Endung unserer Facelets. Auf die Konfiguration werde ich später noch genauer eingehen.

Zurück zu den Templates. In diesem Abschnitt haben wir das Template und die individuellen Dateien (Template Clients) manuell vorhandenen Seiten erstellt. Mit dem Wissen um Schablonen werden Sie künftige Projekte wohl eher anders angehen: Sie erstellen zuerst den Rahmen (das Template) und dann die einzelnen Seiten, die dieses Template nutzen. Genau eine solche Vorgehensweise unterstützt NetBeans von Haus aus. Diese IDE bietet Ihnen zwei Assistenten, zum Erstellen eines Templates, sowie zum Erstellen eines Template-Clients.

Die Generierung des Templates bietet dabei noch einie nette Ergänzung: Sie können im Assistenten das gewünschte Layout wählen, z. B. Um eine Kopfzeile abzusetzen oder für eine seitliche Navigationsleiste etc. Dazu wählen Sie das gewünschte Layout einfach durch Anklicken im Assitenten.

NetBeans erstellt die Struktur und im Falle des CSS-Layouts auch gleich passende Style Sheets. Mit den abgebildeten Angaben generiert NetBeans den folgenden Code:

  <?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:ui="http://java.sun.com/jsf/facelets"
           xmlns:h="http://java.sun.com/jsf/html">

       <h:head>
               <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
               <link href="./resources/css/default.css" rel="stylesheet" type="text/css" />
               <link href="./resources/css/cssLayout.css" rel="stylesheet" type="text/css" />
               <title>Facelets Template</title>
       </h:head>

       <h:body>

               <div id="top">
                       <ui:insert name="top">Top</ui:insert>
               </div>

               <div id="content">
                       <ui:insert name="content">Content</ui:insert>
               </div>

               <div id="bottom">
                       <ui:insert name="bottom">Bottom</ui:insert>
               </div>

       </h:body>

</html>

Hier können Sie schön die drei Bereiche erkennen, die Sie nun mit Ihren Clients füllen können. Wählen Sie das Layout immer entsprechend Ihren Anforderungen an die Seite, nicht entsprechend den Anforderungen an austauschbare Inhalte! Wenn also beispielsweise Kopf- und Fußbereich für alle Seiten gleich sein sollen, wählen Sie dieses Layout und Sie erhalten ein passendes Grundgerüst für Ihre Entwicklung. Die jeweiligen Insert-Tags ersetzen Sie dann durch Ihre festen Inhalte.

Der zweite der genannten Assitenten verlangt von Ihnen die Angabe des zu nutzenden Templates. Der generierte Client enthält dann Definitionen für jedes Insert-Tag, dass zu diesem Zeitpunkt im Template vorhanden ist.

Ein Template darf selbst Client eines anderen Templates sein. Auf diese Art und Weise können Sie Templates beliebig schachteln. Bei der Arbeit mit Schablonen nutzt ein Client also immer eine “vordefinierte” Umgebung. Der Client für ein Insert-Tag besteht aus den Komponenten innerhalb des Define-Tags. Sie können sich dies auch als eine zusammengesetze Komponente vorstellen. Was, wenn Sie eine solche Komponente an mehreren Stellen nutzen möchten? Wäre es nicht schön, in verschiedenen Seiten auf zusammengesetzte (“vordefinierte”) Komponenten zugreifen zu können? Auch dies ist mit JSF möglich. Hierfür gibt es die Tags composition und component. Es ist sogar möglich, eine eigene Komponente so zu erstellen, dass sie parametrisierbar wird. Dies in einem späteren Teil des Tutorials.

Zunächst aber etwas anders. Der kurze Blick in die Konfiguration sollte zeigen: Es gibt noch einiges neben der Seitendefinition und dem Java-Code in den Beans. Was passiert eigentlich, wenn der Browser eine Seite anfordert? Werfen wir im nächsten Teil also zunächst einmal einen Blick hinter die Kulissen.