Giving Your Body Tag Some Kick

A while back, my friend Dave Dash shared a Symfony CSS tip with me that I still use in almost every project I work on. It’s incredibly simple, yet powerful.

This tip (for me at least) was born from the need for a simpler way to target classes that might have different appearances depending on what section of a site is being displayed. For example, I might have several modules in my Symfony project that use a class called “container” but which have a different border or background color depending on what module or page they’re being used in. It would be helpful then if it were possible to have a mechanism for defining (or overriding) a specific CSS class attribute depending on the page or module you’re in. It would be even better if it were automatic so that there was no need to add HTML markup to a page just so we could target that attribute differently that in other pages.

enter the body tag

If you’re working with CSS and HTML, chances are, you use already use the body tag to set up some defaults like font-family or color that are used throughout your site. Let’s extend this a bit. The basic concept is simple: give the body tag an id attribute corresponding to each Symfony module. Then we’ll be able to do something like this:

.container { color: blue } /* site-wide declaration */
#news .container { color: red } /* "news" module declaration */

This could very easily be accomplished by printing the module name as an id attribute of the body tag in your main Symfony layout.php file:

<body id="<?php echo $sf_context->getModuleName() ?>">

Which might render something like this:

<body id="news">

With this addition every module will now have a root-level id declaration that you can use in your style-sheet to override styles on a per-module basis. We can even take this a bit further with Symfony by adding a declaration based on the action as well:

<body id="<?php echo $sf_context->getModuleName() ?>" class="<?php echo $sf_context->getModuleName() . '_' . $sf_context->getActionName() ?>">

Now we can use even more specific targeting like so:

.container { color: blue } /* site-wide declaration */
#news .container { color: red } /* "news" module declaration */
#news.news_headlines .container { color: green } /* "news/headlines" module and action declaration */

This all looks great so far… but what if you need to change this for further granularity? For example, what if you had another dynamic attribute that you were passing as part of the URL that you wanted to have specific styles for? As it stands, you’re stuck, but this is where Symfony’s slot mechanism comes to the rescue.

Here’s the custom slot helper we’ll be using:

function text_slot($slot, $default = null)
{
    if (has_slot($slot))
    {
        include_slot($slot);
    }
    else
    {
        echo $default;
    }
}

You can drop this into your own custom helper file in the lib/helper folder. This is simply a wrapper for Symfony’s built-in slot functionality to make it a bit easier to use in this situation. Once that’s done, our body tag can be changed to something like the following:

<body id="<?php text_slot('body_id',$sf_context->getModuleName())?>" class="<?php text_slot('body_class',$sf_context->getModuleName() . '_' . $sf_context->getActionName()) ?>">

This is basically the same functionality as before, with one important difference. Now, by default, we’re putting the contents of our declarations into Symfony slots body_id and body_class respectively. This means that at any time in a module we can override the default body id or class attribute to something more relevant if we need to. For example, in our “news” module and our “headlines” action, we might be using another parameter to differentiate between types of headlines. Let’s call this the “type” parameter. In the “news/headlines” template we’ll redeclare the body_class slot like so:

<?php slot('body_class') ?><?php echo $sf_context->getModuleName() . '_' . $sf_context->getActionName() . '_' . $sf_params->get('type') ?><?php end_slot() ?>

… which would render something like this if our type parameter was set to “local”:

<body id="news" class="news_headlines_local">

As you can see, you can extend this little trick into many different use-cases. It’s especially useful for navigations where you need to know what section you’re in so you can change the navigation appropriately. With this trick you can let the CSS worry about doing the heavy lifting without having to add logic to your templates. It’s powerful, light-weight and automatic.


About this entry