Viewlets on OpenPlans HOWTO
howto for zope viewlets...as applied to the OpenPlans product
The Big Picture
``Page Templates are a web page generation tool. They help programmers and designers collaborate in producing dynamic web pages for Zope web applications. Designers can use them to maintain pages without having to abandon their tools, while preserving the work required to embed those pages in an application.''
-- Using Zope Page Templates, The Zope Book (2.6 Edition)
So what is this all about? Despite the disclaimer in the zope book quote, and Principle #3 in its source:
3. Keep code out of templates, except for structural logic.
in practice, zope page templates (ZPTs) often contain heavy logic that makes them ugly, fugly, not at all similar to what you get, and a programmatic nightmare (since they weren't really designed to do complicated python expressions). In fact, all three design criteria from the Zope Book regarding page template use are broken. Who would have thought that such things would be abused? If you want to see a fairly randomly picked example, click here. They get much worse, trust me. The point is that lots of programmatic logic is used in something that is supposed to be just for markup.
So what to do? There are ``things'' in the zope world, called views and viewlets that allow page templates and python classes to interact intelligently. The goal (just as stated in the zope book) is still
- to have markup (i.e. HTML-ish) stuff live in ZPTs --AND--
- to have programmatic stuff live in python
This page will attempt to explain how to use viewlets (well, a viewlet and viewlet manager pair, really) to create a flexible menu in Zope land. All examples are taken from this endeavor. I'm no zope expert, and don't really understand views or viewlets. But this is how to apply them, so hopefully from the tech-manual point of view, this will be a decent presentation.
The example problem whereby viewlets were introduced to me as a good solution is replacing an existing navigation bar done in ZPTs (with many, many
python: calls) with one implemented in a much cleaner way in viewlets:
While secondary to the purpose of discussion on viewlets, this new navigation bar implements functionality based on where the user is, giving different menu items and links based on context, permissions, and where the user is the site -- either the projects area, their own user area, or another user's area. Ultimately, the whole menu bar -- or maybe even the whole site -- should be replace with various viewlets and viewlet managers. But we're going to start small with this area and a single viewlet and viewlet manager.
- breadcrumb_personalbar.pt: this contains some old-school style skins code in order to pull in Plone's default (but working) personal menu on the left side. in order to take care of the viewlet side of things, it invokes: | -> personalbar_wrapper view: sacrificing a dead chicken to keep the viewlet gods happy, here. this does nothing but invoke: | + opencore.personalbar viewlet manager: this is directly associated w/ the personalbar.pt template. this template talks to the viewlet manager to retrieve and then render the associated viewlets, of which there are one: | -> project_menu viewlet: this viewlet has a ProjectMenu class, and also is associated with the project_menu.pt template. the ProjectMenu class contains the "mode" stuff that used to be in the Navigation viewlet class.
So what are views? In Zope, a View is really a multiadapater which ``discriminates against context (the object being viewed) and the request (HTTP request with skin information, FTP requests, and so on)'' ( A Glance at Zope 3.2 ) What this means to programmers is that you can provide different views for different types of requests.
Views may be referenced within ZPTs with two little eyes (@@):
<div tal:replace="structure here/@@personalbar_wrapper"> Our viewlet-based personal bar implementation (currently only handles part of the personal bar) </div>
This is short-hand for
Declaring a view in zcml:
<browser:page name="personalbar_wrapper" template="personalbar_wrapper.pt" permission="zope2.View" for="*" />
browser:pagedeclares a view (really, one type of view, but probably the most common). A view may have a class (and?/)or a page template associated with it which is rendered when the view is referenced. In this case, there is a page template, personalbar_wrapper.pt. If there was a class, it should inherit from
zope.publisher.browser.BrowserPageand there should be a
__call__(self):that should return the desired text/html. It should also mark the
self.request.response, with for instance
for = "*"here indicates the personalbar_wrapper view is for all interfaces -- alternatively, a specific interface could be specified. The permission tag tells who can see the view -- all users normally have the permission zope2.View.
So what are viewlets? How are they `better' than views?The high-level view is that a viewlet is a view, but each viewlet has a viewlet manager. This allows the programmer to organize page elements and gives the manager the ability to deal with viewlets (to render, not render, filter, sort, alphabetize their socks...) based on the object, request, and view. The higher-level perspective is that viewlets are small(er) content providers, and a viewelet manager aggregates them together.
viewlets.zcml, a file included by
configure.zcml in the OpenPlans
browser/ directory that declares a viewlet and its viewlet manager:
<configure xmlns="http://namespaces.zope.org/zope" xmlns:five="http://namespaces.zope.org/five" xmlns:browser="http://namespaces.zope.org/browser"> <!-- viewlets and viewletManagers --> <browser:viewletManager name="opencore.personalbar" provides=".interfaces.IPersonalBar" permission="zope2.View" template="personalbar.pt" /> <browser:viewlet name="project_menu" for="*" manager=".interfaces.IPersonalBar" class=".viewlets.NavMenu" template="project_menu.pt" permission="zope2.View" /> </configure>
This zcml illustrates using a viewletManager with a single viewlet. Note that the markup is similar to that for views -- views, viewlets, and viewletManagers all have
name tags; and the viewlet and the view both have
for tags. These tags all mean the same thing within the three contexts. The viewlet looks up its viewletManager using the
manager=".interfaces.IPersonalBar" declaration, which the manager
Viewlets are actually quite like views -- in fact, they are views (or so I've been told). This is evident from this example. The
project_menu viewlet has a class (
.viewlets.NavMenu) -and- a page template (
project_menu.pt, whose name comes from legacy when only stuff related to projects lived there). The class may (and in fact does) have functions that are called from the page template:
<div id="portal-breadcrumbs"> <ul> <li tal:repeat="menu_item view/menuItems"> <a tal:content="menu_item/title" tal:attributes="href menu_item/url">MENU ITEM</a> </li> </ul> </div>
In this ZPT,
view refers to the project_menu viewlet, and
view/menuItems calls the function
menuItems from the class
.viewlets.NavMenu. This method returns a dictionary with keys
url. Hopefully, it is evident from this simple example how functionality may be extended. For reference, here is
Viewlet ManagersTo quote A Glance at Zope 3.2 Content Providers, viewlet managers ``filter, sort, update, and render each Viewlet'' that they manage. Viewlet Managers are content providers, which means they provide HTML. It also means that, unlike views, they adapt three objects:
- the object being viewed
- the request
- the view itself
<div tal:content="structure provider:opencore.personalbar"> Personal Bar </div>
adapter = zope.component.queryMultiAdapter( (self.folder, self.folder.REQUEST, self.folder.unrestrictedTraverse('topp_bar') ), interfaces.IContentProvider, 'OpenPlans.PersonalBar')
Each viewletManager has an interface associated with it, indicated with the
IPersonalBarin this case is a dummy interface:
from zope.viewlet.interfaces import IViewletManager class IPersonalBar(IViewletManager): """Viewlets for the personal bar (e.g. 'project home')"""
templateassociated with it, personalbar.pt:
<tal:personal-viewlets repeat="viewlet view/viewlets"> <tal:viewlet replace="structure viewlet" /> </tal:personal-viewlets>
Once you have this code in place, you should have a viewlet (though you could easily add more) and a viewlet manager working together. That is the nuts and bolts of it. Does it explain much of the magic or art of viewlets? Probably not, but it at least gives you a starting point. Happy coding! And keep those ZPTs clean!
Reference LinksListed in order of perceived usefulness
- philiKON's tutorial on viewlets and UIS from the 2006 Plone Conference Very useful, and the worldcookery has helpful examples that go along with the tutorial.
- A Glance at Zope 3.2 - Content Providers, Viewlets, and formlib Informative, though a bit vague on how to use them, it at least gives the why of viewlets.