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:
z3c.zrtresource und CSS-Viewlets
Zu Beginn der Erstellung des Layouts für ZContact begannen, haben Sie einfach das CSS-Stylesheet in das Layout integriert. Das ist im allgemeinen ein schlechter Stil. Wie man es besser macht wird jetzt erklärt. Die CSS-Anweisungen sollen nun in einer separaten Datei untergebracht werden. Sie erreichen das, indem Sie die nächste z3c-Komponente verwenden: z3c.zrtresource.
Eine Herausforderung bei der Entwicklung von Web-Applikationen ist, einem Designer, mit geringen Programmier-Kenntnissen in die Lage zu versetzen, die visuellen Aspekte einer Anwendung zu verändern. Dazu gehören auch die Stylesheets. Designer arbeiten normalerweise in einer lokalen Umgebung ohne den darunterliegenden Programmcode und so entsteht ein Konflikt zwischen den Zugriff auf Resourcen wie den Stylesheets vom lokalen Dateisystem auf der einen Seite und in der laufenden Web-Anwendung auf der anderen Seite. Page-Templates unterstützen uns hier, öffnen Sie einfach die Datei layout.pt direkt im Browser und Sie werden sehen, wie die Applikation aussehen wird, auch wenn sie nicht läuft. Bei den Stylesheets und anderen Resourcen unterstützt wird nun die Komponente z3c.zrtresource eingesetzt.
Die Verwendung von z3c.zrtresource
Abhängigkeiten
Der erste Schritt bei der Verwendung einer neuen z3c.*-Komponente ist das Hinzufügen der Abhängigkeiten zu unserer Anwendung. Öffnen Sie setup.py und fügen Sie "z3c.zrtresource" zu den install_requires hinzu. Dann lassen Sie ./bin/buildout noch einmal laufen. z3c.zrtresource definiert eine neue zcml-Direktive. Dafür includieren wir die meta.zcml-Datei in unsere src/zcontact/configure.zcml mit dem foldenden Einzeiler:
<include package="z3c.zrtresource" file="meta.zcml" />
Die Verwendung der zrt-resource-Direktive
Jetzt entfernen Sie das CSS aus layout.pt und fügen die Style-Angaben in eine neue Datei ``src/zcontact/style.css ein. Statt eines style-Elementes wird nun ein link-Element wie hier gezeigt verwendet:
<link rel="stylesheet" type="text/css" href="style.css" />
Diese Anweisung verwendet das Stylesheet aus dem Dateisystem. Im Kürze werden wir den Code für einen dynamisch generierten Pfad zum Stylesheet erzeugen. Wenn Sie jetzt den Server neu starten und http://localhost:8080/++skin++ZContact/ aufrufen, werden die Styles nicht funktionieren, weil http://localhost:8080/++skin++ZContact/style.css nicht als Resource geladen werden kann. Deshalb müssen Sie das Stylesheet als eine Ressource in Zope einbinden. In der Datei skin.zcml wird die zrt-resource-Directive am Ende eingefügt:
<browser:zrt-resource
name="style.css"
file="style.css"
layer="zcontact.layer.IZContactBrowserLayer"
/>
Nun kann der Server neu gestartet werden und Sie sollten auf das Stylesheet wie folgt zugreifen können: http://localhost:8080/++skin++ZContact/++resource++style.css
Der Link in der Datei layout.pt wird wie folgt geändert:
<link rel="stylesheet" type="text/css" href="style.css"
tal:attributes="href string:++resource++style.css"/>
Das sollte den Anforderungen der Designer an das Projekt genügen, es gibt jedoch noch weitere Anwendungsmöglichkeiten für “zrt resources”.
Zeichenketten-Ersetzungen mit zrt-resources
Bis jetzt haben wir mit “zrt resources” nicht viel mehr getan, als Sie auch mit normalen Zope-Anweisungen hätten tun können. Wenn Sie nun auf der z3c.zrtresource-Seite von pypi die Dokumentation durchlesen, werden Sie einige interessante Template-Funktionen finden. So kann man zum Beispiel die Hintergrund-Farbe für die Seite dynamisch vom Request erzeugen lassen. Das Ziel ist die Erzeugung einer URL wie: http://localhost:8080/++skin++ZContact/@@index.html?color=LightGoldenRodYellow die eine Hintergrundfarbe LightGoldenRodYellow erzeugt. Als erstes passen Sie den Link in layout.pt so an, dass die Daten aus dem Request-Objekt verwendet werden können:
<link rel="stylesheet" type="text/css" href="style.css"
tal:attributes="href string:++resource++style.css?color=${request/color|string:white}"/>
Wenn nun die Seite gerendert wird, zeigt der Stylesheet-Link auf ++resource++style.css?color=LightGoldenRodYellow. Jetzt bearbeiten Sie die Datei style.css und fügen einen Kommentar am Anfang der Datei ein und ändern den Style für das body-Element:
/* zrt-replace: "requestcolor" tal"request/color|string:white" *
body {
padding: 1em;
background: requestcolor;
}
Öffnen Sie nun die folgende URL http://localhost:8080/++skin++ZContact/@@index.html?color=LightGoldenRodYellow und ein leicht gold-gelb-roter Hintergrund sollte erscheinen. Für den Zugriff über tal-Code, gibt es keinerlei Einschränkungen. Noch mehr Dokumentation mit Beispielen finden Sie unter z3c.zrtresource.
Viewlets für CSS Stylesheets
Sie haben ein Beispiel der Verwendung von Viewlets für die Verwaltung von Menüs erstellt, aber es gibt weitere Möglichkeiten für deren Einsatz.
Nun soll die richtige CSS-Datei passend zum Aufruf der entsprechenden Web-Seite eingefügt werden. Auch das Einfügen anderer CSS-Dateien aus anderen Komponenten soll möglich werden z. B. aus z3c.form, ohne die CSS-Datei kopieren zu müssen. Verwenden Sie Viewlets, die die Erzeugung von dynamisch generierten HTML passend zu den verwendeten Parametern inclusive dem Kontext, Skin/Layer und anderen Regeln erlauben. Ein heute gern verwendeter Einsatzfall ist es, alle CSS-Dateien in einem Viewlet mit zcml-Anweisungen zu verwalten. Lassen Sie uns ein solches Szenario umsetzen.
Einen Viewlet-Manager erstellen
Als erstes wird ein Viewlet-Manager erstellt, der alle vorhandenen CSS-Viewlets vereint. Sie machen das auf die gleiche Art und Weise, wie vorher mit dem Navigations-Menü. Öffnen Sie also src/zcontact/skin.py und fügen Sie die folgenden zwei Zeilen ein:
class ICSS(IViewletManager):
"""CSS viewlet manager."""
Als nächstes registrieren wir den Viewlet-Manager in src/zcontact/skin.zcml :
<browser:viewletManager
name="ICSS"
provides="zcontact.skin.ICSS"
class="z3c.viewlet.manager.WeightOrderedViewletManager"
layer="zcontact.layer.IZContactBrowserLayer"
permission="zope.Public"
/>
Mit dem neuen Viewlet-Manager ICSS, können Sie das Link-Element mit einem tal-Block ersetzen, der die relevanten CSS-Viewlets anzeigen wird. In der Datei src/zcontact/layout.pt sieht das Link-Element dann wie folgt aus:
<tal:block tal:content="structure provider:ICSS">
<link rel="stylesheet" type="text/css" href="style.css"/>
</tal:block>
Beachten Sie bitte, dass das original link-Element erhalten bleibt. Wenn die Seite in Zope generiert wird, findet eine Ersetzung durch die CSS-Viewlets statt, wenn aber ein Designer die Datei layout.pt direkt öffnet, wird das StyleSheet trotzdem geladen, nur nicht dynamisch.
Jetzt können Sie Viewlets hinzufügen. Wie bei z3c.menu gibt es bereits eine Viewlet-Klasse, die speziell für CSS-Viewlets die link-Elemente erzeugt. Erstellen Sie ein solches Viewlet durch Hinzufügen von zwei Zeilen zur Datei src/zcontact/skin.py:
from zope.viewlet.viewlet import CSSViewlet
ZContactCSSViewlet = CSSViewlet('style.css')
Die Argumente die wir der CSSViewlet-Klasse übergeben, entsprechen dem Namen der css-Ressource und nicht der CSS-Datei. Weil die Ressource oft genau so benannt wird wie die Datei selbst, kann das etwas verwirrend sein. Abschließend muss das Viewlet noch mit zcml in src/zcontact/skin.zcml registriert werden:
<browser:viewlet
name="style.css"
for="*"
manager="zcontact.skin.ICSS"
class="zcontact.skin.ZContactCSSViewlet"
permission="zope.Public"
layer="zcontact.layer.IZContactBrowserLayer"
/>
Noch ein Hinweis zu angepassten Layouts
Normalerweise würden das Tutorial hier Enden. Nach dem Neustart des Servers können Sie beobachten, dass die Stylesheetsimmer noch geladen werden. Leider ist da ein Wermutstropfen in der hier gezeigten Lösung, denn es wird ein Layer verwendet, der nicht vom Standard-Layer erbt.
Wenn die CSSViewlet-Klasse das Viewlet zu HTML rendert, ist die URL zum Zugriff auf die Ressource (style.css) nicht /++resource++style.css sondern etwas einfacher /@@/style.css. Leider ist @@ eine Ansicht, die für den Standard-Layer registriert ist und im verwendeten IZContactBrowserLayer nicht existiert, d.h. die Ressource kann innerhalb unseres Skin nicht diese URL benutzen.
Es gibt ein paar Lösungen für dieses Problem, aber die einfachste ist es, die @@-Ansicht für unseren eigenen Skin zu registrieren. Das zcml sieht dann wie folgt aus (in src/zcontact/skin.zcml):
<browser:page
name=""
for="zope.app.component.interfaces.ISite"
class="zope.app.publisher.browser.resources.Resources"
permission="zope.Public"
allowed_interface="zope.publisher.interfaces.browser.IBrowserPublisher"
layer="zcontact.layer.IZContactBrowserLayer"
/>
Falls es Sie interessiert, die Original-Sicht ist in zope/app/publisher/browser/configure.zcml definiert.
Nach diesem letzten Schritt kann der Server neu gestartet werden und Sie werden keine Veränderung feststellen, nur erfolgt hier die Umsetzung mit einer flexibleren und mächtigeren Viewlet-Lösung.
Viewlets anpassen
Note
Der nächste Teil erfordert noch etwas mehr Mut.
Nachdem nun das Viewlet das link-Element erzeugt hat, wird der Farbwert aus dem Request nicht mehr gesetzt. Zur Reaktivierung passen Sie das CSS-Viewlet mit einer eigenen Viewlet-Klasse an.
In der Datei src/zcontact/skin.py wird dazu der folgenden Code eingefügt:
from zope.app.pagetemplate.viewpagetemplatefile import ViewPageTemplateFile
from zope.viewlet.viewlet import CSSResourceViewletBase
from zope.viewlet.viewlet import ViewletBase
class RequestCSSResourceViewlet(CSSResourceViewletBase, ViewletBase):
index = ViewPageTemplateFile(os.path.join(os.path.dirname(zope.viewlet.viewlet.__file__), 'css_viewlet.pt'))
requestKey = ''
default = ''
path = ''
def getURL(self):
self._path = self.path
baseURL = super(RequestCSSResourceViewlet, self).getURL()
fullURL = baseURL + '?%s=%s' % (self.requestKey,
self.request.get(self.requestKey, self.default))
return fullURL
An dieser Stelle ist einen Blick auf den Quellcode der Basisklassen CSSResourceViewletBase und ViewletBase zu empfehlen. Drei Teile der Basisklassen, werden überschreiben. Das index-Attribut, welches zum Page-Template für das zu rendernde link-Element zeigt, das path-Attribut welches den Namen der Ressource angibt, nach der gesucht wird und die getURL-Methode, die die aktulle URL erzeugt, die Sie brauchen. Für index-Page-Template wird wieder die Datei css_viewlet.pt aus dem Paket zope.viewlet.viewlet verwenden. Die Methode getURL` führt die gleiche Aktion aus, wie vorher bei den tal-Anweisungen praktiziert. Sie verbindet die Request-Paramter mit der URL.
Diese Klasse wurde auch so allgemein definert, dass sie nicht nur für den Parameter “color” verwendet werden kann. Eine interessante Eigenschaft von Viewlets ist, wie Sie gleich feststellen werden, die Möglichkeit Klassen-Attribute mit dem gleichen Namen im zcml über das browser:viewlet -Element zu definieren. Im konkreten Fall werden drei Extra-Attribute requestKey, default und path genutzt. Öffnen Sie nun also src/zcontact/skin.zcml und ändern Sie das vorhandene Viewlet-Element für style.css ab, es sollte dann so aussehen:
<browser:viewlet
name="style.css"
for="*"
manager="zcontact.skin.ICSS"
class="zcontact.skin.RequestCSSResourceViewlet"
permission="zope.Public"
layer="zcontact.layer.IZContactBrowserLayer"
requestKey="color"
default="white"
path="style.css"
/>
Sie sehen hier, wie die Klassen-Attribute und die dazugehörigen Attribute für das Viewlet geändert wurden. Damit endet der Streifzug durch die CSS-Viewlet-Welt und Sie sollten nach dem Server-Neustart die URL http://localhost:8080/++skin++ZContact/@@index.html?color=LightGoldenRodYellow aufrufen können und einen gold-gelb-roten Hintergrund erblicken!