• Viewlets on OpenPlans HOWTO

last modified April 15, 2008 by douglas

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

  1. to have markup (i.e. HTML-ish) stuff live in ZPTs
  2. --AND--
  3. to have programmatic stuff live in python
Of course, it ain't a perfect world. but we can get close.

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.

Here is a brief description of the viewlet setup:
- 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.

Views

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 here/++view++personalbar_wrapper .

Declaring a view in zcml:

<browser:page
  name="personalbar_wrapper"
  template="personalbar_wrapper.pt"
  permission="zope2.View"
  for="*"
/>
In this zcml, browser:page declares 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.BrowserPage and there should be a __call__(self): that should return the desired text/html. It should also mark the self.request.response, with for instance response.setHeader('Content-Type', 'text/plain'). 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.

Viewlets

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.

Here is 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 permission, template, and 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 provides.

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:

project_menu.pt:

<div id="portal-breadcrumbs">
  <ul>
    &nbsp;
    <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 title and url. Hopefully, it is evident from this simple example how functionality may be extended. For reference, here is viewlets.py .

Viewlet Managers

To 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
In page templates, viewlet managers (and other content providers) may be looked up using the provider keyword:

<div tal:content="structure provider:opencore.personalbar">
  Personal Bar
</div>

They may also be looked up in python as a multi-adapter:

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 provides= declaration:

 provides=".interfaces.IPersonalBar"
This allows viewlets to indicate their manager by referencing this interface. IPersonalBar in this case is a dummy interface:
from zope.viewlet.interfaces import IViewletManager

class IPersonalBar(IViewletManager):
    """Viewlets for the personal bar (e.g. 'project home')"""    
This viewletManager has a template associated with it, personalbar.pt:
<tal:personal-viewlets repeat="viewlet view/viewlets">
  <tal:viewlet replace="structure viewlet" />
</tal:personal-viewlets>

In this case, the template simply looks over the viewlets and renders each one.

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 Links

Listed in order of perceived usefulness