.. index:: z3c.menu, menu, viewlets
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:
.. sourcecode:: python
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:
.. sourcecode:: xml
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.
.. sourcecode:: xml
Navigation Menu
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:
.. sourcecode:: xml
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:
.. image:: images/menuScreenShot.png
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:
.. sourcecode:: xml
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:
.. sourcecode:: html
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:
.. sourcecode:: xml
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:
.. sourcecode:: html
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:
.. sourcecode:: html
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:
.. sourcecode:: css
/* 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:
.. sourcecode:: python
class ICSS(IViewletManager):
"""CSS viewlet manager."""
Als nächstes registrieren wir den Viewlet-Manager in ``src/zcontact/skin.zcml`` :
.. sourcecode:: xml
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:
.. sourcecode:: xml
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``:
.. sourcecode:: python
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:
.. sourcecode:: xml
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``):
.. sourcecode:: xml
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:
.. sourcecode:: python
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:
.. sourcecode:: xml
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!
.. _zopeproject: http://pypi.python.org/pypi/zopeproject
.. _z3c.zrtresource: http://pypi.python.org/pypi/z3c.zrtresource