Picture
Farsheed Hamidi-Toosi Senior Drupal Developer
January 22, 2010

Chapter Three is in love with Panels. A lot of people are, and with good reason. It has the power to eliminate the need for theme developers, especially with the development of Panels Everywhere.

I have recently returned to Drupal after a long hiatus and while a lot remains familiar, Panels is one of those modules that has grown into a wild, hairy beast. I can't really tell you what variants or contexts are used for, and I'm not quite sure why I have to save twice when updating anything on a panel page. There is scarce documentation about using plugins, and I wouldn't necessarily describe creating a plugin as "fun". But it's a learning process, eh?

So I'll share with you some of the best practices or problems I've encountered, and then maybe Merlin of Chaos will magically appear and comment that there is a far simpler way of doing it. Or maybe some of you have different approaches that you'd like to share or can find faults with my approach?

**EDIT** I highly recommend reading all the comments attached to this post, Merlin of Chaos did appear and explained a few things, particularly how to specify an alternate admin theme and css without doing my hack.

In this blog post I'll discuss how to make your theme aware of panels, as well as how to create a custom panel layout plugin. Read the full post here.

Letting your theme know about Panels

It's kind of a weird concept to define regions in a theme, and then to have Panels define even more regions inside. Usually this doesn't cause many problems, as long as the rest of the theme doesn't care if panels is rendered inside of $content in the theme. However if you want to tell the rest of the theme when you are viewing a panel page, you can set a variable in template.php.

In Panels 3: set a flag variable telling you if panels has taken over.

/**
  * Override or insert PHPTemplate variables into the templates.
  */

function yourtheme_preprocess_page(&$vars) {
 
// Determine if the current page is a panels page
 
$vars['is_panels'] = panels_get_current_page_display() ? TRUE : FALSE;
}

?>

 

This lets you do all sorts of things based on , switch templates, hide theme regions, and allows your theme to "know" if panels is in the house.

Creating custom panels layouts

The flexible panels layout option is pretty useful (in theory) but I tend to dislike it. I don't like the idea of storing layout/theme information in the database, and because I like having a more hands on approach to creating custom layouts. Plus having a standalone layout means it is more portable and you can copy it to a new site quickly.

To create a custom panels layout, you'll need to add the following to a custom module (I'm assuming you know how to create your own module already):

Panels 3: Tell panels where to look for new custom plugins

/**
* Implementation of hook_ctools_plugin_directory()
*/

function yourmodule_panels_ctools_plugin_directory($module, $plugin) {
  return
'plugins/' . $plugin;
}

?>

 

This tells panels to look inside of sites/all/modules/yourmodule/plugins/ directory for any new panels plugins (plugin meaning new layouts, style plugins, or content types).

So to create a new layout, you create a new directory layouts inside of plugins and then you can put your new layout inside of that:

/sites/all/modules/yourmodule/plugins/layouts/super_bricks

Super_bricks is the name of the layout we are creating. We'll need to create 4 files inside of the super_bricks directory:

/sites/all/modules/yourmodule/plugins/layouts/super_bricks/super_bricks.inc
/sites/all/modules/yourmodule/plugins/layouts/super_bricks/yourmodule-panels-super-bricks.tpl.php
/sites/all/modules/yourmodule/plugins/layouts/super_bricks/super_bricks.css
/sites/all/modules/yourmodule/plugins/layouts/super_bricks/super_bricks.png

super_bricks.inc defines regions and defines paths for all the other files;
super_bricks.tpl.php is the template used for your layout;
super_bricks.css is loaded when your template is loaded;
super_bricks.png is an icon to show when choosing the layout.

For super_bricks.png I usually just copy and modify an icon from one of the existing panels layouts.

Panels 3: super_bricks.png

Here is what you need inside of super_bricks.inc:

Panels 3: super_bricks.inc

/**
* Implementation of hook_panels_layouts().
*/

function yourmodule_panels_super_bricks_panels_layouts() {
 
$items['super_bricks'] = array(
   
'title' => t('yourmodule: super bricks'),
   
'icon' => 'super_bricks.png',
   
'theme' => 'yourmodule_panels_super_bricks',
   
'css' => 'super_bricks.css',
   
'panels' => array(
     
'region1' => t('Region 1'),
     
'region2' => t('Region 2'),
     
'region3' => t('Region 3'),    
    ),
  );
  return
$items;
}

?>

 

And now in your tpl.php file you'll have the regions $content['region1'], $content['region2'], and $content['region3'] available, as well as an optional css id variable.

Panels 3: yourmodule-panels-super-bricks.tpl.php

/**
* @file
*    Super Bricks Layout
*  
*   Variables:
* - $id: An optional CSS id to use for the layout.
* - $content: An array of content, each item in the array is keyed to one
*   panel of the layout. This layout supports the following sections:
*   - $content['region1']: Content in the top row.
*   - $content['region2']: Content in the left column in row 2.
*   - $content['region3']: Content in the right column in row 2.
*/

?>

// Note the conditional loading of the super-bricks class when NOT on an admin page. ?>
if (!empty($css_id)) { print "id=\"$css_id\""; } ?>>
 
print $content['region1'] ?>

 
print $content['region2'] ?>

 
print $content['region3'] ?>

 

In the super_bricks.css file, I define very generic styles for the layout:

Panels 3: super_bricks.css

/* Use super-bricks-base class for generic, admin only styles */
.super-bricks-base .region1 {
  width:49%;
  float:left;
}
.super-bricks-base .region2 {
  width:49%;
  float: left;
}
.super-bricks-base .region3 {
  clear:both;
}

/* Use the .super-bricks class for actual panel styles*/
.super-bricks .region1 {
  //highly customized styles, fixed width, background images, etc
}
.super-bricks .region2 {
  //highly customized styles, fixed width, background images, etc
}
.super-bricks .region3 {
  //highly customized styles, fixed width, background images, etc
}

 

Finally, to get Panels to recognize the new layout, you'll have to clear the drupal cache at: admin/settings/performance

What's going on here? Why are there two classes in the tpl.php file?

So you may notice I used two classes in the tpl.php, super-bricks-base and super-bricks. Here is why: when you style a panels page, you often make assumptions about the way it will look like with content inside of it. You may apply background images to the regions and set fixed widths. Unfortunately when a user goes to edit content in the panels backend, it will look like a mess. For usability sake, you want the admin backend to look like a generic version of the frontend so that a user understands where something goes.

So the .super-bricks-base class represents this generic, percent width version of the layout. Then only if we are NOT looking at the panels admin page do we load in the regular .super-bricks class. This way you can go NUTS styling under .super-bricks and not worry about breaking the admin side of things. It also means you can define these specific styles in your style.css in your theme or wherever else. I think this is a good way of separating out the base styles of a layout and customizations of that layout. It's much easier than trying to undo admin styles with #panel-content-edit.

Building Panel layouts from HTML cutups

In an ideal world (for me at least), a designer will create mockups in Fireworks, Photoshop or similar and then after everything is finalized, those mockups will then be converted into static HTML. The static HTML is browser tested and when ready, converted into a template tpl.php file by adding in regions and template logic. The speed and quality of the HTML -> Drupal conversion usually depends on how familiar the person who created the HTML is with Drupal and the concept of separating content from layout.

A really good practice is hiring someone like Squiggy. She does an amazing job of creating perfect, static HTML that is Drupal Ready (tm). :D

In the case of creating a layout tpl.php file, sometimes it can be hard to separate out what is a layout region and what is content. For instance, in the design there may be a floating block that also has some crazy background style. You have to decide is that background style unique to the block, or to the region that its in? It's not always obvious. For this reason when creating the static HTML out of designer mockups it is a good idea to use the class .panel-pane as the boundary between content and layout (whenever possible). To take it one step further, you may want to put some dummy Lorem Ipsum content in there. The point is that a robust panels layout should be able to handle most common sense content that gets placed inside. Then all you have to do is remove the .panel-pane class and replace with a print statement.

Panels 3: Plan for what will be injected into your $content['region']


 

   
   
   

     

Title of content #1


     
Lorem ipsum phonius latinus

   

   
 

   

     

Another piece of content #2


     
Yet another piece of dummy Lorem ipsum phonius latinus

   

   
 

 

becomes:

Panels 3: Substitute a print statement for the dummy static output

// This is the tpl.php file ?>
if (!empty($css_id)) { print "id=\"$css_id\""; } ?>>
 

    print $content['region1'] ?>
 

 

Alright, this seems like a good stopping point. Like I said, creating a plugin isn't exactly "fun". It's challenging to navigate through panels as well as managing the nuts and bolts of the Mockup -> HTML -> Template conversion.

Review: Creating a Panel Layout

  1. Create static HTML first from mockups, but plan ahead for panel panes
  2. Replace dummy content with print statements to create your tpl.php files
  3. Use 2 separate panel layout classes, so that you can keep the panels backend organized
  4. Set an $is_panels flag variable in template.php so you can make theme adjustments

Confused? Did I make a mistake somewhere? Have an easier way of doing something? We're all in this together so leave a comment below.