Adventures In Theming – The Complete Saga

The Beginning

The Concept

I’m working on a large-scale inventory management system built as a series of Plone content types. It’s primary focus is on scientific research labs in a university setting. This means we’ll have a lot of individual labs with specific needs using the system in different ways. I took on the task of building a generic, centralized theme that each lab theme can extend. I wanted to define a specific look for the system, but also allow instances to be tailored for end users.

The Design

The first step was to build a mockup in a graphics program, and show it around to my colleagues for review.

The (very) basic layout with most of the stock Plone viewlets removed.

The (very) basic layout with most of the stock Plone viewlets removed.

I produced this in Inkscape. It’s heavily based on the stock Plone theme, but things are re-arranged a bit. I could have gone to more extremes in moving things around, but at this point what I could do with the viewlets and portlets is pretty much unknonwn. No sense in pushing the envelope when you haven’t written the letter yet :)

Implementation Goals

I want the theme to be customizable in simple ways, and easily extended, so I want to place strong emphasis on CSS for the look and feel, and utilize Plone conventions to control the regions that are being styled.

By studying the default Plone theme and comparing it to my design, I was able to break down what the differences into a replacement header, with existing components rearranged within it. I’d then hide the existing header elements.

This accomplishes a couple of things:

  • The theme stays flexible. Theoretically, the viewlets in my new header can be controlled by the @@manage-viewlets view, so they can be changed as needed after the theme is installed (although I’ll probably want to keep the changes in the theme product)
  • Maximizes code re-use. By re-styling and re-organizing the stock viewlets, I can avoid re-implementing the stock viewlet code. This should help keep the theme future-proof.

I’m (still) not sure if this is the best possible approach, but I think it’s flexible enough to allow a site to fall back to plone defaults in a worst-case scenario.

Now that I’ve got my layout and an idea of how this will happen, I needed to build the theme product.

The Plan: The Lone Viewlet

After reading the reference materials I had available (primarily Professional Plone Development and Web Component Development with Zope 3) on viewlets and the provider prefix, I came up with a theory. There weren’t many examples of viewlet manager creation; most of the information I had was focused on overriding existing viewlets. I didn’t want to stick my neck out too far, so a lone viewlet, possibly overriding and existing viewlet, would be the way to go.

I thought that because viewlets were registered providers, I could easily get the stock plone viewlets I wanted to re-organize to show up in my new viewlet by simply using the provider prefix in TALES expressions in my page template.

This was a bad plan. When I started testing my theory, it became apparent that viewlets are only accessible from within their viewlet managers. Worse, it seemed that only viewlet managers are presentable using the provider prefix.

During the process of putting this series together, I’ve come to the realization that this is due to a silly bug in Plone’s viewlet base. More on this later.

UPDATE: I’ve collected the details on this bug in a follow up post. Check that out if you really don’t want to write your own viewlet manager.

The Other Plan: DIY Viewlet Manager

If a hack won’t work, you just have to do things “right”. :P Apparently, the correct way to reuse existing viewlets is to create your own viewlet manager and assign those viewlets to it using ZCML. You can rename the viewlets if you’d like, or extend the viewlets if you need to, but it all revolves around creating your own viewlet manager.

I went down this path twice. The first time I hadn’t seen this: http://plone.org/documentation/manual/theme-reference/elements/viewletmanager. Or if I had, I didn’t look at it critically enough.

I built a viewlet manager using Web Component Development with Zope 3, and it just wouldn’t show up in Plone’s viewlet manager.

What I didn’t know until I was turned on to the tutorial, was that Plone has to specifically render each viewlet manager it wants in main_template (which is the core template that Plone uses to display everything). There’s a slight reference on the page about moving viewlet managers about how to move a manager in your own product. This was the break I needed. All I had to do was edit main_template to get my viewlet manager to stick.

I’m not happy with this, though. I shouldn’t have to duplicate a core piece of Plone functionality just to add one little additional piece of functionality. Still a small price to pay for total control :).

The Product

The Theme Product

I’ve done a lot of Plone development over the past couple of years, but I’ve never done anything with theming. This is my first attempt at theming Plone. From what I’ve seen, it’s probably good that I’ve waited. :) The way themes are handled in Plone has changed a lot over the past couple of years, and I get the feeling that if I had become a theming pro when I first started with Plone, this could prove an even greater challenge.

Another good reason that I’ve waited, whichb contributes in the hair-pulling-reduction-department: there’s a plone3_theme template in the ZopeSkel suite. Score!

I first saw the template in action at the Plone conference in DC last year, during Rob Porter’s “Theming a Plone 3.1 site from start to finish” presentation. It seemed like a real blessing to theme developers: it’s got the wiring necessary to build an old-school “skins layer” theme, and also add in viewlets and other zope3 tech that is available to us in modern times. It’s a bit complicated at first glance, but once you get your bearings and start customizing things, you realize it’s quite a useful tool.

If you’ve never messed with ZopeSkel before, be sure to check out the PyPI page and My quickstart guide to get up and running.

First, as I often do when starting a new Plone project, I start with a plone 3 buildout skeleton (I like to name my buildout after the egg that’s being developed):

$ cd ~
$ paster create -t plone3_buildout my.theme
Selected and implied templates:
  ZopeSkel#plone3_buildout  A buildout for Plone 3 projects

Variables:
  egg:      my.theme
  package:  mytheme
  project:  my.theme
Enter zope2_install (Path to Zope 2 installation; leave blank to fetch one) ['']: 
Enter plone_products_install (Path to directory containing Plone products; leave blank to fetch one) ['']: 
Enter zope_user (Zope root admin user) ['admin']: 
Enter zope_password (Zope root admin password) ['']: admin
Enter http_port (HTTP port) [8080]: 
Enter debug_mode (Should debug mode be "on" or "off"?) ['off']: on
Enter verbose_security (Should verbose security be "on" or "off"?) ['off']: on
...
-----------------------------------------------------------
Generation finished
You probably want to run python bootstrap.py and then edit
buildout.cfg before running bin/buildout -v

See README.txt for details
-----------------------------------------------------------

The answers to the questions are pretty straightforward; my usual modus operandi when creating a new development buildout.

So now that I’ve got my buildout, I need to create a skeleton for the theme, and then wire it all up in my buildout.cfg.

$ cd my.theme/src
$ paster create -t plone3_theme my.theme
Selected and implied templates:
  ZopeSkel#basic_namespace  A project with a namespace package
  ZopeSkel#plone            A Plone project
  ZopeSkel#plone3_theme     A Theme for Plone 3.0

Variables:
  egg:      my.theme
  package:  mytheme
  project:  my.theme
Enter namespace_package (Namespace package (like plonetheme)) ['plonetheme']: my
Enter package (The package contained namespace package (like example)) ['example']: theme
Enter skinname (The skin selection to be added to 'portal_skins' (like 'My Theme')) ['']: MY Theme
Enter skinbase (Name of the skin selection from which the new one will be copied) ['Plone Default']: 
Enter empty_styles (Override default public stylesheets with empty ones?) [True]: False
Enter include_doc (Include in-line documentation in generated code?) [False]: 
Enter zope2product (Are you creating a Zope 2 Product?) [True]: 
Enter version (Version) ['1.0']: 0.1
Enter description (One-line description of the package) ['An installable theme for Plone 3.0']: MY base theme that other specific themes are based on
Enter long_description (Multi-line description (in reST)) ['']: 
Enter author (Author name) ['Plone Collective']: Josh Johnson
Enter author_email (Author email) ['product-developers@lists.plone.org']: lionface.lemonface at gmail dot com
Enter keywords (Space-separated keywords/tags) ['web zope plone theme']: my base jjmojojjmojo
Enter url (URL of homepage) ['http://svn.plone.org/svn/collective/']: https://lionfacelemonface.wordpress.com/2009/01/12/adventures-in-theming
Enter license_name (License name) ['GPL']: 
Enter zip_safe (True/False: if the package can be distributed as a .zip file) [False]: 
...
------------------------------------------------------------------------------
The project you just created has local commands. These can be used from within
the product.

usage: paster COMMAND

Commands:
  addcontent  Adds plone content types to your project

For more information: paster help COMMAND
------------------------------------------------------------------------------
Running /home/jj/zopeskel/bin/python setup.py egg_info

Most of the questions are old news if you’ve done ZopeSkel-based products before, but there are a few questions that are little more confusing than the usual plone product questions.

  • Enter skinname (The skin selection to be added to ‘portal_skins’ (like ‘My Theme’)) This question relates to giving your theme a skin name. This helps isolate your changes so that the end user can switch themes later.

  • Enter empty_styles (Override default public stylesheets with empty ones?) This question is asking you if you’d like all of the default plone stylesheets replaced with empty shells. Answering ‘True’ to this question will essentially remove all of Plone’s default styling. This is good if you’d like to start completely from scratch, but in my case, I want to leave a large amount of the default plone styling intact, so I specified ‘False’.

  • Enter skinbase (Name of the skin selection from which the new one will be copied) I’m not a hundred percent sure what this is. I believe it has to do with GenericSetup and how the skin is initialized, but I haven’t tried anything but the default so far.

  • Enter include_doc (Include in-line documentation in generated code?)
    This is a switch that turns on or off a whole bunch of inline documentation. The first time you create a theme product, it can be very very helpful, but is just a lot of extra bytes in your code otherwise. I’ve opted to turn it off here. It can also be useful to create an additional theme product with the inline docs turned on so you can refer to them as you develop your target theme.

Now that I’ve got a plone theme, it just needs to be wired up into plone via buildout.cfg, settting up my.theme as a “development egg”. This is accomplished using the ‘develop’ configuration directive under the [buildout] section, and the ‘zcml’ and ‘eggs’ directives under the [instance] section.

[buildout]
...
develop =
    src/my.theme
...
[instance]
eggs =
    ${buildout:eggs}
    ${plone:eggs}
    my.theme
...
zcml =
    my.theme

What’s Next

At this point I’ve got a working plone theme that’s ready to be customized. My first step in that direction will be to create my own custom viewlet manager.

Rolling My Own Viewlet Manager

The Viewlet Manager

There are 4 parts to a viewlet manager. There’s a class that implements zope.viewlet.interfaces.IViewletManager, a template that renders the output of the manager, some configuration in ZCML that identifies the class as a viewlet manager, and a marker interface that facilitates the association of the viewlets with this particular viewlet manager.

All of this configuration will occur in the browser directory.

The Class

The class is very simplistic in this case. Most of the code that you may be used to seeing (say, in side of a browser view class) is handled by ZCML directives.

src/my.theme/my/theme/browser/viewletmanager.py

from Products.Five.viewlet.manager import ViewletManagerBase

class MyHeaderViewletManager(ViewletManagerBase):
    """
    A custom viewlet manager to re-organize some stock plone viewlets.
    """

That’s it (at least for now). Note that we’re inheriting from Products.Five.viewlet.manager.ViewletManagerBase. Generally speaking, if you see something in a zope 3 reference (like Philipp’s book), you should check for a Five equilivent, and use that if you can.

The Interface

In order for the viewlet manager to be found by the provider prefix later on, it must provide an interface that inherits from zope.viewlet.interfaces.IViewletManager. Technically, I think there’s a deeper provider-related interface that’s required, but for our purposes IViewletManager is sufficient.

This is a marker interface, and it will be used to associate viewlets with our viewlet manager later on.

src/my.theme/my/theme/browser/interfaces.py

from plone.theme.interfaces import IDefaultPloneLayer
from zope.viewlet.interfaces import IViewletManager

class IThemeSpecific(IDefaultPloneLayer):
    """Marker interface that defines a Zope 3 browser layer.
    """

class IMyTop(IViewletManager):
    """
    Marker interface for viewlets that can be displayed in my custom header
    viewlet manager
    """

The ZCML

The real heavy lifting in setting up a viewlet manager happens via ZCML, in the <browser:viewletManager> tag.

src/my.theme/my/theme/browser/configure.zcml

...
<!-- Viewlets registration -->
  <browser:viewletManager
     name="my.header"
     provides=".interfaces.IMyTop"
     class=".viewletmanager.MyHeaderViewletManager"
     layer=".interfaces.IThemeSpecific"
     permission="zope2.View"
     template="myheader.pt"  
     />
... 

The name designates an identifier for the viewlet manager. provides is (more or less) equilivent to adding a implements() call in your viewlet manager class. class specifies the class name. permission sets the zope permission for the viewlet manager (this would allow you to create special viewlet managers for logged in users, for example).

The layer attribute is important, it helps to separate your theme configuration out from other themes (especially the default plone theme). This is accomplished by indicating a theme-specific interface, which ZopeSkel sets up for you as my.theme.browser.interfaces.IThemeSpecific.

Finally, the template directive lets you specify a zope page template that will be used to render the viewlet manager.

The Template

This template is very simple. It’s aim is to break the header up into two columns, so my header will stretch to handle the width of the browser window (and I can put different background images on each side). It also puts the "personal bar" and the "global sections" into one line. I accomplish this with divs, which will be styled later on.

src/my.theme/my/theme/browser/myheader.pt

<div id="my_header">
    <div id="main">
        <div id="left_side">
        Left-wing
        </div>
        <div id="right_side">
        Right-wing
        </div>
    </div>
    <br />
    <div>
        <div id="personal_bar">
            Personal Bar
        </div>
        <div id="sections">
            Global Tabs
        </div>
    </div>
    <br />
    <div id="crumbtrail">
        The Crumb Trail
    </div>
</div>

I’ll root all of my css rules on that main #my_header div, so I should avoid any major namespace collisions.

First Test

At this point, I’ve got a working custom viewlet manager. Assuming you’ve already run bootstrap.py and bin/buildout, running bin/instance fg should run without any errors.

This is great, but if you want to know for sure that it worked (and you aren’t writing unit tests, whcih you probably should), you’ll have to add a plone site and install your product. Then reload the main page and… alas, you still won’t see anything.

As I mentioned earlier, viewlet managers aren’t dynamically pulled into the main plone template, which is aptly named main_template. You have to add a TALES expression into that template to get your viewlet manager to render.

Altering main_template

This can be accomplished in the short term by finding the template in the ZMI and using the "customize" feature to copy it into your custom directory. A better way is to override main_template in your skin.

This is jumping the gun a little bit, since I wanted to talk about resources and customizations later on, but I think it’s important to see something working at this stage in the process.

The Through The WebTM (TTW) way is a classic zope/plone customization use case. If you’ve done anything serious with Plone, you’ve probably done this before. I’m going to cover it here since I didn’t really do it much in the past, and I think it’s a great way to do quick prototyping or poke around in templates that people reference by id.

Here are the steps:

  1. Fist make sure that your Plone instance is up and running.

  2. Open up http://localhost:8080/plone. (I’m assuming you’ve got a plone site object already and you set up zope to listen on port 8080). Log in as an administrator or manager (if you used the buildout above log in with username/password admin/admin).

  3. Click on the ‘site setup’ link in the upper right hand corner (http://localhost:8080/plone/plone_control_panel,
    and click on the "Zope Management Interface" link (http://localhost:8080/plone/manage_main

  4. Click on ‘portal_skins’ (http://localhost:8080/plone/portal_skins/manage_main), and then click on the ‘Find’ tab at the top (http://localhost:8080/plone/portal_skins/manage_findForm).

    Enter ‘main_template’ in the field labeled "with ids:". Click the ‘Find’ button, and you should see a link to plone_templates/main_template. Click on it.

  5. Click the "Customize" button. This will copy the template into the custom folder, which will override anything that shipped with Plone. This works for other templates as well as other resources (try putting an image called “logo.jpg” in that folder).

The way we alter main_template is the same whether we do the override TTW or override it in our theme product, so I’m going to cover getting main_template into the theme product first.

In this case, we’ll just copy the template from it’s home in parts/plone/CMFPlone/skins.

I found the template by doing a find, and then copied the CMFPlone one.

This will only work after you run bin/buildout :)

$ cd ~/my.theme
$ find . -name main_template*
./eggs/plone.app.kss-1.4.3-py2.4.egg/plone/app/kss/browser/main_template_standalone.pt
./parts/plone/CMFPlone/skins/plone_templates/main_template.pt
./parts/plone/CMFDefault/skins/zpt_generic/main_template.pt
./parts/plone/NuPlone/skins/nuplone_templates/main_template.pt
$ cp ./parts/plone/CMFPlone/skins/plone_templates/main_template.pt ./src/my.theme/my/theme/skins/my_theme_custom_templates/

In either case, getting the viewlet manager to render is accomplished the same way. We simply need to add this line:

    <div tal:replace="structure provider:my.header" />

To the copy of main_template. In my case, I put it just after the opening <body> tag, so my header would be above all the others.

This is a <div> tag that is using the replace TAL attribute to call the viewlet manager that I registered earlier as a content provider. A content provider is an object that produces a chunk of content. This is a TALES expression. I’m specifying the value (my.header) of the name attribute that I used when I registered my viewlet manager.

The structure keyword tells TAL that the output of the expression shouldn’t be escaped; it should be treated as literal HTML.

For more information about TALES syntax, see the page templates reference

Now if you reload the main Plone page, you’ll see the header template output at the top of the screen, similar to this:

The viewlet manager is ALIVE!

The viewlet manager is ALIVE!

Note:I am not happy about this situation. It’s fairly simple to customize main_template, but there is a lot of Plone-specific code in that template right now. I know, it could be worse, but it’s still pretty bad. I worry that keeping up with this template as Plone evolves may prove to be a real PITA. :)

Let There Be Viewlets!

Now that the viewlet manager is up and running, and I can see it in the plone site, there are two steps left before styling the whole shebang. First, I have to associate all of the viewlets we want with the new viewlet manager. Second, I have to call the viewlets inside of the vielwet manager’s template, myheader.pt, which I created earlier and registered in the <browser:viewletManager> tag.

Before we get down to business, I need to identify all of the viewlets that we’ll need for my header.

I won’t go into the details here, but taking a glance at the @@manage-viewlets view (by say, opening http://localhost:8080/plone/@@manage-viewlets) you can figure out the names of the various viewlets that come with plone. That view also lets you show or hide various viewlets. Handy!

Another useful tool for figuring out what viewlets you want (or want to override, for that matter) is GloWorm. It gives you an introspection interface, similar to FireBug, for inspecting and overriding viewlets.

So here’s what I’ll need, viewlet-wise:

  • plone.logo. I want to move the logo over to the right hand column of my header.

  • plone.personal_bar. This viewlet handles the "log in" link.

  • plone.global_sections. This viewlet controls the main navigation tabs across the top of the site. I want to merge them into the same line as the personal bar

  • plone.path_bar. This is the "crumb trail" that lets you know where you are in the site.

  • plone.site_actions. These are the links to the control panel, accessibility information, etc that are usually on the top right hand side of the site. I want to move them to the left hand side.

To connect viewlets to a viewlet manager, you use the <browser:viewlet> ZCML tag. I’ll need a tag for each of the viewlets I want access to in my viewlet manager.

Aside from the name of the viewlets I want, I also need to know what the name of their class is. Luckily, all of the stock Plone viewlets are located in one place. Poke around in eggs/plone.app.layout-x.x.x-py2.4.egg/plone/app/layout/viewlets (the x’s are the version number, which may vary) to see them all, how they’re coded, and how they’re wired up.

src/my.theme/my/theme/browser/configure.zcml

...
<!-- Viewlets registration -->
  <browser:viewletManager
     name="my.header"
     provides=".interfaces.IMyTop"
     class=".viewletmanager.MyHeaderViewletManager"
     layer=".interfaces.IThemeSpecific"
     permission="zope2.View"
     template="myheader.pt"
     />
  
  <browser:viewlet
    name="plone.logo"
    manager=".interfaces.IMyTop"
    class="plone.app.layout.viewlets.common.LogoViewlet"
    permission="zope2.View" 
    />
    
  <browser:viewlet
    name="plone.global_sections"
    manager=".interfaces.IMyTop"
    class="plone.app.layout.viewlets.common.GlobalSectionsViewlet"
    permission="zope2.View" 
    />
 <browser:viewlet
    name="plone.personal_bar"
    manager=".interfaces.IMyTop"
    class="plone.app.layout.viewlets.common.PersonalBarViewlet"
    permission="zope2.View" 
    />
 <browser:viewlet
    name="plone.path_bar"
    manager=".interfaces.IMyTop"
    class="plone.app.layout.viewlets.common.PathBarViewlet"
    permission="zope2.View" 
    />
  
  <browser:viewlet
    name="plone.site_actions"
    manager=".interfaces.IMyTop"
    class="plone.app.layout.viewlets.common.SiteActionsViewlet"
    permission="zope2.View" 
    />
...

In this ZCML, I’m defining new viewlets that utilize the class of an existing plone viewlet. To do this, I specify the manager via the marker interface (IMyTop), and attach a permission to restrict access. I haven’t tried this but according to the docs, you can also specify a template attribute like I did for the viewlet manager to customize the way the viewlet looks without touching the python code. Nice!

Now for rendering the viewlets in my template.

Viewlet managers implement the __getitem__() method of the dictionary api, so in the viewlet manager template you can access a viewlet by the simple TALES expression (for the plone logo) view/plone.logo.

At first, I thought I could use the provider prefix like I did to get the viewlet manager to display in main_template. This doesn’t work. You have to access the viewlet as an object, and call it’s render() method.

But again, things aren’t always as they seem. Calling render() directly causes problems because it’s expected that another method is called first, update(). It took me a while to figure this out, so hopefully this will save some hassle. It doesn’t seem quite right, however. My understanding of the provider concept is that the update() method would be called before the render() method and all would be well. Yet, I get a ComponentLookupError whenever I try to render a viewlet as a provider. Weird.

To get around this problem, I went back to my viewlet manager class and overrode the __getitem__() method, so that when it’s called, the update() method is called on the viewlet before the viewlet object is returned.

src/my.theme/my/theme/browser/viewletmanager.py

from Products.Five.viewlet.manager import ViewletManagerBase

class MyHeaderViewletManager(ViewletManagerBase):
    """
    A custom viewlet manager to re-organize some stock plone viewlets.
    """
    
    def __getitem__(self, name):
        """
        Overriding getitem to call update() on access
        """
        viewlet = super(MyHeaderViewletManager, self).__getitem__(name) 
        
        viewlet.update()
        
        return viewlet

There’s something fishy about having to explicitly call update() and render(). I believe that if I could have gotten to viewlets to work as proper content providers, this wouldn’t be a problem (the. In spite of that, it’s pretty simple and it works, so I’m not complaining (much).

Now that the viewlets are available in a proper manner (and by proper I mean working), I can add them to my header template.

src/my.theme/my/theme/browser/myheader.pt

<div id="my_header">
    <div id="main">
        <div id="left_side">
        <tal:site-actions replace="structure view/plone.site_actions/render" />
        </div>
        <div id="right_side">
        <div tal:replace="structure view/plone.logo/render" />
        </div>
    </div>
    <br />
    <div>
        <div id="personal_bar">
            <tal:global-tabs replace="structure view/plone.personal_bar/render" />
        </div>
        <div id="sections">
            <tal:global-tabs replace="structure view/plone.global_sections/render" />
        </div>
    </div>
    <br />
    <div id="crumbtrail">
        <tal:global-tabs replace="structure view/plone.path_bar/render" /> 
    </div>
</div>

After restarting Zope, here’s what the main page looks like:

Is that double vision? Nope!

Is that double vision? Nope!

Things aren’t terribly different. This is, obviously, because there isn’t any CSS contoling how things are layed out. But because of the way the divs break things up, I’ll be able to put things in their place quite easily.

Once everything is styled, I’ll use GenericSetup to hide the viewlets/managers that are redundant now.

Resources

One of the parts of a theme that really sets it apart from the plone default, regardless of how similar it may be, are it’s resources. These include images, special templates, style sheets, etc.

In these uncertain times, there are different ways that resources are utilized, in the sense of a theme. The traditional "skins directory" still applies. Also at our disposal is the Zope 3 concept of a resource, and a resource directory.

In the first case, ZopeSkel has wired up some convenient places for us to put resources. If you look in the skins directory under your theme product, you’ll see three subdirectories, my_theme_custom_images, my_theme_custom_templates, and my_theme_custom_styles. The purposes of these directories is pretty obvious. You put files relating to the directory name in these directories. Images go into my_theme_custom_images, templates into my_theme_custom_templates (you may recall this is where we put main_template earlier), and CSS style sheets into my_theme_custom_styles.

The primary purpose of the skins directory is to override existing plone resources. When a url is being looked up and acquisition is happening in a plone site, the skins directory is the last place checked. This of course doesn’t limit the skins directory to this purpose. You can put anything there, and acquisition will find it. If you need to use DTML in your CSS, you’ll want to put it here.

The Zope 3 WayTM of handling resources utilizes ZCML registration. You add some tags to your configure.zcml file, and you can access resources via a special URL. This is useful for brand new resources that you aren’t overriding or probably won’t want to override in the future.

I’m going to use both resource approaches to style my theme.

Images

I’ve chopped up the PNG I posted earlier into the necessary bits and pieces for the theme, and I need to put them somewhere. I’ve opted to do this in the Zope 3 style, since these resources are specific to my theme and aren’t likely to ever be overridden. And if they are, this will likely happen via a filesystem product. ZCML resources can be overriden only by more ZCML, so this works out nicely.

Browser resources are established by using a <browser:resourceDirectory> tag. You specify the directory where your images are located, give it a name that’s unique across your Zope instance, and you can pin it down to a specfic layer via a marker interface.

Good news everyone! ZopeSkel has already done this for us.

src/my.theme/my/theme/browser/configure.zcml

...
  <!-- Zope 3 browser resources -->

  <!-- Resource directory for images -->
  <browser:resourceDirectory
      name="my.theme.images"
      directory="images"
      layer=".interfaces.IThemeSpecific"
      />

  <!-- Resource directory for stylesheets -->
  <browser:resourceDirectory
      name="my.theme.stylesheets"
      directory="stylesheets"
      layer=".interfaces.IThemeSpecific"
      />
...

So I just need to put my images into src/my.theme/my/theme/browser/images. Accessing the images gets a little weird, due to the special url convention that Zope 3 uses to locate resources.

To get to one of the images in your images resource directory, you add ++resource++ to any url relative to your plone instance, followed by the name specified in the ZCML above and the name of the image, for example: localhost:8080/plone/++resource++my.theme.images/left_background.png

You’ll notice that it also sets up a resource directory for our style sheets. Nice.

You can also register resources individually, which makes it easier override them individually via ZCML later on (I haven’t done this myself yet, but it’s possible).

CSS and Hiding Viewlets

A Touch Of Style

I’m going to continue using the Zope 3 resource approach with my style sheet. I don’t need DTML, I’m not overriding any specific stock Plone CSS file, and I like keeping all of my browser-related stuff in the browser directory of my product.

Doing things this way requires an additional step. For Plone to incorporate the style sheet(s), they have to be registered with the portal_css tool. This is accomplished via GenericSetup.

First, it’s a good idea to turn on “Debug/development mode” under portal_css in the ZMI of your plone instance (http://localhost:8080/plone/portal_css/manage_cssForm). This will ensure that your css will be refreshed in the browser as it’s updated on disk.

Then you need to create a css file, for example main.css in browser/stylesheets

To get the CSS registered, you need to add a tag to profiles/default/cssregistry.xml

What do you know! A quick glance at the profiles/default directory and we see that ZopeSkel has already done this for us. :). For the sake of illustration, here’s what that file looks like, after ZopeSkel has generated the skeleton:

src/my.theme/my/theme/profiles/default/cssregistry.xml

<?xml version="1.0"?>
<object name="portal_css">
 <stylesheet title=""
    id="++resource++my.theme.stylesheets/main.css"
    media="screen" rel="stylesheet" rendering="import"
    cacheable="True" compression="safe" cookable="True"
    enabled="1" expression=""/>
</object>

You can add additional style sheets in the same manner.

You can verify that the css is active by checking the portal_css tool (it will be second-to-last in the list), and making sure http://localhost:8080/plone/++resource++my.theme.stylesheets/main.css doesn’t throw any errors.

Now that the style sheet is registered, it needs to be populated with styles.

src/my.theme/my/theme/browser/stylesheets/main.css

/* Stylesheet for the 'MY Theme' Plone theme */

#my_header #main {
    height: 90px;
}

#my_header #left_side {
    float: left;
    clear: left;
}

#my_header #right_side {
    float: right;
    clear: right;
}

#my_header #personal_bar_sections {
    clear: both;
}

#my_header #personal_bar {
    float: right;
    clear: right;
}

#my_header #sections {
    float: left;
    clear: left;
}

#my_header #crumbtrail {
    clear: both;
}

I had to add another id to my template, and re-arrange things a bit to get it all to work. Here’s the modified template:

src/my.theme/my/theme/browser/myheader.pt

<div id="my_header">
    <div id="main">
        <div id="right_side">
            <div tal:replace="structure view/plone.logo/render" />
        </div>
        <div id="left_side">
            <div tal:replace="structure view/plone.site_actions/render" />
        </div>
    </div>
    <br />
    <div id="personal_bar_sections">
        <div id="sections">
            <div tal:replace="structure view/plone.global_sections/render" />
        </div>
        <div id="personal_bar">
            <div tal:replace="structure view/plone.personal_bar/render" />
        </div>
    </div>
    <br />
    <div id="crumbtrail">
        <div tal:replace="structure view/plone.path_bar/render" /> 
    </div>
</div>

To explain a bit, what I’ve done is used <div>‘s to separate everything out, and then used float and clear to create the sort of layout I wanted. float moves the block-level elements to one side, and clear controls where float-ing elements are allowed to go. Here’s what it looks like at this point:

We're Almost There!

We're Almost There!

As you can see from the screen shot, I’ve achieved my primary goal of re-arranging the stock plone viewlets into the orientation that I specified in my initial design.

So now, I’ve just got to add in styles to handle the more looky-feely elements (background images, colors, fonts) and override bits of Plone style that either don’t look good in my theme or are counter to it (like the padding around the personal_bar)

Ousting The Doppelgängers

Now’s a good time to get rid of the superflous viewlets that I’ve re-implemented in my viewlet manager. This is accomplished through the <hidden> tag, in our GenericSetup profile’s viewlets.xml file.

src/my.theme/my/theme/profiles/default/viewlets.xml

<?xml version="1.0"?>
<object>
    <hidden manager="plone.portaltop" skinname="My Theme">
     <viewlet name="plone.header" />
     <viewlet name="plone.personal_bar" />
     <viewlet name="plone.path_bar" />
   </hidden>
</object>

The <hidden> tag takes two attributes, manager and skinname. manager is the name of the viewlet manager that you want to hide viewlets in. In this case, it’s plone.portaltop. You can figure out where the viewlets you want to hide are by using GloWorm or @@manage-viewlets.

Inside of the <hidden> tag are <viewlet> tags, each specifying the name of a viewlet you’d like to hide.

You’ll need to re-install your theme product to see the change.

Overriding A Stock Viewlet

I Forgot The Footer! (Overriding Stock Viewlets)

I’ve managed to get all of the styling nailed down. Here’s what it looks like now:

Looking Good.... But wait!

Looking Good.... But wait!

So here’s what’s different: The extra viewlets are gone. I’ve added in all of the background images. I’ve styled the portlets. It’s become obvious that I’m going to have to do some more specific customization of the calendar portlet, but that’s not a big deal right now.

You may have been wondering what I did with the search viewlet that’s normally up in the right hand corner. In this screenshot, you can see I opted to use the search portlet instead… and yeah, that was my plan all along. :)

In addition, I’ve added my own copy of the modern plone logo, with a transparent background. I accomplished this by putting a file called "logo.jpg" into /src/my.theme/my/theme/skins/my_theme_custom_images.

Here’s the CSS as it stands now:

src/my.theme/my/theme/browser/stylesheets/main.css

/* Stylesheet for the 'My Theme' Plone theme */

body {
    background-color: #cfd8e5;
}

#siice_header #main {
    height: 90px;
}

#my_header #left_side {
    float: left;
    clear: left;
    height: 90px;
    width: 50%;
    background-image: url("++resource++my.theme.images/left_background.png");
    background-repeat: no-repeat;
}

#my_header #right_side {
    float: right;
    clear: right;
    height: 90px;
    width: 50%;
    background-repeat: no-repeat;
    background-position: right;
    background-image: url("++resource++my.theme.images/right_background.png");
}

#my_header #personal_bar_sections {
    clear: both;
}

#my_header #personal_bar {
    float: right;
    clear: right;
}

#my_header #sections {
    float: left;
    clear: left;
}

#my_header #crumbtrail {
    clear: both;
}

#my_header #personal_bar_sections {
    background-image: url("++resource++my.theme.images/tabs_background.png");
    height: 20px;
    background-repeat: repeat-x;
    background-color: #b7c4c8;
    border-bottom: 1px solid #93a7ac;
    background-position: top;
}
/************* Override Plone Styles **********/

/* Logo */
#portal-logo {
    float: right;
    padding-right: 5px;
}

/* Site Actions */
#portal-siteactions {
    float: left;
}

#portal-siteactions li a, #portal-siteactions li a:hover, #portal-siteactions li a:active, #portal-siteactions li a:visited {
    border: 0px;
    color: #5f8dd3;
    background-color: transparent;
}

/* Top Sections */
#portal-globalnav {
    padding: 0px;
    font-size: 110%;
}

#portal-globalnav li.selected a {
    color: #000000;
    background-color: transparent;
    border: none;
}

#portal-globalnav li.plain a {
    color: #000000;
    background-color: transparent;
}

/* get rid of the left border for the first tab, the index page */
#portaltab-index_html {
    border: 0px !important;
}

#portal-globalnav li {
    border: 0px;
    border-left: 1px solid #000000;
}
#portal-globalnav li a {
    border: 0px;
    color: #000000;
    background-color: transparent;
    margin: 0px;
}

/* Personal Bar */
#portal-personaltools {
    padding: 0px;
    padding-right: 1em;
    background-color: transparent;
    border: 0px;
}

/* Bread Crumbs Nav */
#portal-breadcrumbs {
    background-color: #b7c4c8;
    border-bottom: none;
}

#portal-breadcrumbs a {
    color: #4b93ff;
}

/* "Green" action tabs */
.contentViews li a {
    background-color: #e3f4d7;
}

/* Portlets */
.portletHeader, .portletFooter, .portletItem, .portlet {
    border: 0px !important;
}

.portletHeader {
    text-transform: none;
    font-size: 110%;
}

.portletHeader, .portletFooter {
    background-color: #b5c3d7 !important;
}

.portletItem {
    background-color: #dfe5ee;
}

.portletHeader a, .portletFooter a, .portletItem a {
    border: 0px;
}

/* Page Body */
.documentContent {
    border: 1px #447821 solid;
}

Considering the amount of CSS that Plone uses, this isn’t that much to override. I’m sure if I was delving deepter into the content area it might be a little more cumbersome, but it looks like the core theme is pretty well built for utilizing CSS to it’s fulllest.

A nice bonus of this layout: it works with large fonts. I haven’t tested it in Internet Explorer or Safari yet, but I can hit ctrl-+ over and over with no ill effects. w00t!

You’ll notice, of course, that I’ve missed the portal footer, the bit just above the colophon at the bottom of the page. I styled it in my mockup but have neglected to give it any attention in my theme product. Now is the time.

I don’t like the idea of just slapping a background on the div around the footer via CSS. I like my styles to be flexible, and if the font size got beyond a certain point, the text would exceed the height of the background. That’s just messy. There’s also the chance that I may need to put some static content inside of the portal footer that I don’t want overriden by any user-configurable options. Examples include a special copywright notice or link to a help center.

Normally, this isn’t a big deal. In my application, however, I may have people with the Manager role running around on the sites that use this theme that aren’t under my jurisdiction.

What this all means is that I need a new viewlet to replace the portal footer viewlet. Luckily, executing such a task is easier than explaining why I need to do it :).

Anyway, overriding a viewlet can be accomplished entirely in ZCML if you want. The same goes for viewlet managers, in theory at least. But, if you need it, you have the option to produce a class that can give you more flexibility.

The Theme Reference has a good overview of what you need to do. Here’s what my <browser:viewlet> tag looks like:

src/my.theme/my/theme/browser/configure.zcml

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

With this configuration, I just had to drop a footer.pt file into my browser directory and bickety-bam, my footer shows up!

Note that I made sure to specify a layer so my override will only be enabled when my theme is in effect.

I took a look at the footer template that comes with Plone to be sure nothing special was happening. No worries there, so in my footer.pt, I was free to code it how I wished. I did borrow the date/time stuff to display the copywright year, however. Clever!

For the sake of illustration, in my examples here I’ve pulled the full copywright from Plone’s storck footer template. Here’s what it looks like:

src/my.theme/my/theme/browser/footer.pt

<div id="my_footer">
    <div id="my_copywright">
        <p>
    <span i18n:translate="description_copyright" tal:omit-tag="">
    The
    <span i18n:name="plonecms" tal:omit-tag="">
        <a href="http://plone.org" i18n:translate="label_plone_cms">Plone<sup>&reg;</sup> CMS &mdash; Open Source Content Management System</a>
    </span>
    is
    <acronym title="Copyright" i18n:name="copyright" i18n:attributes="title title_copyright;">&copy;</acronym>
    2000-<span i18n:name="current_year"
               tal:omit-tag=""
               tal:define="now modules/DateTime/DateTime"
               tal:content="now/year" />
    by the
    <span i18n:name="plonefoundation" tal:omit-tag="">
        <a href="http://plone.org/foundation" i18n:translate="label_plone_foundation">Plone Foundation</a></span>
    et al.
    </span>
</p>

<p>
    <span i18n:translate="description_trademark" tal:omit-tag="">
    Plone<sup>&reg;</sup> and the Plone logo are registered trademarks of the
        <span i18n:name="plonefoundation" tal:omit-tag="">
            <a href="http://plone.org/foundation" i18n:translate="label_plone_foundation">Plone Foundation</a></span>.
    </span>

    <span i18n:translate="description_license" tal:omit-tag="">
    Distributed under the
        <span i18n:name="license" tal:omit-tag="">
            <a href="http://creativecommons.org/licenses/GPL/2.0/" i18n:translate="label_gnu_gpl_licence">GNU GPL license</a></span>.
    </span>
</p>
    </div>
    <div id="my_footer_shadow"></div>
</div>
<br />

Here’s what I added to my existing CSS file to make the footer look like it does in my original design:

src/my.theme/my/theme/browser/stylesheets/main.css

...
#my_footer {
    border-top: 2px solid #a9dd89;
        background-color: #d2dce2;
    background-image: url("++resource++my.theme.images/footer_background.png");
    height: 32px;
    background-repeat: repeat-x;
    background-position: top;
    clear: both;
}

#my_footer #my_copywright {
    text-align: center;
}

#my_footer #my_footer_shadow {
    background-image: url("++resource++my.theme.images/footer_shadow.png");
    border-top: 2px #879ba3 solid;
    height: 10px;
    background-repeat: repeat-x;
    background-position: top;
    clear: both;
}
...

A quick note about my background images

Each of my gradient backgrounds are much larger than they need to be height-wise, and the color of the last row of pixels is the same as the background color of the element they’re in the background of. This helps to ensure that the text can stretch to a severe degree, either in amount or font-size, and the gradient still looks good.

With the new CSS added, here’s what it looks like:

Ah, that's much better

Ah, that's much better

So almost everything is done! All that’s left is some cleanup and testing.

The Final Chapter

I’m Not Done Yet

Now that the theme is finished, there are a few more steps before I can consider it truly complete.

  1. Does it work in IE? Or FireFox, or Safari, or Konqurer or Your Cellphone or a screen reader…. It’s important to test your theme across a few browsers. Chances are if you follow the Plone WayTM, you won’t have too much trouble, but it’s always a good idea. This goes beyond the usual web designer’s perogative. You’ve opted to use a CMS that prides itself in graceful fallbacks, cross-browser compatibility and accessability. Make sure you’re not stomping all over those ends just so you can have a "rad" theme.

  2. Is anything missing? Much like how I missed the footer in my theme, there are other bits and pieces of Plone that your theme may not have taken into account. A good example of this is the calendar portlet. There are some very specific CSS rules that you may have to override to get your theme to take affect.

    It’s a good idea to add all the stock portlets, and install any third-party products you plan to use with your site into your theme product’s development buildout. Poke around the site, and make sure you didn’t miss anything. It may be the product or portlet developer’s fault, but it’s up to you as the theme creator to cover all the bases.

  3. Can you change themes? There is a theme control panel configlet (http://localhost:8080/plone/@@skins-controlpanel) that has a dropdown list allowing you to select the current theme. You should be able to select the "Default Plone" theme without any lingering effects.

    You will however, notice some immediate effects :). Reload the page, restart the server and poke around again like you did earlier just to be sure.

    If you find reminates, they can probably be attributed to forgetting to associate a stylesheet or viewlet customization specifically with your theme.

  4. Does it uninstall? If you do more than add theme-specific viewlets, make sure that nothing breaks when you uninstall your theme. This is especially important if you ever want to distribute your theme to other people, but even if it’s for internal use only, it will make you happier in the long run.

Walking through these steps myself, here’s what I discovered.

It looks funny in IE

A preliminary glance in IE 6 (installed on my Ubuntu Hardy system via IEs4Linux) showed some messy bits under my theme relating to portlets, but the same issues come up with the default Plone theme, so I’m counting this one passable.

I’m in the process of getting a new windows VM up and running so I can test "real" IE6 and IE7 (and probably IE8-beta just for kicks)

I need to dig a bit and see if IE6 compatibility is a priority for Plone, and see if there are any bugs relating to this issue (I’m sure there are, but I should verify)

Odd Portal Rows

I hadn’t noticed it before, but there are special odd/even classes for portlet content like there are for listing tables (e.g. folder contents). I added a bunch of portlets to my layout, and added some events and recent changes. I tried to cover as many bases as I could for the sake of throughness.

After adding all the content, I noticed that the portal footers and headers looked nice, but the content seemed a bit messy, as each item in the events and recent changes portlets would run together. I investigated a bit and found that there was class="portletItem even" and class="portletItem odd" attributes specified in the portlet content.

To account for this, I added the following to my CSS

src/my.theme/my/theme/browser/stylesheets/main.css

...
.portletItem.even {
    background-color: #ecf0f5;
}
...

Uninstallability

In testing the theme, I found out that the theme itself isn’t "deleted" when the product is uninstalled, inspite of the fact that all of it’s required resources and viewlets are no longer available.

And worse, the theme is still set as the default theme. This leaves the site in a more-or-less broken state to the end user, who may not realize they need to change the default theme to a theme that’s actually available still. And the theme stays in the dropdown list.

To fix this requires setting up an uninstall profile. This is documented on plone.org, but I’ll show you what I did in my case.

First, I created a new directory called uninstall in my profiles directory.

$ cd ~/my.theme/src/my/theme/profiles
$ mkdir uninstall

Then I added the XML files that made up the profile. It’s basically what I did in the default profile, but with remove="True" added to the bits I wanted to remove, and the default theme changed back to a sane default.

src/my.theme/my/theme/profiles/uninstall/skins.xml

<?xml version="1.0"?>
<object name="portal_skins" 
   default_skin="Plone Default">

 <object name="my_theme_custom_images" remove="True"/>
 <object name="my_theme_custom_templates" remove="True"/>
 <object name="my_theme_styles" remove="True"/>

 <skin-path name="My Theme" based-on="Plone Default" remove="True">
  <layer name="my_theme_custom_images"
     insert-after="custom" remove="True"/>
  <layer name="my_theme_custom_templates"
     insert-after="my_theme_custom_images" remove="True"/>
  <layer name="my_theme_styles"
     insert-after="my_theme_custom_templates" remove="True"/>
 </skin-path>

</object>

You need just enough information to let GenericSetup find the configuration bits that need to be removed, so you may notice that I removed some of the extra attributes from this skins.xml.

Then, I had to wire up the profile. This is a multi-part process, that starts with registering the profile with ZCML

src/my.theme/my/theme/profiles.zcml

...
  <genericsetup:registerProfile
      name="uninstall"
      title="My Theme - Uninstall"
      directory="profiles/uninstall"
      description='"My Theme" Plone theme. - UNINSTALL'
      provides="Products.GenericSetup.interfaces.EXTENSION"
      />
...

This is esentially the same as the other <genericsetup:registerProfile> tag that ZopeSkel already put into profiles.zcml for you, modified slightly to reflect the location and name of the new profile.

Now I’ve got a brand new GenericSetup profile, but no way to invoke it using the add/remove products interface that my users will be acustomed to. This means I’ll have to apply the uninstall profile with python code.

This is accomplished by bringing back an old-school, but still effective, method of controlling what happens when Plone installs or uninstalls a product: Extensions/install.py

The Extensions directory is not created for us by ZopeSkel, so we need to create it ourselves.

$ cd ~/my.theme/src/my/theme/
$ mkdir Extensions

Then we need to create the install.py file. This file can contain 3 functions: install(), uninstall(), and upgrade(). Each function will only be called if it’s present, and can basically do whatever you want. This is a very straight-forward way to add in some "magic" activity upon install, do some migration upon upgrade, or as in our case, tidy up when the product is being removed. It’s definately the go-to proceedure when GenericSetup doesn’t do what you want, or time prevents you from implementing a GenericSetup-based solution effectively.

src/my.theme/my/theme/Extensions/install.py

from Products.CMFCore.utils import getToolByName

def uninstall(portal):
    setup_tool = getToolByName(portal, 'portal_setup')
    setup_tool.setImportContext('profile-my.theme:uninstall')
    setup_tool.runAllImportSteps()
    return "Ran all uninstall steps."

You can find out more about the setup_tool‘s API by inspecting it with DocFinderTab or looking at the source code (in parts/plone/CMFQuickInstallerTool)

2 Responses to Adventures In Theming – The Complete Saga

  1. Pingback: Adventures In Theming - Followup « jjmojojjmojo: In Effect

  2. Pingback: Weekly post (weekly) « ifPeople Blog

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s