Wednesday, July 7, 2010

Adding CSS that uses settings from content

Use case



A client wants to display a section of their site in a slightly different style. This different style currently consists of adding a background image, which should be changed by content editors who have no technical knowledge of CSS, and who we don't want to bother with things like the ZMI.

Approach



The object at the top of the section is a custom content type. We can add a background_image attribute to that, so content editors can change it. But can we get that into a CSS file?

Attempt 1: a skin template CSS file



First off, i tried to add a skin template to generate the CSS. This worked beautifully when calling section_object/my_skin_template.css. Unfortunately, CSS that is included by Plone's portal_css tool has the portal root as context, so my lookup of the background_image attribute never found anything.

Solution: A CSS viewlet



Adding a viewlet to the plone.htmlhead.links viewlet manager solved my problem.

browser/viewlets.zcml:

<browser:viewlet
name="myproduct.sectioncss"
manager="plone.app.layout.viewlets.interfaces.IHtmlHeadLinks"
class=".viewlets.SectionCSS"
layer=".interfaces.IMyProduct"
permission="zope2.View"
/>


This will render the viewlet in the <head> tag, so we can generate an internal style sheet here.

browser/viewlets.py:

class SectionCSS(ViewletBase):

def getBackgroundImageUrl(self):
return_value = None
try:
bg_image = self.context.getBackground_image()
return_value = bg_image.absolute_url()
except:
pass
return return_value

def render(self):
return_string = ''
image_url = self.getBackgroundImageUrl()
if image_url:
return_string = "<style type='text/css'>body { background-image: url('%s'); } </style>" % image_url
return return_string




What's next



There's much room for improvement here: The try/except will have to go, for example. I'm also curious what other people have done to solve this problem.

5 comments:

David said...

As the saying goes, there's many ways to skin a cat (or I guess in your case, a background image). I've done things similar to this in several different ways -- depending on how quick/dirty I wanted the solution.

One way of allowing the background image to be changed would be providing the given images as actual Plone content. This avoids the necessity of custom fields, and allows the image to be changed at will by someone non-technical. With the right page template or viewlet calls, you could check for the image content's existence and render it into your CSS. Hack-y? Perhaps.

Another (more elegant) solution I've just been playing around with was using Zope annotations to record theme settings on content. Essentially, I just use a z3c form front end to store theme options as Annotations for a given context. Then, my given viewlet renders said options onto the page, searching parent content for options to be inherited.

Whichever way you go, if you're storing settings, you might want to consider Annotations rather than going for a custom content type.

juh said...

Couldn't you use the automatically generated section-class?

Given you have a folder called "2010" in the root folder of a Plone website, Plone creates this:

class="section-2010 template-folder_listing"
dir="ltr"

in the body-tag

dukebody said...

Just specify a relative path for the background image of your CSS file:

#some-selector {
background-image: url(./method_returning_image);
}

Australia Web Development said...

Have you tried the simpler version?

Ex:
body
{
background-image:url('paper.gif');
}

Kees Hink said...

@Australia Web Development:

That would require content editors to upload an image with exactly that name, which is not user-friendly enough.

Also, if you add this CSS everywhere, you get 404's each time the image isn't found (practically everywhere).

@dukebody:

Your method seems more plausible, at least if the method returning the image is a script that is available everywhere and returns a "none" string when the background image isn't found.

@juh:

Your suggestion might be handy in combination with dukebody's, to prevent overriding any other background that was specified outside of that particular section.

@David:

The Zope annotations looks interesting, do you have any further documentation about that?