Posted Dec 05, 2008
Theme switching by URL, folder, page, etc.
When we want to use two or more themes in the same Plone 3.1 site, we need some way of switching between the themes. Mobilize currently uses theme switching by URL. This approach can be modified to offer different themes in different folders (subskinning, anyone?).
How this came about...
Here at WebLion, and in the wider Plone community, there has been a big demand for sectioned theming, subskinning, and URL-based theme switching. I have heard through the grapevine that this ability will be added in later versions of Plone. However, many people are asking for this ability right now. I believe I have come up with a somewhat unorthodox solution for the time being.
It should be noted that it was not my original purpose to create a theme switching product. I started my WebLion internship working on a revamp of Mobilize, when I realized Mobilize's theme switching ability could be harnessed for wider ends. That realization has now turned into a product that will switch skins based on URL, folder, etc.
The Overall Premise
Theme switching isn't exactly a new idea, in fact my code is based off Tomster's URL-dependent skinswitching. However, Tomster's approach runs into problems when two or more themes are installed simultaneously, because one theme usually distorts the other... either from style issues or from viewlet moves and hides.
For instance, when Nautica05 Theme, Green Community and Santaplex Theme are all installed in one Plone site, we end up with "viewlet bleed" (see the image). Viewlets from one theme leak into another.
Why does this happen? Simply put, every theme attaches itself to the request. So to correct this condition we need to remove the non-default theme from the request.
Tada, successful theme switching without fragments from the other themes.
The Code Explained
Checkout themetweaker.themeswitcher to see the full code.
Grab the request!
The key to theme switching is modifying the request prior to rendering a response. We do this by tapping into the "Before Traversal" event (IBeforeTraverseEvent). Plus we would like to make the event local to the site the themeswitcher is installed on. To do this, we provide a marker interface on the portal root (IThemeSwitcher). We can now change the skin with the built in 'changeSkin' method from within our event handler.
The Problem
The method 'changeSkin' sets the current skin for the given request. This does not remove the other skins from the request, instead simply pushes them down in the layering. This is how fragments from the other themes bleed into the default theme. What is supposed to prevent this? The 'layer' attribute we religiously define in zcml with the IThemeSpecific marker interface.
If we remove the rogue's marker interface (IThemeSpecific) from the request we should be able to prevent the rogue's browser resources from loading. However, as it stands, the IThemeSpecific interface is not directly applied to the request and therefore can not be removed.
As luck would have it, I chanced across a lovely Plone document entitled Making Components Theme Specific. Following the instructions of this document, we define the browser layer using GenericSetup XML (browserlayer.xml), rather than via zcml. I am not a fan of GenericSetup but in this case I will gladly use it because it works as I believe browser layers should work. So what does browserlayer.xml do that the zcml definition does not? It directly provides IThemeSpecific to the request. As a result, we can remove the rogue's marker interface from the request. Fragments be gone!
Problems Solved, Kinda
There is still a problem, however, because theme marker interfaces are not discoverable (if you know of a way to make them discoverable, please let me know!). I would preferably like to discover the marker interface of the theme by its theme name (or vice versa).
So at the moment, I must look up each theme marker interface and manually include it my theme switcher's event handler code. <sigh> This isn't ideal, but it works (and that is what is important!). And hopefully, we can find a way to make marker theme interfaces discovered automatically.
What Now?
Discoverable Themes
The collective.sectionsubskin package by Matthew Wilkes of Team Rubber makes its sectionsubskin products discoverable, because anything that uses it is a utility look up away.
I did not take the sectionsubskin approach because I wanted to use existing theme technologies without changing them to my specific implementation. In other words, I would like to use any theme without modifying it.
Let's find a way to make themes discoverable: something that can look up a theme by name and see what interfaces it provides (and vice versa). There may be a way to do this currently (IBrowserSkinType?). I'll probably lose sleep over this, unless someone knows of a solution.
Room For Improvement
For my development purposes on the Mobilize product, switching by URL does the trick. However, this approach can be modified to enable you to switch by things other than URL (use your imagination!). Something interesting to think about might be switching by day of the week (maybe on December 25th we should switch all plone sites to Santaplex Theme. ;).
Caching
I came across one caching problem so far, the logo. I am sure there are other problems though. In my case, I am fine because I am switching the theme for the entire site by URL from behind a caching service. But if you wanted different themes in different folders and subfolders you may run into problems.
A Theme or Not a Theme, That is the Question
What if I have a product that is not a theme? For instance, a product that only adds a viewlet to the page is not a theme but is treated as one because it uses the same browser-layer-defining syntax as a theme. This in my opinion is a greater problem with how we define themes versus components in themes.

Browser Resources
But I'm getting an odd result with the resource directories in the browser package. Here's a simple test I did, maybe you have a tip as to how to work with the resource directories.
1) ++resource++theme.main.images/some-image.jpg is globally available, no matter what theme (plone default, sub, main...).
2) ++resource++theme.sub.images/sub-skin-image.jpg will only work when the theme.sub is the default, site-wide theme.
The same pattern holds for all resource directories (registered via .browser package configure.zcml).
It could be that all my testing/installing/reinstalling etc... has corrupted things to some odd state, but perhaps you know of something to get the browser resources included?
I see that your santaplex theme doesn't really use the resource directories the way I currently understand them to be used, but I don't think I'm alone in being confused about where to put what parts when creating a plone theme. Any thoughts?
Thank you again for creating this, it seems to be exactly what I want and I see it working. I realize I could rework my code to use it effectively, but I think I'd like to use the resource directories.
mark (at) taocode (dot) com