I generally would style individual page elements like menus, blocks, views, and other content by using their own class names or IDs.That would mean if I wanted a consistent style to be applied to many of these elements I would have to override template files just to add a consistent class attribute, or have multi-line selectors in my css which would make it incredibly difficult to organize. Then one day I got smart and started to use Panels. Without getting into much detail, I’d have to say that using Panels to create my own custom layouts and plugins has changed my game as a Drupal themer.

But I’m already a proficient Drupal themer, I don’t need panels to do my layouts...

Perhaps, but you reap huge benefits from developing and designing Panel layouts and style plugins instead of block and page template files. For example:

  • An intuitive administration page - The block administration page has a single column to represent your layout; not exactly user-friendly.
  • Reusable style plugins for both panes and entire regions - sure you can use classes to style your blocks, but that requires extra work in your code when you need to change skins quickly.. Not to mention you can accidentally break things.
  • Apply the same piece of content in multiple regions - You can’t do this out of the box with Drupal, however, the Multiblock module helps with that, but I found that to be a little extra maintenance on my part.
  • Creating Panel layouts are actually easy - Just as much as page.tpl.php, but you get more bang for your buck.
  • More control over visibility settings - Panels goes way beyond Drupal’s simple “show all, or show none” approach (see block visibility). Panels has built-in context rules, as well as hooks to define your own contexts.
  • More control over caching - Again, Panels provides hooks to create your own caching plugins. Might not be used too often, but it’s there when you need it.
  • The list literally goes on and on and on...

So are you sold yet?

Overview of the Panels API for Style Plugins

Developing a Panels style plugin isn’t particularly complicated, but does require you to have familiarity with some of the tools available. Here are the essentials:

HOOK_STYLENAME_panels_styles()

This returns an array of the following keys:

  • title - displays the name of your style to the UI
  • description - displays a description above your settings form (if applicable)
  • render pane - callback to render a pane (implemented as theme_YOURCALLBACK)
  • render panel - callback to render a panel region (implemented as theme_YOURCALLBACK)
  • settings form - callback to function that returns a form definition
  • settings validation - same as FAPI form_validate
  • hook theme - same details as hook_theme

theme_style_render_pane( $content, $pane, $display )

This is your render pane callback (the “style_render_pane” text can be whatever you want, so long it matches the value of “render pane” key in HOOK_STYLENAME_panels_styles)

  • $content object - contains information about what type of content that particular pane is displaying (node, menu, block, custom, etc.) title, content, so on...
  • $pane object - contains all the properties you can think of for a pane: pane id, panel display id, which region it’s rendered in, the style plugin attached to it, user configured css ID and classes, position in region, even data about the content that it contains ($content).
  • $display panels_display object - contains a HEAP of data about the panel itself: arguments passed to it, it’s regions, it’s layout style, cache settings, and even the data about each the panes that it contains ($pane).

theme_style_render_panel( $display, $panel_id, $panes, $settings )

This is your render panel callback (“style_render_pane” can be called whatever you like, so long it matches the value of “render panel” key in HOOK_STYLENAME_panels_styles)

  • $display panels_display object - same as above
  • $panel_id int - self explanatory..
  • $panes array - an array of all pane objects that are rendered on the display
  • $settings array - The results of the settings form for each panel

Creating a demo plugin

Hover Demo
click here to download the demo and test out on your own site.

In a future post I’d like to show exactly how I’m using Panel style plugins to demonstrate some powerful tools you can create in your theme, but for now I’ll do something really simple to help get people familiar with what you can do.
I generally like to put panel layouts and plugins in my theme opposed to in a module. So first thing we’ll do is tell Panels that we have a custom plugin in our theme. In this case, our theme name is called ‘mytheme’, so we’ll add this line to our mytheme.info file:
(you can drop this into your already existing theme, just replace ‘mytheme’ with whatever you theme is called.)

mytheme.info

<?php
plugins
[panels][styles] =plugins/styles
?>

Now we will create a folder named demo with a file named demo.inc in the plugins/styles directory in our theme. Optionally you can create the file without putting it in a folder, I just do it to help me organize any additional files I may include with my plugin.

Let’s create our initial hook to set up the plugin, we’ll be referencing HOOK_STYLENAME_panels_styles() :

demo.inc


<?php
/**
* Implementation of hook_panels_styles().
*/
functionmytheme_demo_panels_styles() {
return array(
'demo'=> array(
'title'=>t('Demo'),
'description'=>t('my custom Panel style plugin'),
'render panel'=>'demo_style_panel',
'render pane'=>'demo_style_pane',
'settings form'=>'demo_form',
'hook theme'=> array(
'demo'=> array(
'template'=>'demo_template',
'path'=>drupal_get_path('theme','mytheme') .'/plugins/styles/demo',
'arguments'=> array(
'content'=>NULL,
),
),
),
),
);
}
?>

So in our hook implementation we are stating we created theme_demo_style_panel for the the panel render, theme_demo_style_pane for the pane render, a function named demo_form that will return our settings form definition, and we are registering a template to the theme registry in which we can call theme(‘demo’, $content) that will render markup using demo_template.tpl.php.

Flush your cache and go create a panel and add some content in panes. Click on the cog (either on the top left if it’s a region, or the top right if it’s a pane) and choose a new “Style”. At this point you should be able to see your plugin listed, Woo-hoo! However, you’ll get an error if you try using it... First, we have to create our style callbacks!

demo.inc

<?php
functiontheme_demo_style_panel($display,$panel_id,$panes,$settings){
$content= newstdClass;
$content->settings= array();
$content->settings['position'] =$settings['flare_position'] ?$settings['flare_position'] :'left-top';
foreach(
$panesas$pane_id=>$data) {
$content->content.=panels_render_pane($data,$display->content[$pane_id],$display);
}
return
theme('demo',$content);
}
?>

All we’re doing here is using an object to collect all the output of the panes this panel region contains. We are doing this in a foreach loop using the panels_render_pane() function. It is important that we do this, otherwise our panel will be missing content. There’s also a mention of a settings array, ignore that for now, we’ll use it later when we implement a settings form. Now you can use your region style without errors, however, there isn’t anything exciting about returning the raw content.. Let’s move into actually using a template. Create a file named demo_template.tpl.php in your demo folder.

demo_template.tpl.php


<?php drupal_add_css(drupal_get_path('theme','mytheme') .'/plugins/styles/demo/demo.css','theme');?>

&lt;div class="demo"&gt;
&lt;div class="demo-inner"&gt;
&lt;div class="demo-inner-deep"&gt;
<?phpprint$content->content;?>
      <?phpif ($content->settings['position']):?>
      &lt;div class="flare flare-<?phpprint$content->settings['position'];?>"&gt;&lt;/div&gt;
<?phpendif;?>
    &lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
?>

Here I’m using drupal_add_css() to retrieve the css file we need to style our template. This is all pretty typical stuff if you’re familiar with template files. Oh, one more thing, create a file named demo.css. Here’s what that looks like:

demo.css

.demo,
.demo-inner,
.demo-inner-deep{
  background:url(demo.png) no-repeat;
}
.demo{
  position:relative;
  width:373px;
  background-position:0 0;
  padding-top:25px;
  color:#fff;
  font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;
  font-size:1.2em;
  margin:12px;
}
.demo-inner{
  background-position: right bottom;
  padding-bottom:25px;
}
.demo-inner-deep{
  background-position:-373px 0;
  background-repeat:repeat-y;
  padding:15px 35px;
}
.demo .flare{
  background:url(flare.png) no-repeat;
  width:50px;
  height:62px;
  position:absolute;
}
.demo .flare-left-top{
  top:-10px;
  left:0;
}
.demo .flare-right-top{
  top:-10px;
  right:0;
}
.demo .flare-left-bottom{
  bottom:-10px;
  left:0;
}
.demo .flare-right-bottom{
  bottom:-10px;
  right:0;
}
/************ Demo Pane Styles *****************/
.demo-pane{
  font-family: "Zapfino", "westminster", "webdings";
  color:#bada55;
  line-height:40px;
}

At this point you can apply the style to the Panel (not the pane, we’ll do that next), test the page and see what it looks like. If you’re getting errors make sure you clear your cache. If you errors indicate it cannot find your template, you can explicitly assign a ‘path’ in your theme hook:
<?php'path'=>drupal_get_path('theme','mytheme') .'/plugins/styles/demo'?>

Okay, so let me explain what the settings array is going to do. I want to include an image on the corner of my container to give it a little flare. However, I’d like to be able to configure which corner I want to use each time I use this style. So next we will implement a settings form that we can configure in case we decide that we can’t commit to a particular corner.

demo.inc

<?php
functiondemo_form($style_settings){
$form= array();
$form['flare_position'] = array(
'#type'=>'radios',
'#title'=>'Choose the position of your flare.',
'#default_value'=> (isset($style_settings['flare_position'])) ?$style_settings['flare_position'] :'left-top',
'#options'=> array(
'left-top'=>t('Top Left'),
'right-top'=>t('Top Right'),
'left-bottom'=>t('Bottom Left'),
'right-bottom'=>t('Bottom Right'),
),
);
return
$form;
}
?>

Pretty standard Form API stuff. But here is the fun part: You can go back to the panel configuration page and choose “style settings” from the panel cog. Once a user submits this form, we will have access to all of their configurations in the panel style callback using the $settings parameter! That explains the $content->settings array in the panel style callback.

The last thing we want to play with is creating our pane callback. Just for the sake of demonstration we’re going to do this one really, really simple.

demo.inc

<?php
functiontheme_demo_style_pane($content,$pane,$display){
$output='';
$output.='<div class="demo-pane">';
$output.=$content->content;
$output.='</div>';
return
$output;
}
?>

Go ahead and configure the pane with this style in the admin page. All this is doing is wrapping a class around the pane content for styling purposes. Of course, you can almost do everything a panel callback is capable of (with the exception of including a settings form, as far as I can tell.)

So there you have it. A typical style plugin in which you can build from. I’d like to note here that I read the plugins code that come with the Panels module as my documentation, so I want to give thanks to the author, Earl Miles, for having that in there.

In my next post on Panels I’d like to talk about the data structure of the objects that are being passed into the style callbacks and map out the important data you can mine out for your templates.