A Zope 3 AJAX viewlet manager

I've finally got tired of writing small fragments of JavaScript code and views to be able to make different areas updatable through ajax for my current Zope 3 application. Plus, when I've started writing this application, I've started doing the layout with viewlets and managers, and it seems that for each viewlet that I'm creating I have to have a way to get its content through AJAX, which means having a page available for it, and this is very odd, as it almost defeats the point of having viewlets.

So I've decided to create the "Ajax viewlet manager". I'm not sure how good of an idea this is, I'll have to give it some thought and see how I feel about it in the future. Let me describe my solution.

First, I want to be able to have the manager available at a certain URL, to be able to reload this URL through an Ajax call later on. So I've created a new namespace traverser, ++vmanager++. A viewlet manager would be accessible (as rendered HTML) at a location such as http://localhost:8080/mysite/myobject/++vmanager++ITop. This is the traverser code:

from zope.publisher.interfaces.browser import IBrowserView
from zope.traversing.interfaces import TraversalError
from zope import component
from zope.interface import implements
from zope.traversing.namespace import SimpleHandler
from zope.viewlet.interfaces import IViewletManager

class vmanager(SimpleHandler):
implements(IBrowserView)
def __init__(self, context, request):
self.context = context
self.request = request

def traverse(self, name, ignored):
manager = component.queryMultiAdapter((self.context, self.request, self), IViewletManager, name=name)
if manager:
return manager
else:
raise TraversalError(self.context, name)

The traverser is registered like this:

<adapter
name="vmanager" for="*"
provides="zope.traversing.interfaces.ITraversable"
factory=".namespace.vmanager"
/>

<view
name="vmanager" for="*"
type="zope.interface.Interface"
provides="zope.traversing.interfaces.ITraversable"
factory=".namespace.vmanager"
/>

The traverse() in the vmanager class will return the manager. One thing to note is that once the manager returned, Zope will try to get the default view for this object, so a default page (index.html) is needed for IViewletManager

class ViewViewletManager(object):
"""View a rendered viewlet manager"""

def __call__(self):
manager = self.context
manager.update()
return manager.render()

This page is registered as

<browser:page
name="index.html"
class=".browser.ViewViewletManager"
for="zope.viewlet.interfaces.IViewletManager"
permission="zope.View"
/>

Next, to automate things a bit more, I wrote a template, along these lines:

<tal:vars
    tal:define="vmgr_name view/__name__; here_url context/@@absolute_url">
    <div id="provider_ITop"
        tal:attributes="id string:provider_$vmgr_name">
        <div tal:repeat="viewlet view/viewlets" tal:omit-tag="">
            <div tal:replace="structure viewlet/render" />
        </div>
    </div>
    <script
        tal:content="string:
function reload_${vmgr_name}(){
    new Ajax.Updater('provider_${vmgr_name}', '$here_url/++vmanager++${vmgr_name}')
}" />
</tal:vars>

This template inserts the viewlet contents in a named container and creates a JavaScript function (which uses Prototype) that can be called later to update that container. The last piece is to tell my viewlet providers to use this template (observe the template argument):

<viewletManager name="IFooter" provides=".IFooterSlotManager"
        class="z3c.viewlet.manager.WeightOrderedViewletManager"
        template="vmanager.pt"
        permission="zope.View" />

I'll probably still have to write some views and JavaScript to make things dynamics, but this solution will sure help to reduce the amount of code I have to write.

Comments