Using Viewlets for Layout

Author:Unknown

Viewlets can be used instead of macros to
make a flexible layout. This is an example
application that shows how viewlets can be used.

The code can be found here: http://svn.zope.org/grokapps/SimpleViewletDemo

To try it out, do something like this:

$ svn co svn://svn.zope.org/repos/main/grokapps/SimpleViewletDemo
$ cd SimpleViewletDemo
$ python bootstrap.py
$ ./bin/buildout
$ ./bin/zopectl fg

This how-to explains how this application is built and how viewlets are used to get this layout and behaviour.

../_images/understanding_viewlets_for_layout.png

Viewlets and Viewlet Managers

A viewlet is a component that represents an HTML snippet. Viewlets are multi adapters (context, request, view, viewlet manager) so they only show up when they are registered for the interfaces or classes that currently applies. Viewlets are grouped using viewlet managers. On the picture above each coloured area is a viewlet manager. There are five viewlet managers and they are referred to by name: header, left, main, footer and head. Only four of them are visible, ‘head’ is only including stylesheets. The ‘left’ viewlet manager has two viewlets, a navigation viewlet and a login viewlet, but only one is visible on the picture. The left viewlet manager might show one of the viewlets, both or none of them. It depends completely on how the viewlets are registered. When you click on ‘Navigation’ in the navigation viewlet the login viewlet will show up. That’s because the login viewlet was only registered for the Application model and not the Fruit model.

A viewlet is often associated with a template which will render the HTML and use data from the viewlet object. Instead of using a template it’s also possible to have a render method defined in the viewlet. In this example application most viewlets use templates, but there are two that uses the render method instead. You can’t use both, grok will complain and refuse to start up.

What happens?

When you visit the frontpage of the application the ‘index’ view is invoked. The ‘index’ view uses the master.pt template for its HTML output. The master.pt is what decides the layout of the page. It adds some div tags and asks viewlet managers to put HTML in each slot.

For example, the template asks for HTML output from the viewlet manager called ‘left’:

<tal:left replace="structure provider:left" />

This will ask the viewlet manager called ‘left’ to insert its input here. The viewlet manager will see which viewlets are associated with it and are registered for the current context, request and view. The results (HTML snippets) of all viewlets are concatenated and inserted into the template output.

The master.pt template is used for all views so they all get the same layout. It’s the viewlets’ responsibility to decide what content to display. When you’re not using viewlets (for layout purpose) it’s generally the views’ responsibility to provide content.

Stylesheets

Viewlets can be used to dynamically add stylesheets or javascripts in the <head> tag of the HTML. In this example application the frontpage will use app.css and when visiting a fruit object it will use fruit.css. That is the reason why the colours change when you navigate to a fruit:

class Head(grok.ViewletManager):
    grok.name('head')

class Title(grok.Viewlet):
    grok.viewletmanager(Head)

class AppCSS(grok.Viewlet):
    grok.viewletmanager(Head)
    grok.context(App)

class FruitCSS(grok.Viewlet):
    grok.viewletmanager(Head)
    grok.context(Fruit)

The viewlet manager is called ‘head’ and three viewlets are associated with it. One CSS viewlet is registered for the application model and the other CSS viewlet is registered for the fruit model so they will never be used both at the same time. The title viewlet will be used on all pages because of the grok.context(Interface) directive which is found higher up in app.py. You can add as many viewlets as you want. Each CSS viewlet has a template which will include the CSS link into the HTML.

Forms

Viewlets make it possible to use grok.AddForm in just a part of the page. It’s not as easy as it could be, but it works fine and is pretty straightforward:

class Admin(grok.View):
    grok.template('master')

class AddFruit(grok.Viewlet):
    grok.viewletmanager(MainArea)
    grok.context(App)
    grok.view(Admin)

    def update(self):
        self.form = getMultiAdapter((self.context, self.request), name='addfruitform')

        self.form.update_form()

        if self.request.method == 'POST':
            app = get_application(self.context)
            self.__parent__.redirect(self.__parent__.url(obj=app))

    def render(self):
        return self.form.render()

class AddFruitForm(grok.AddForm):
    form_fields = grok.AutoFields(Fruit)

    @grok.action('Add fruit')
    def add(self, **data):
        obj = Fruit(**data)
        name = data['name'].lower().replace(' ', '_')
        self.context[name] = obj

First you can see that an admin view is registered. That is what you visit when you click the “Add Content” link in the example application. When you visit the admin page or view it will call the master template. As you can see the AddFruit viewlet is registered for the main content area viewlet manager, the App model class and the ‘admin’ view. This means that the AddFruit viewlet is only shown when visiting /admin on the root of the site and it will show up in the main content area (the green area).

The AddFruit viewlet just delegates its HTML rendering to the AddFruitFrom class which is the form. If the request method is POST (the user submitted the form) the user is redirected to the frontpage and will see the new fruit in the navigation.

Context dependent viewlets

In this application the main content area shows different text depending on the model you are visiting:

class FruitContent(grok.Viewlet):
    grok.viewletmanager(MainArea)
    grok.context(Fruit)
    grok.template('fruit')


    def update(self):
        self.name = self.context.name

This viewlet is only registered for the Fruit model. This means that this viewlet will be responsible for showing the relevant information about the currently selected fruit. When visiting the frontpage (the application model) the Content viewlet (see app.py) is used instead.

Notice that there is no special index view for the Fruit model. It uses the same index view as the App object. This is because of the grok.context(Interface) directive high up in app.py. The directive has the effect that the index view is used for all models (contexts).

In the update() method we add all data to self that we want available in the template.

In the login viewlet below we can see that it’s associated with the App model and the ‘index’ view. That means that it will only appear on the frontpage. If we didn’t specify grok.view(Index) the login viewlet would still be visible on the ‘admin’ view. So to be sure that login should only be shown on the frontpage we need both grok.context() and grok.view():

class Login(grok.Viewlet):
    grok.viewletmanager(LeftSidebar)
    grok.context(App)
    grok.view(Index)
    grok.order(2)

Ordered viewlets

In the login viewlet above you can see that order 2 is specified. The navigation portlet has order 1. This means that the ‘left’ viewlet manager will return the output of the navigation viewlet first and then the login viewlet.