Skinning mit z3c.layout, z3c.pagelet, und z3c.template

Im nächsten Schritt erstellen Sie eine schöne Oberfläche (Skin) für die Anwendung. Im normalen Zope 3 werden dafür Makros definiert, die in allen Page-Templates verwendet werden. Typischerweise sind die Makros in einer standard_macros-Datei definiert. Mit z3c.*-Komponenten machen Page-Templates von diesen Makros keinen Gebrauch mehr.

Abhängikeiten

Das Wichtigste zuerst: Sie konfigurieren die neuen Abhängigkeiten, die mit dem Skinning verbunden sind.

Öffnen Sie die Datei src/zcontact/configure.zcml und fügen Sie <include package="z3c.pagelet" file="meta.zcml" /> zu den anderen “meta includes” hinzu. Daraus ergibt sich eine neue z3c:pagelet-zcml-Direktive. Weiter unten können Sie zwei weitere Include-Anweisungen einfügen:

<include package="z3c.pagelet" />
<include package="zope.contentprovider" />

Erstellung des Layouts

Jetzt folgt der interessante Teil. Sie erstellen ein sogenanntes Layout, welches wie ein Makro funktioniert aber einfacher ist. Anstatt ein kompliziertes Makro zu erstellen, das von allen Seiten (z. B. mit use-macro und fill-slot) benutzt werden muss, wird hier ein normales Page-Template mit einem speziellen Satz von Elementen für den jeweils aktuellen Inhalt der Seite erstellt. Sie können dann unterschiedliche Layouts für unterschiedliche Skins oder auch verschiedene Objekt-Interfaces verwenden. Los gehts! Erstellen Sie die Datei src/zcontact/layout.pt und lassen Sie den Inhalt etwa wie folgt aussehen. Experimentieren Sie ruhig ein wenig (Kreativität ist gefragt):

<!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" xml:lang="en" lang="en">
  <head>
    <title>ZContact</title>

    <style type="text/css">
      <!--
       * {
         margin: 0;
         padding: 0;

       }
       body {
         padding: 1em;
       }
       #content{
         border: 1px solid #999;

         padding: 1em;
         background: #eee;
       }
      -->
    </style>
  </head>

  <body>
    <h1>ZContact</h1>

    <!-- Die Magie findet in der naechsten Zeile statt! -->
    <div id="content"

         tal:content="structure provider:pagelet">Page Content</div>

    <i>ZContact Tutorial Application</i>
  </body>
</html>

Beachten Sie die zwei Zeilen am Ende der Datei die sagen, das hier die Magie enthalten ist.

<!-- Die Magie findet in der naechsten Zeile statt! -->
<div id="content"
     tal:content="structure provider:pagelet">Page Content</div>

Der provider:pagelet-Teil fordert zope auf, nach einem content provider zu suchen, der pagelet genannt wird. Mit z3c.pagelet wird der Inhalt der aufgerufenen Seite geliefert, ob es nun ein Formular oder ein anderes Page-Template ist. Und das ist ziemlich raffiniert, aber dazu muss das Layout für unseren Skin mit zcml registriert werden. Ergänzen Sie die Datei zcontact/skin.zcml und fügen Sie die selbsterklärenden Zeilen ein:

<z3c:layout
    for="*"

    layer="zcontact.layer.IZContactBrowserLayer"
    template="layout.pt"
    />

Vergessen Sie nicht den z3c-Namensraum in die Konfigurationsdatei mit xmlns:z3c="http://namespaces.zope.org/z3c" einzufügen.

Die Verwendung des Layouts in Pagelets (und Templates)

Ist das Layout erstellt, müssen die existierenden Seiten ein wenig angepasst werden, damit das Layout auch verwendet wird. Der erste Schritt ist das Hinzufügen des z3c-Namensraumes xmlns:z3c="http://namespaces.zope.org/z3c" in der Datei zcontact/browser/configure.zcml. Nun muss jedes page-Element durch z3c:pagelet ersetzt werden. Alles andere bleibt unverändert. Das Besondere an den Pagelets ist (und damit unterscheiden sie sich von normalen Page-Templates), sie haben nichts mit Page-Templates zu tun. Die pagelet-Direktive erhält kein template-Attribut und benötigt statt dessen ein Klassen-Attribut. Für die meisten Seiten mit automatisch generiertem Inhalt ist das kein Problem. Für die Startseite verwendet man ein eigenes Template.

Ersetzen Sie die Konfiguration für die Startseite durch die folgenden Zeilen:

<z3c:pagelet
    name="index.html"
    for="zope.app.folder.interfaces.IRootFolder"
    permission="zope.Public"

    layer="zcontact.layer.IZContactBrowserLayer"
    class=".contact.FrontPage"
    />

Beachten Sie die Veränderung. Das template-Attribut wurde durch das class-Attribut ersetzt, wobei die FrontPage-Klasse noch erstellt werden muss. Um die Skin-Problematik abzuschließen, erstellen Sie diese Klasse indem Sie die am Ende der Datei zcontact/browser/contact.py folgenden Quellcode einfügen:

class FrontPage(BrowserPagelet):
    """Pagelet for the front page."""

Gleichzeitig wird die Import-Anweisung from z3c.pagelet.browser import BrowserPagelet am Anfang der Datei benötigt.

Sie werden nun vielleicht denken: “Was passiert mit unserem Template und die Klasse ist irgendwie ohne Funktionalität”. Weil die Klasse jedoch von BrowserPagelet erbt, besitzt sie eine doch eine gewisse Funktionalität. Die BrowserPagelet-Klasse liefert eine spezielle Methode __call__ welche einen Adapter sucht, der wiederum das Template findet. Sie werden sagen: “Ein Adapter um ein Template zu finden? Bedeutet das, wir können unterschiedliche Templates für die gleiche Seite(Pagelet) verwenden?” Die Antwort ist ein eindeutiges JA. Sie können das Template für das Pagelet sowohl in der Klasse (unter Verwendung von template = ViewPageTemplateFile('frontpage.pt')) als auch durch Registrierung des Templates mit zcml, einem speziellen Skin zuordnen. Zur Demonstration verwenden Sie die letztere Variante und fügen zu diesem Zweck in zcontact/browser/configure.zcml die folgende Directive unter dem “front page”-Pagelet ein:

<z3c:template
    template="frontpage.pt"
    for=".contact.FrontPage"
    layer="zcontact.layer.IZContactBrowserLayer"
    />

Die Verwendung von Layouts mit Forms

Es ist noch ein Problem zu lösen, bevor unsere Applikation das Licht der Welt erblicken kann,

Die z3c.form-Komponente wurde für die normale Nutzung entworfen, ohne den neuen Layout- und Pagelet-Mechanismus benutzen zu müssen (eine gute Sache). Leider liefert die z3c.formui-Komponente Unterstützung für ein eigenes form-Modul. Damit unsere Formulare das neue Layout verwenden, müssen wir unsere import-Anweisungen austauschen, statt from z3c.form import form, verwenden wir nun from z3c.formui import form. Die import-Anweisungen am Anfang der Datei zcontact/browser/contact.py sollten nun so aussehen:

from z3c.form import field, button

from z3c.form.interfaces import DISPLAY_MODE
from z3c.formui import form
from z3c.pagelet.browser import BrowserPagelet

from zope.traversing.browser.absoluteurl import absoluteURL
from zope.traversing.api import getParent, getName

from zcontact import interfaces
from zcontact.contact import Contact

Das schließt die Skin-Transformation ab und Sie sollten nach dem Server-Neustart einen schönen Skin sehen, wenn Sie das beschriebenene CSS verwendet haben.

Hier ein paar Bildschirmfotos der neuen Seiten:

skinFrontPageScreenShot.png skinAddFormScreenShot.png

Menüs für die z3c-Komponenten

Zur Zeit ist es schwierig, zwischen den Seiten der Applikation zu wechseln. Es wäre von Vorteil, wenn auf jeder Seite die gleichen Links erscheinen würden, um zwischen der Startseite und dem Hinzufügen-Formular wechseln zu können. Sie können keine hardcodierten Links in das Layout-Template einfügen, weil nicht bekannt ist, die URL auf dem Server aufgebaut ist. Auch ändern sich die relativen Pfadangaben, in Abhängigkeit von der gerade betrachteten Seite. Deshalb wird so etwas wie z3c.menu benötigt.

Die Komponente z3c.menu ist ein einfaches Paket zur Erstellung von Menüs und enthält nur wenige Hilfs-Klassen.

Die wirkliche Stärke steckt in der Zope-Kern-Komponente zope.viewlet. Die Idee ist, nicht mit den alten zope-Menüs zu arbeiten, die recht unflexibel sind, sondern mit der Verwendung von Viewlets auf einfache Art Menüs zu definieren und zu entscheiden, wann und wo sie zur Anzeige kommen. Ihr Navigationsmenü wird ein Viewlet-Manager sein und jeder Link im Menü wird als Viewlet definiert. Falls Sie noch nie mit Viewlets gearbeitet haben, werde ich die Funktionweise nun erklären.

Einen Viewlet-Manager definieren

Was ist ein Viewlet-Manager? In einem Satz gesagt, repräsentiert er einen Bereich auf einer Web-Seite, in dem dynamisch generierter Inhalt platziert wird. Ein einfaches Beispiel ist ein in vielen Blogs reservierter Bereich zum platzieren eines Bildes für den Blogger, eine kurze Beschreibung des Blogs, eine Liste der aktuellsten Beiträge, ein kleiner Kalender und vielleicht eine Region mit Tag’s. Jeder der genannten Teile gilt als ein Viewlet (stellen Sie sich eine Mini-Anzeige vor), die von dem Viewlet-Manager gesammelt und zusammengehalten werden.

Um einen Viewlet-Manager zu erstellen, öffnen Sie die Datei src/zcontact/skin.py und fügen die folgenden Zeilen hinzu:

from zope.viewlet.interfaces import IViewletManager

from zope.viewlet.manager import WeightOrderedViewletManager

class INavigationMenu(IViewletManager):
    """Navigation Menu Viewlet Manager."""

class NavigationMenu(WeightOrderedViewletManager):
    zope.interface.implements(INavigationMenu)

Wenn Sie Viewlets erstellen, werden diese für einen bestimmten Viewlet-Manger und den dazugehörigen Interface registriert. Deshalb wird ein eigenes Interface INavigationMenu für das Navigationsmenü erstellt, das wiederum vom IViewletManager (Zeilen 4-5) erbt. Als nächstes Implementieren Sie das Interface INavigationMenu (Zeilen 7-8). Die WeightOrderedViewletManager-Klasse kann Viewlets nach einer gegebenen Wichtung sortieren, was für ein Navigationsmenü recht nützlich ist. Als nächstes registrieren Sie diese Viewlet-Manager-Instanz in zcml, damit es von einem Page-Template verwendet werden kann. Fügen Sie die folgenden Registrierungs-Anweisungen in der Datei src/zcontact/skin.zcml ein:

<browser:viewletManager
    name="INavigationMenu"
    provides="zcontact.skin.INavigationMenu"
    class="zcontact.skin.NavigationMenu"
    layer="zcontact.layer.IZContactBrowserLayer"

    permission="zope.Public"
    />

Auch hier muss der browser-Namensraum xmlns:browser="http://namespaces.zope.org/browser" ergänzt werden.

Nun haben Sie einen Viewlet-Manager als Verwalter von Menüeinträgen definiert. Dieser Viewlet-Manger wird nun in den Skin integrieren, indem wir die Datei src/zcontact/layout.pt bearbeiten. Der folgende Schnipsel kann überall dort platziert werden, wo das Menü später erscheinen soll. Sie können es zum Beispiel rechts unter dem Header erscheinen lassen.

<div tal:content="structure provider:INavigationMenu">Navigation Menu</div>

Viewlets hinzufügen

Nachdem der Viewlet-Manager platziert ist, können Sie Viewlets (Menü-Einträge) hinzufügen. Dafür ist lediglich die Registierung der Viewlets genau dort notwendig, wo die Pagelets als Ziele der Navigationslinks definiert wurden. Deshalb öffnen Sie die Datei zcontact/browser/configure.zcml und ergänzen die beiden Konfigurationsblöcke:

<viewlet
    name="Add Contact"
    viewURL="@@addContact.html"
    for="*"
    manager="zcontact.skin.INavigationMenu"

    class="z3c.menu.simple.menu.GlobalMenuItem"
    permission="zope.Public"
    layer="zcontact.layer.IZContactBrowserLayer"
    weight="2"
    />

<viewlet
    name="Contact List"
    viewURL="@@index.html"
    for="*"
    manager="zcontact.skin.INavigationMenu"

    class="z3c.menu.simple.menu.GlobalMenuItem"
    permission="zope.Public"
    layer="zcontact.layer.IZContactBrowserLayer"
    weight="1"
    />

Das Attribut name wird für die Link-Beschriftung verwendet und viewURL definiert die relative URL von der Wurzel der Anwendung zu den entsprechenden Ansichten. Dann setzen Sie noch das Attribut manger auf das INavigationMenu-Interface, und endlich wird das erste Teil von z3c.menu unter Verwendung der Klasse GlobalMenuItem für das Viewlet sichtbar. Diese Klasse ermittelt den ersten Teil der URL und fügt den im viewURL-Attribut definierten Wert am Ende an.

Wenn alles vollendet ist, können Sie den Server neu starten. Ein neues Menü erscheint, das auf allen Seiten der Anwendung verfügbar ist.

Wie immer auch hierfür ein Bildschirmfoto:

menuScreenShot.png