Viewlets are view snippets which will render a part of the HTML page. Viewlets provide conflict-free way to contribute new user-interface actions and snippets to Plone pages.
# Your product must have GenericSetup profile registered
<configure
xmlns="http://namespaces.zope.org/zope"
xmlns:five="http://namespaces.zope.org/five"
xmlns:genericsetup="http://namespaces.zope.org/genericsetup"
i18n_domain="plone.app.headeranimation">
<!-- Register the installation GenericSetup extension profile -->
<genericsetup:registerProfile
name="default"
title="Header Animations"
directory="profiles/default"
description="Integrating external content to Plone site"
provides="Products.GenericSetup.interfaces.EXTENSION"
/>
</configure>
Viewlets have two important methods
See http://svn.plone.org/svn/plone/plone.app.layout/trunk/plone/app/layout/viewlets/common.py
Below is a complex example how to expose viewlets without going through a viewlet manager.
See collective.fastview for updates and more information.
from Acquisition import aq_inner
import zope.interface
from plone.app.customerize import registration
from Products.Five.browser import BrowserView
from zope.traversing.interfaces import ITraverser, ITraversable
from zope.publisher.interfaces import IPublishTraverse
from zope.publisher.interfaces.browser import IBrowserRequest
from zope.viewlet.interfaces import IViewlet
from zExceptions import NotFound
class Viewlets(BrowserView):
""" Expose arbitary viewlets to traversing by name.
Exposes viewlets to templates by names.
Example how to render plone.logo viewlet in arbitary template code point::
<div tal:content="context/@@viewlets/plone.logo" />
"""
zope.interface.implements(ITraversable)
def getViewletByName(self, name):
""" Viewlets allow through-the-web customizations.
Through-the-web customization magic is managed by five.customerize.
We need to think of this when looking up viewlets.
@return: Viewlet registration object
"""
views = registration.getViews(IBrowserRequest)
for v in views:
if v.provided == IViewlet:
# Note that we might have conflicting BrowserView with the same name,
# thus we need to check for provided
if v.name == name:
return v
return None
def setupViewletByName(self, name):
""" Constructs a viewlet instance by its name.
Viewlet update() and render() method are not called.
@return: Viewlet instance of None if viewlet with name does not exist
"""
context = aq_inner(self.context)
request = self.request
# Perform viewlet regisration look-up
# from adapters registry
reg = self.getViewletByName(name)
if reg == None:
return None
# factory method is responsible for creating the viewlet instance
factory = reg.factory
# Create viewlet and put it to the acquisition chain
# Viewlet need initialization parameters: context, request, view
try:
viewlet = factory(context, request, self, None).__of__(context)
except TypeError:
# Bad constructor call parameters
raise RuntimeError("Unable to initialize viewlet %s. Factory method %s call failed." % (name, str(factory)))
return viewlet
def traverse(self, name, further_path):
"""
Allow travering intoviewlets by viewlet name.
@return: Viewlet HTML output
@raise: RuntimeError if viewlet is not found
"""
viewlet = self.setupViewletByName(name)
if viewlet is None:
raise NotFound("Viewlet does not exist by name %s for theme layer %s" % name)
viewlet.update()
return viewlet.render()
Default viewlet managers render viewlets as HTML code string concatenation, in the order of appearance. This is unsuitable to build complex layouts.
Below is an example which defines master viewlet HeaderViewlet which will place other viewlets into the manually tuned HTMLable.
header.py:
from Acquisition import aq_inner
# Use template files with acquisition support
from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
# Import default Plone viewlet classes
from plone.app.layout.viewlets import common as base
# Import our customized viewlet classes
import plonetheme.something.browser.viewlets.common as something
def render_viewlet(factory, context, request):
""" Helper method to render a viewlet """
context = aq_inner(context)
viewlet = factory(context, request, None, None).__of__(context)
viewlet.update()
return viewlet.render()
class HeaderViewlet(base.ViewletBase):
""" Render header with special le layout.
Though we render viewlets internally we not inherit from the viewlet manager,
since we do not offer the option for the site manager or integrator
shuffle viewlets - they are fixed to our templates.
"""
index = ViewPageTemplateFile('header_items.pt')
def update(self):
base.ViewletBase.update(self)
# Dictionary containing all viewlets which are rendered inside this viewlet.
# This is populated during render()
self.subviewlets = {}
def renderViewlet(self, viewlet_class):
""" Render one viewlet
@param viewlet_class: Class which manages the viewlet
@return: Resulting HTML as string
"""
return render_viewlet(viewlet_class, self.context, self.request)
def render(self):
self.subviewlets["logo"] = self.renderViewlet(something.SomethingLogoViewlet) # Customized viewlet
self.subviewlets["second_level_navigation"] = self.renderViewlet(something.SecondLevelSectionsViewlet)
self.subviewlets["sections"] = self.renderViewlet(something.SomethingGlobalSectionsViewlet)
self.subviewlets["search"] = self.renderViewlet(base.SearchBoxViewlet) # Default Plone viewlet
self.subviewlets["selector"] = self.renderViewlet(something.SomethingTransla leLanguageSelector)
self.subviewlets["site_actions"] = self.renderViewlet(something.SiteActionsViewlet)
# Call template to perform rendering
return self.index()
header_items.pt
<table class="something-header">
<tbody>
<tr class="upper">
<td class="left">
<div tal:replace="structure view/subviewlets/logo" />
</td>
<td>
< le class="right">
<tbody>
<tr>
<td>
<div tal:replace="structure view/subviewlets/search" />
</td>
</tr>
<tr>
<td>
<a href="http://www.something.fi">
<img tal:attributes="src string:${view/site_url}/++resource++plonetheme.something/something_logo.gif" />
</a>
</td>
<td>
<div tal:replace="structure view/subviewlets/selector" />
</td>
</tr>
</tbody>
</ le>
</td>
</tr>
<tr class="lower">
<td class="left">
<div tal:replace="structure view/subviewlets/sections" />
</td>
<td class="right">
<div tal:replace="structure view/subviewlets/site_actions" />
</td>
</tr>
</tbody>
</ le>
configure.zcml
<configure xmlns="http://namespaces.zope.org/zope"
xmlns:browser="http://namespaces.zope.org/browser"
xmlns:plone="http://namespaces.plone.org/plone"
xmlns:zcml="http://namespaces.zope.org/zcml"
>
<!--
Public localizable site header
See viewlets.xml for order/hidden
-->
<!-- Header viewlet which replaces the standard plone.header-->
<browser:viewlet
name="plone.header"
manager="plone.app.layout.viewlets.interfaces.IPortalTop"
class=".header.HeaderViewlet"
permission="zope2.View"
layer="..interfaces.IThemeSpecific"
/>
<!-- Site actions-->
<browser:viewlet
name="plonetheme.something.site_actions"
class=".common.SiteActionsViewlet"
permission="zope2.View"
template="site_actions.pt"
layer="..interfaces.IThemeSpecific"
allowed_attributes="site_actions"
manager="..interfaces.IHeader"
/>
<!-- The logo -->
<browser:viewlet
name="plonetheme.something.logo"
class=".common.SomethingLogoViewlet"
permission="zope2.View"
layer="..interfaces.IThemeSpecific"
template="logo.pt"
manager="..interfaces.IHeader"
/>
<!-- Searchbox -->
<browser:viewlet
name="plone.searchbox"
for="*"
class="plone.app.layout.viewlets.common.SearchBoxViewlet"
permission="zope2.View"
template="searchbox.pt"
layer="..interfaces.IThemeSpecific"
manager="..interfaces.IHeader"
/>
<!-- First level navigation -->
<browser:viewlet
name="plonetheme.something.global_sections"
for="*"
class=".common.SomethingGlobalSectionsViewlet"
permission="zope2.View"
template="sections.pt"
layer="..interfaces.IThemeSpecific"
manager="..interfaces.IHeader"
/>
<!-- Second level navigation -->
<browser:viewlet
name="plonetheme.something.second_level_sections"
class=".common.SecondLevelSectionsViewlet"
permission="zope2.View"
template="second_level_sections.pt"
layer="..interfaces.IThemeSpecific"
manager="..interfaces.IHeader"
/>
<!-- Language selector-->
<browser:viewlet
name="something.languageselector"
class=".common.SomethingTransla leLanguageSelector"
permission="zope2.View"
layer="..interfaces.IThemeSpecific"
manager="..interfaces.IHeader"
/>
</configure>
portal_header.pt
<div id="portal-header">
<div tal:replace="structure provider:something.header" />
</div>
Viewlets can be registered to one special page only using a marker interface. This allow loading a page specific CSS files.