Personal tools
You are here: Home Documentation Tutorials Customizing the viewlets in main_template
Support

Get Help

Join our chat rooms or support forums if you have more specific questions.

Plone Training
Learn how to design, build, and deploy a website in Plone through one of the numerous Plone training sessions around the world.
Find Plone training…
 
Document Actions

Customizing the viewlets in main_template

Note: Return to tutorial view.

Since Plone 3.0, main_template.pt calls viewlet managers instead of METAL macros. Learn from this tutorial how viewlets can be programmatically reordered, hidden or added (from a product on the filesystem).

Introduction

What this tutorial is about

Plone 3 has switched to use Zope 3 viewlet components instead of the old macro include approach.

This tutorial intends to teach you what viewlets and viewlet managers are, and how you can play with them in the context of adding a new theme to a Plone 3.0 site.

Even if it is or will be possible to go through all the steps that will be demonstrated in this tutorial from a Through The Web interface, the purpose here is to show how it is possible to achieve the given goals programmatically, from a Python product that lives on the filesystem.

So let's dig right into the code and have a look at the main_template template of Plone, which is the template that aggregates the page regions that are rendered around the content region of a Plone page (i.e. the header, the footer, and the two side columns):

In main_template.pt, which resides in $INSTANCE_HOME/Products/CMFPlone/skins/plone_templates/, we can see that the content of the <div /> region with id portal-top contains three lines of code. In versions of Plone previous to 3.0, it used to contain a whole list of macro calls for rendering the site wide actions, the quick search box, the logo, the global sections, the personal bar and breadcrumbs, etc..

For the example that I want to show, the old portion of the main_template.pt file used to look like this (in CMFPlone prior to 3.0):

<div id="portal-top" i18n:domain="plone">

<div id="portal-header">
  <p class="hiddenStructure">
    <a accesskey="2"
       tal:attributes="href string:${current_page_url}#documentContent"
       i18n:translate="label_skiptocontent">Skip to content.</a> |

    <a accesskey="6"
       tal:attributes="href string:${current_page_url}#portlet-navigation-tree"
       i18n:translate="label_skiptonavigation">Skip to navigation</a>
  </p>

     <div metal:use-macro="here/global_siteactions/macros/site_actions">
       Site-wide actions (Contact, Sitemap, Help, Style Switcher etc)
     </div>

     <div metal:use-macro="here/global_searchbox/macros/quick_search">
       The quicksearch box, normally placed at the top right
     </div>

     <a metal:use-macro="here/global_logo/macros/portal_logo">
       The portal logo, linked to the portal root
     </a>

     <div metal:use-macro="here/global_skinswitcher/macros/skin_tabs">
       The skin switcher tabs. Based on which role you have, you
       get a selection of skins that you can switch between.
     </div>

     <div metal:use-macro="here/global_sections/macros/portal_tabs">
       The global sections tabs. (Welcome, News etc)
     </div>
  </div>

  <div metal:use-macro="here/global_personalbar/macros/personal_bar">
     The personal bar. (log in, logout etc...)
   </div>

   <div metal:use-macro="here/global_pathbar/macros/path_bar">
     The breadcrumb navigation ("you are here")
   </div>
</div>

Where it now looks like this:

<div id="portal-top" i18n:domain="plone">
    <div tal:replace="structure provider:plone.portaltop" />
</div>

The provider TAL expression is a Zope 3 expression which looks up a content provider from a page template.

In the Zope 3 world (since Zope 3.2), a content provider is a component that generates a portion of an HTML page.

Like Zope 3 views, content providers are multi adapters that adapt the context and the request. Content providers additionally adapt the view they are looked up from.

Viewlets are content providers, they are small components of a page that render a small piece of HTML code.

In Plone templates, viewlets are not looked up directly though. In order to be able to organize viewlets with a maximum of flexibility, they are aggregated in viewlet managers.

A viewlet manager is also a Zope 3 content provider, which renders a set of viewlets that are registered for it.

In the further chapters of this tutorial, I will do my best to teach you more about what viewlets and viewlet managers are, what you can do with them and what are the advantages of this approach. If you still feel that you need more input on the subject, you should read the dedicated section of Philipp's book (Web Component Development with Zope 3 - 2d. edition, chapter 10.4).

I also found this .. _very good blog entry: http://griddlenoise.blogspot.com/2005/12/glance-at-zope-32-content-providers.html where Jeff Shell gives a more extensive description of content providers and viewlets in the context of developing a pure Zope 3 application.

Viewlet registration in Plone 3.0

How viewlets are registered in a default Plone installation.

As mentioned in the above introduction, Plone 3 main template looks up viewlet managers only, it does not call viewlets directly. This also applies to any Plone 3 template that generates HTML code from Zope 3 components.

The advantage of this approach is that we can now organize the different regions of a Plone page without having to modify the code in main_template.pt itself.

The viewlet managers of main_template.pt and their registered viewlets are defined in the plone.app.layout package, in the module called viewlets. That package can be found in any Plone installation on the filesystem, in $INSTANCE_HOME/lib/python/plone/app/layout/.

In the configure.zcml file of the viewlets module (read: in $INSTANCE_HOME/lib/python/plone/app/layout/viewlets/configure.zcml), we can find viewlet manager definitions, followed by the registration of their viewlets.

We see for instance that the plone.portaltop viewlet manager provides the interface IPortalTop:

<browser:viewletManager
    name="plone.portaltop"
    provides=".interfaces.IPortalTop"
    permission="zope2.View"
    class="plone.app.viewletmanager.manager.OrderedViewletManager"
    />

IPortalTop is a marker interface for the component that manages the plone.header, the plone.personal_bar and the plone.path_bar viewlets.

It is in the browser:viewlet declarations that we find the management association between the viewlets and the viewlet manager, for instance:

<!-- The portal header -->
<browser:viewlet
  name="plone.header"
  manager=".interfaces.IPortalTop"
  template="portal_header.pt"
  permission="zope2.View"
  />

...

<!-- The personal bar -->
<browser:viewlet
    name="plone.personal_bar"
    manager=".interfaces.IPortalTop"
    class=".common.PersonalBarViewlet"
    permission="zope2.View" 
    />

<!-- The breadcrumbs -->
<browser:viewlet
    name="plone.path_bar"
    manager=".interfaces.IPortalTop"
    class=".common.PathBarViewlet"
    permission="zope2.View" 
    />

We notice from its ZCML definition that the plone.header viewlet looks up a template directly (portal_header.pt), where the other viewlets managed by IPortalTop are associated with a class.

In that template, all there is is the call of another viewlet manager, which name is plone.portalheader:

<div id="portal-header">
    <div tal:replace="structure provider:plone.portalheader" />
</div>

Again from configure.zcml we see that plone.portalheader, which implements the IPortalHeader interface, manages the plone.skip_links, the plone.site_actions, the plone.searchbox, the plone.logo, and the plone.global_sections viewlets.

All these viewlets, with the aforementioned two ones registered for plone.portaltop, are associated with python classes defined in plone/app/layout/viewlets/common.py. Those classes are Zope 3 browser views that implement zope.viewlet.interfaces.IViewlet, which means that they have a render() method that yields the HTML snippet that will be injected in the page.

We can also see in configure.zcml that all viewlet managers are instances of OrderedViewletManager (see manager.py in plone.app.viewletmanager). It means that viewlets will be looked up in a specific order.

The order of the viewlets is defined in a utility that implements IViewletSettingsStorage.

That utility stores the order of the viewlets in a viewlet manager for a specific skin. Its setup comes from a Generic Setup profile called viewlets.xml.

For the Plone Default skin, ordering of viewlets is described in Products/CMFPlone/profiles/default/viewlets.xml:

<?xml version="1.0"?>
<object>
    <order manager="plone.portaltop" skinname="Plone Default">
        <viewlet name="plone.header" />
        <viewlet name="plone.personal_bar" />
        <viewlet name="plone.app.i18n.locales.languageselector" />
        <viewlet name="plone.path_bar" />
    </order>
    <order manager="plone.portalheader" skinname="Plone Default">
        <viewlet name="plone.skip_links" />
        <viewlet name="plone.site_actions" />
        <viewlet name="plone.searchbox" />
        <viewlet name="plone.logo" />
        <viewlet name="plone.global_sections" />
    </order>
</object>

Now that we know how Plone 3.0 sets up the defaults for viewlet managers and their viewlets in the Plone Default skin, let's see how we can customize those values from a Python product on the file system, either for the default skin or for a skin that we would be developing.

In the following paragraphs, I will refer to a product called MyTheme. Simply replace MyTheme with the name of the Python product you are developing on the file system.

Note
For quickly generating a theme (skin) product for Plone 3.0 (a Python product that has to be placed in the Products folder of a Zope instance), you can use the generator script of DIYPloneStyle (svn trunk until version 3.0 is out). If you want to quickly generate a python egg or a package that must be installed in $INSTANCE_HOME/lib/python/ or somewhere in the Python path, you can use the plone3_theme template of ZopeSkel (from svn trunk).

Reordering and Hiding viewlets

How to simply change the viewlets behavior from a Generic Setup Profile.

We saw in the previous paragraph that viewlets ordering is stored in a utility. That utility is set up from the viewlets.xml file of a Generic Setup profile.

Ordering

If all you need is to reorder the viewlets in the Plone Default skin, you can simply copy the original viewlets.xml from CMFPlone/profiles/default/ into MyTheme/profiles/default/, and edit the copied file to make it reflect your needs.

But you may want to order viewlets with more flexibility. Be happy: there are handy parameters for each node in viewlets.xml.

Let's see how would look a viewlets.xml file for a 3rd party theme product:

<?xml version="1.0"?>

<object>

  <order manager="plone.portalheader" skinname="My Theme"
         based-on="Plone Default">
    <viewlet name="plone.logo" insert-before="*"/>
  </order>
  <order manager="plone.portaltop" skinname="*">
    <viewlet name="plone.app.i18n.locales.languageselector"
             insert-after="plone.path_bar"/>
  </order>

</object>

The above code contains two <order /> declarations: The first one creates a new skin named My Theme which is based on the Plone Default one. This means that the new skin inherits from the viewlets ordering of the skin it is based on. In our example, the plone.logo is moved at the first position in the plone.portalheader viewlet manager. The second declaration moves plone.app.i18n.locales.languageselector right after plone.path_bar in the plone.portaltop viewlet manager for all skins (notice the skinname="*" statement).

Note

Both DIYPloneStyle and ZopeSkel add a viewlets.xml file when generating a blank theme product (use --add-viewlet-example with the DIYPloneStyle generator script).

For your convenience, you can find inline documentation about the parameters that can be used in the generated file (It is an option with ZopeSkel, answer 'True' when it asks for it).

Hiding

Hiding a viewlet is also done from the viewlets.xml with the <hidden /> node which is at same level as <order />, and is done per skin selection.

For instance, if you need to remove the global sections for your skin, you'd have to add some declaration like the following one to viewlets.xml:

<hidden manager="plone.portalheader" skinname="My Theme">
  <viewlet name="plone.global_sections" />
</hidden>

Unhiding

If, for any reason, you need to unhide one or more viewlets for a given viewlet manager, you can make use of the purge and remove node parameters in your <hidden /> declaration in viewlets.xml.

To unhide all hidden viewlets for a given viewlet manager:

<?xml version="1.0"?>
<object>
  <hidden manager="plone.portalheader" skinname="Plone Default"
          purge="True" />
</object>

To unhide specific viewlet(s):

<?xml version="1.0"?>
<object>
  <hidden manager="plone.portalheader" skinname="Plone Default">
    <viewlet name="plone.global_sections" remove="True" />
  </hidden>
</object>
Note
The purge and remove node parameters are also supported inside the <order /> declaration.

Nothing that difficult here ;-)

Adding a viewlet

How to add a viewlet and still have several skins living in peace.

Hiding and reordering viewlets within a viewlet manager is something we can do, we now have to cover how to add a new one.

Example

First, lets have a look at one of the examples that are shipped with DIYPloneStyle (version 3.0 is out, still beta but useable) in order to have a better understanding of the machinery: The credits_viewlet product that is located in DIYPloneStyle/example/ adds a new viewlet to the portal footer region.

In the credits_viewlet code, in its browser/ folder, we have a set of files:

- browser/
    - __init__.py
    - configure.zcml
    - interfaces.py
    - logo.pt
    - viewlets.py
__init__.py
This is an empty file, that is here simply to make browser a python package.
configure.zcml
The file where all Zope 3 configuration is defined for browser.
interfaces.py
We'll need this file a bit later, I leave it where it is for now.
logo.pt
The template that will replace the original HTML code in the main template.
viewlets.py
The file that stores viewlet Python classes.

In the same DIYPloneStyle/example/credits_viewlet subfolder, we find the profiles/ repository that stores the Generic Setup configuration files: skins.xml and viewlets.xml.

Practice

Let's get back to our own MyTheme product and reproduce the example we just had a look at.

Viewlet registration

In order to create our new viewlet, we'll have to write a viewlet class in MyTheme/browser/viewlets.py (that renders the MyTheme/browser/credits.pt template), and register it for the viewlet manager we want it to show in (from MyTheme/browser/configure.zcml). Then we'll have to order it at the place we want it to show within the viewlet manager (in MyTheme/profiles/default/viewlets.xml).

Note
If you generate your theme product with DIYPloneStyle, you have to use the --add-viewlet-example option while calling the generator script in order to have the viewlet base code included in your project. The plone3_theme from ZopeSkel includes viewlet base code by default.

We have to edit the following files…

MyTheme/browser/__init__.py:

This is an empty but needed file that makes browser/ a python package.

In MyTheme/browser/viewlets.py:

from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
from plone.app.layout.viewlets.common import ViewletBase

DESIGNER = 'John Doe'

class CreditsViewlet(ViewletBase):
    render = ViewPageTemplateFile('credits.pt')

    def update(self):
        # set here the values that you need to grab from the template.
        # stupid example:
        self.designer = DESIGNER

In MyTheme/browser/credits.pt:

<div id="design-credits"
     i18n:domain="custom_i18n_domain">

    <span i18n:translate="design_by">Design by:
        <span tal:omit-tag="python:True"
              tal:content="view/designer"
              i18n:name="designer">Terry Gilliam</span>.</span>

</div>

In MyTheme/browser/configure.zcml:

<configure
    xmlns="http://namespaces.zope.org/zope"
    xmlns:browser="http://namespaces.zope.org/browser"
    i18n_domain="custom_i18n_domain">

    <browser:viewlet
        name="example.credits"
        manager="plone.app.layout.viewlets.interfaces.IPortalFooter"
        class=".viewlets.CreditsViewlet"
        permission="zope2.View"
        />

</configure>

Don't forget to include the browser package in MyTheme/configure.zcml:

<configure
   xmlns="http://namespaces.zope.org/zope"
   xmlns:browser="http://namespaces.zope.org/browser">

   <include file="profiles.zcml" />

   <include package=".browser" />

</configure>

In MyTheme/profiles/default/viewlets.xml:

<?xml version="1.0"?>
<object>
  <order manager="plone.portalfooter" skinname="Credits Viewlet Theme"
         based-on="Plone Default">
    <viewlet name="example.credits" insert-before="plone.colophon" />
  </order>
</object>

We can register our new skin from MyTheme/profiles/default/skins.xml:

<?xml version="1.0"?>
<object name="portal_skins">

    <skin-path name="Credits Viewlet Theme" based-on="Plone Default" />

</object>

Now we can restart Zope and install our product in Plone.

No problem so far, after restarting Zope, installing our product in Plone, and refreshing the portal home page, we see that the footer region has been modified and now displays, in addition to its default content, a message telling who owns credits for the site design.

Skin layer

Now let's go, as portal manager, to Site Setup > Themes in the Plone interface, and select Plone Default as default theme.

After refreshing the portal home page, we see that the credits viewlet is still rendered by the bottom of the page, which is not what we want.

We must find a way to make the credits viewlet render only for the skin it was designed for. We could hide it for all other themes from the viewlets.xml Generic Setup file with the <hidden /> node, but that won't work if you want to design a distributable product. You never know what other theme(s) could be installed on any portal that will have yours installed on.

We can register a viewlet only for one theme (one skin selection) thanks to the plone.theme package. Thanks to plone.theme, we can set a Zope 3 skin layer that corresponds to a skin selection in portal_skins (a theme).

In order to set that skin layer up, add or edit the following files in the browser/ folder in our MyTheme product:

  • MyTheme/browser/interfaces.py:

    from plone.theme.interfaces import IDefaultPloneLayer
    
    class IThemeSpecific(IDefaultPloneLayer):
        """Marker interface that defines a Zope 3 skin layer bound to a Skin
           Selection in portal_skins.
        """
    

It is not needed to rename that interface class if you have more than one theme product. There will be no clash with other products holding the same interface in their own browser/ directories.

  • MyTheme/browser/configure.zcml:

    <configure
       xmlns="http://namespaces.zope.org/zope"
       xmlns:browser="http://namespaces.zope.org/browser">
    
       <interface
          interface=".interfaces.IThemeSpecific"
          type="zope.publisher.interfaces.browser.IBrowserSkinType"
          name="My Theme"
          />
    
       <browser:viewlet
          name="example.credits"
          manager="plone.app.layout.viewlets.interfaces.IPortalFooter"
          class=".viewlets.CreditsViewlet"
          permission="zope2.View"
          layer=".interfaces.IThemeSpecific"
          />
    
    </configure>
    

Note the interface declaration for the Zope 3 skin layer, and the layer parameter for the viewlet itself.

The value for the name parameter in the interface declaration must be the name of the theme added by MyTheme (also known as skin name), as seen in the Site setup > Themes Plone control panel.

The value for the layer attribute in the viewlet declaration must be the interface that is used to define the skin layer.

After restarting Zope (or refreshing our product), and reloading the portal home page, we can see that the Plone Default theme doesn't show our viewlet up anymore.

Cool :-)

Overriding a template viewlet

How to use Zope 3 technology to override a viewlet with template definition.

In the previous chapters of this tutorial we learned how to add a viewlet to a viewlet manager for a specific theme, and how to hide a viewlet from a viewlet manager in a specific theme. By combining these two techniques we should be able to override a default Plone viewlet with a custom one for a specific theme. It shouldn't be that different than hiding the targeted Plone viewlet and adding/positioning a custom one.

Actually, it is different, and simpler.

Theory

The key concept here is the use of a Zope 3 skin layer.

We saw in the previous chapter how to make use of a Zope 3 skin layer to make sure that the viewlet we added to our theme would not render in any other one. In this chapter we will make use of the same skin layer to override a default Plone viewlet with a custom one.

Principle is this:

In our product (or package), we declare in configure.zcml a viewlet that has the same name as one of the Plone default viewlets, but with a different constructor class or template than the original one (We can use a template even if the original declares a class and vice and versa).

By using the layer attribute in the custom viewlet zcml declaration, we register the viewlet for a Zope 3 skin layer. That skin layer has the same name as the name of the theme that is added to the portal_skins tool.

Zope 3 skin layers are bound to skin selections in portal_skins (themes) thanks to the plone.theme package. Which means that a skin layer is applied if its name matches the name of the selected skin in portal_skins.

A viewlets that is registered for a skin layer is rendered when that layer is applied, and overrides any other viewlet that has the same name but which is not registered for that layer.

Practice

Let's see how we can apply this viewlet overriding within MyTheme, the product that we started to develop in the previous chapters.

Our goal here is to have the footer rendering a different text than the default Plone one in our theme.

1. Creating/Registering the Zope 3 skin layer

Both ZopeSkel and DIYPloneStyle generate code that defines the skin layer interface in MyTheme/browser/interfaces.py. The interface class is named IThemeSpecific and inherits from plone.theme.interfaces.IDefaultPloneLayer.

The plone3_theme template of ZopeSkel generates a package which includes a browser/ directory and a Zope 3 skin layer by default.

That is not the case with the generator script of DIYPloneStyle.

In order to have a Zope 3 skin layer added to a product generated with DIYPloneStyle, you have to run the script with the option --add-viewlet-example.

If you already generated your product with DIYPloneStyle without the --add-viewlet-example option, see in the previous chapter how to add the skin layer manually.

2. Identifying which viewlet to override

We have to identify which viewlet we want to override, and for which viewlet manager it is registered.

Plone ships with a basic user interface for reordering and hiding/showing viewlets inside their viewlet managers. To access that configuration page, point your browser to the URL http://localhost:8080/portal/@@manage-viewlets (or equivalent based on your Zope/Plone installation).

The viewlets management page should look like this one (rendered with the Plone Default theme):

manage-viewlets.jpg

By the end of that page, we can identify the footer region, and note that it is rendered by a viewlet manager called plone.portalfooter. We can also see that two viewlets are registered with plone.portalfooter: plone.footer and plone.colophon. plone.footer is our target: it is the one that we want to see rendered differently in our theme.

3. Write the code that renders the viewlet

Now that we know which viewlet we want to override, let's have a look at its code in our Plone installation on the file system. Plone default viewlets are declared in the plone.app.layout.viewlets package.

Most of the time, that package can be found in $INSTANCE_HOME/lib/python/plone/app/layout/viewlets/. It might be located somewhere else in the $PYTHONPATH though. Depending on the zope installation (win32 or unix like operating system, installation from source, installer, eggs or else…), you may need to use the search tools available in your operating system to locate the package.

Inside the plone.app.layout.viewlets package, we see in configure.zcml where is the code that renders the default plone.footer viewlet in the Plone Default theme. We find the default footer viewlet declaration by the end of the file:

<!-- Footer -->
<browser:viewlet
   name="plone.footer"
   for="*"
   manager=".interfaces.IPortalFooter"
   template="footer.pt"
   permission="zope.Public"
   />

We note that this viewlet isn't rendered by a Zope 3 view but directly from a template, which name is footer.pt. We can make a copy of that template in the browser/ directory of MyTheme. No need to give it a different name.

Edit the template to make it render a different text than the one in the Plone Default theme, for instance:

<div id="portal-footer" i18n:domain="plone">
  <p>
    <span i18n:translate="design_author" tal:omit-tag="">
      Design by
      <span i18n:name="author" tal:omit-tag="">
        <a href="http://mypage.org">John Doe</a>
      </span>.
    </span>
  </p>
</div>

4. Register the custom viewlet in configure.zcml

In order to register the template with the portal footer viewlet manager, we simply copy the portion of ZCML code that we found in lib/python/plone/app/layout/viewlets/configure.zcml (see upper in this page) into MyTheme/browser/configure.zcml, and we edit it to make sure it uses the right interfaces for the manager and the Zope 3 skin layer:

<!-- The customized footer -->
<browser:viewlet
   name="plone.footer"
   for="*"
   manager="plone.app.layout.viewlets.interfaces.IPortalFooter"
   layer=".interfaces.IThemeSpecific"
   template="footer.pt"
   permission="zope.Public"
   />

5. Done

That's all, we can now restart Zope, install the new theme from Site Setup > Add-on Products in the Plone interface, and reload the portal front page (or any other page) to see the result in the footer region.

Other example

Another example of viewlet overriding can be found in DIYPloneStyle/example/custom_footer/. It is quite easy to understand and should be self explanatory now that you read most of this chapter.

This example is slightly different than the one we just covered though: the new viewlet uses a Zope 3 browser view for its rendering. Principles are the same anyway.

The product layout on the file system is also a bit different than the one typically found in a product generated by DIYPloneStyle or ZopeSkel. All viewlet code is kept at first level package in a browser.py module. There is no browser sub-package (folder). This is for showing that there is more than one only possible layout for organizing a plone theme package/product.

  • In browser.py, we find IThemeSpecific, the interface of the Zope 3 skin layer, and FooterViewlet, the viewlet class that overrides the default plone one.
  • footer.pt is the template rendered by FooterViewlet.
  • profiles/default/ is the usual Generic Setup profile for the product.
  • The interface, the viewlet and the product Generic Setup profile are all declared in a single configure.zcml.

And now… happy customizing!

Overriding a class viewlet

How to use Zope 3 technology to override a viewlet with class definition.

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">&rarr;</tal:ltr>
        <tal:rtl condition="view/is_rtl">&raquo;</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.

Actually 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.

Moving a viewlet from a viewlet manager to another one

The title says it all…

To achieve moving a viewlet from a viewlet manager to another one, we'll have to apply what we already covered in the previous paragraphs:

We have to hide the viewlet from the viewlet manager that we want to take it out from, and register it to the other viewlet manager for the zSubject that corresponds to our theme.

Let's assume that we want to move the portal actions from top to bottom…

in browser/interfaces.py, we need to setup our ZSubject:

from plone.theme.interfaces import IDefaultPloneLayer

class IMyThemeSpecific(IDefaultPloneLayer):
    """Marker interface that defines a ZSubject.
       It will be used for the viewlets that we want to add to the
       "My Theme" skin only.
    """

in browser/configure.zcml:

<configure
    xmlns="http://namespaces.zope.org/zope"
    xmlns:browser="http://namespaces.zope.org/browser"
    i18n_domain="mytheme">

    <interface
        interface=".interfaces.IMyThemeSpecific"
        type="zope.publisher.interfaces.browser.IBrowserSkinType"
        name="My Theme"
        />

    <!-- Moved viewlet registration -->
    <browser:viewlet
        name="plone.site_actions"
        manager="plone.app.layout.viewlets.interfaces.IPortalFooter"
        class="plone.app.layout.viewlets.common.SiteActionsViewlet"
        permission="zope2.View"
        layer=".interfaces.IMyThemeSpecific"
        />

</configure>

in profiles/default/viewlets.xml:

<?xml version="1.0"?>
<object>
  <order manager="plone.portalfooter" skinname="My Theme"
         based-on="Plone Default">
    <viewlet name="plone.site_actions" insert-after="*" />
  </order>
  <!-- We hide the one we want to move -->
  <hidden manager="plone.portalheader" skinname="My Theme">
    <viewlet name="plone.site_actions" />
  </hidden>
</object>

Done!

All we did, we did it without touching main_template.pt, isn't it a success?


For any issues with the web site functionality, please file a ticket.

Please consult the policy on plone.org content if you want your content published on this site.

Servers and hosting by