Overriding a class viewlet
In the previous chapter we have learned how to override a viewlet that has a template in it's definition. But there are a lot of viewlets in Plone that have a python class in their definitions instead of a simple template. So, let's have a look how to customize such viewlet.
The steps for overriding a class viewlet do not differ from what you have learned in the previous chapter a lot. So, let's just cover the practical points of our use case.
Introduction
We suppose you are already familiar with how to find the viewlet you want to override, using @@manage-viewlets view from the previous chapter. For this particular example let's override plone.path_bar viewlet that is managed by plone.portaltop viewlet manager. It's the viewlet that renders a path bar (better known as the breadcrumbs) below the global navigation. In this example we will get rid of "You are here:" text in plone.path_bar viewlet.
1. Code that renders the viewlet
Let's have a look at how this viewlet is declared in configure.zcml inside the plone.app.layout.viewlets package:
<!-- The breadcrumbs -->
<browser:viewlet
name="plone.path_bar"
manager=".interfaces.IPortalTop"
class=".common.PathBarViewlet"
permission="zope2.View"
/>
As you can see this viewlet is rendered by a Zope 3 view, defined in common.PathBarViewlet class inside the plone.app.layout.viewlets package.
Here is how this class looks like:
class PathBarViewlet(ViewletBase):
render = ViewPageTemplateFile('path_bar.pt')
def update(self):
portal_state = getMultiAdapter((self.context, self.request),
name=u'plone_portal_state')
self.navigation_root_url = portal_state.navigation_root_url()
self.is_rtl = portal_state.is_rtl()
breadcrumbs_view = getMultiAdapter((self.context, self.request),
name='breadcrumbs_view')
self.breadcrumbs = breadcrumbs_view.breadcrumbs()
We are not going to edit standard behavior of the viewlet in this example. The only thing we want is getting rid of "You are here:" text. Thus render = ViewPageTemplateFile('path_bar.pt') is exactly what we are interested in.
2. Customize the viewlet's template
render = ViewPageTemplateFile('path_bar.pt') line defines the page template for plone.path_bar viewlet. You can find path_bar.pt template inside the plone.app.layout.viewlets package. Let's make a copy of this template in the browser/ directory of MyTheme. No need to give it a different name.
Edit the template to remove the text we do not need:
<div id="portal-breadcrumbs"
i18n:domain="plone">
<!-- We use a comment to show what part we remove
<span id="breadcrumbs-you-are-here" i18n:translate="you_are_here">
You are here:
</span>
-->
<a i18n:translate="tabs_home" tal:attributes="href
view/navigation_root_url">Home</a>
<span tal:condition="view/breadcrumbs" class="breadcrumbSeparator">
<tal:ltr condition="not: view/is_rtl">→</tal:ltr>
<tal:rtl condition="view/is_rtl">»</tal:rtl>
</span>
<span tal:repeat="crumb view/breadcrumbs"
...
</span>
</div>
Now we need to make the plone.path_bar viewlet use this template for rendering.
3. Overriding a class that renders the template
Since ZCML declaration for plone.path_bar in lib/python/plone/app/layout/viewlets/configure.zcml uses a class instead of a simple template the core of customization is overriding that class also. For doing this we will simply subclass the default viewlet's class and apply our changes, so that they could override default ones. You have seen the default class used for this viewlet in p.1 above in this page.
So, to subclass PathBarViewlet class we should add a new viewlets.py into MyTheme/browser/. This will be the place where you override classes, used for viewlets. Add the following to your viewlets.py:
from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
from plone.app.layout.viewlets import common
class PathBarViewlet(common.PathBarViewlet):
"""A custom version of the path bar class
"""
render = ViewPageTemplateFile('path_bar.pt')
As you can see we create a new class PathBarViewlet, that is a subclass of common.PathBarViewlet - default class for the plone.path_bar viewlet. The only thing we need to override is the template used for rendering the viewlet. We already have customized path_bar.pt inside MyTheme/browser/, thus our new class reffers to our customized template.
3.1 Theme Compatibility Between Different 3.x Versions of Plone
Version 1.1.3 of the plone.app.layout egg was released in July of 2008. As of this version, the viewlet template is controlled via the 'index' attribute rather than 'render', because 'index' can be overridden by using the ZCML template attribute, removing the need for a special viewlet subclass. If you need to make a subclass anyway, you can continue to use the render directive as mentioned above if you want to, or you can use "index" instead, like this:
from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
from plone.app.layout.viewlets import common
class PathBarViewlet(common.PathBarViewlet):
"""A custom version of the path bar class
"""
index = ViewPageTemplateFile('path_bar.pt')
If you're going to distribute your theme and need to maintain backwards compatibility with the Plone 3.0.x series, additional code needs to be included. You should add the following lines that make the render method defer to the 'index' attribute. (This is done in ViewletBase in Plone 3.1.x, but not in Plone 3.0.x.):
from plone.app.layout.viewlets import common
class PathBarViewlet(common.PathBarViewlet):
"""A custom version of the path bar class
"""
def render(self):
# defer to index method, because that's what gets overridden by the template ZCML attribute
return self.index()
index = ViewPageTemplateFile('path_bar.pt')
Otherwise, themes created with newer versions of plone.app.layout will not be compatible with older versions of Plone.
That's it with the class. In case you need to customize not only the template, but viewlet's behavior - this is the right place to do so.
So far so good - we have the customized class, that uses the customized template. Now we need to register these customizations with MyTheme/browser/configure.zcml
4. Register the custom viewlet in configure.zcml
In order to register the customized class with the plone.path_bar viewlet, we simply copy the portion of ZCML code that we found in lib/python/plone/app/layout/viewlets/configure.zcml (see p.1 above in this page) into MyTheme/browser/configure.zcml, and edit it to make sure it uses the right class and right interfaces for the manager and the Zope 3 skin layer:
<!-- The customized breadcrumbs -->
<browser:viewlet
name="plone.path_bar"
manager="plone.app.layout.viewlets.interfaces.IPortalTop"
class=".viewlets.PathBarViewlet"
layer=".interfaces.IThemeSpecific"
permission="zope2.View"
/>
Use of layer=".interfaces.IThemeSpecific" has been explained in the previous chapter.
name and permission remain the same as in the default viewlet declaration. Although you could change name to make it clear that this is not a standard viewlet. You could use something like "MyTheme.path_bar" as the name here.
For manager we use the full dotted path to IPortalTop interface, implemented by plone.portaltop viewlet manager.
And the core part of our customization - we switch class to our customized class from MyTheme/browser/viewlets.py.
5. Additional step (Only if you have changed name for viewlet in ZCML)
If you have renamed your custom viewlet to "MyTheme.path_bar", you have created a new viewlet that has to be registered in your Generic Setup profile. To not have 2 path bars in your site you should also hide the default plone.path_bar viewlet. To remember how to hide existing viewlet and register the new one, please refer to page 3 of this tutorial.
6. Done
Now you can restart your Zope. If you have your theme installed already, you need to reinstall it from Site Setup > Add-on Products.
After all these steps you should not see "You are here:" text any more in your breadcrumbs.
