My Life with Panels, Vol. 1

18

January 22, 2010 - 11:07am

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.

<span style="color: #000000"><span style="color: #0000BB"><?php<br /></span><span style="color: #FF8000">/**<br />  * Override or insert PHPTemplate variables into the templates.<br />  */<br /></span><span style="color: #007700">function </span><span style="color: #0000BB">yourtheme_preprocess_page</span><span style="color: #007700">(&</span><span style="color: #0000BB">$vars</span><span style="color: #007700">) {<br />  </span><span style="color: #FF8000">// Determine if the current page is a panels page<br />  </span><span style="color: #0000BB">$vars</span><span style="color: #007700">[</span><span style="color: #DD0000">'is_panels'</span><span style="color: #007700">] = </span><span style="color: #0000BB">panels_get_current_page_display</span><span style="color: #007700">() ? </span><span style="color: #0000BB">TRUE </span><span style="color: #007700">: </span><span style="color: #0000BB">FALSE</span><span style="color: #007700">;<br />}<br /></span><span style="color: #0000BB">?></span></span>

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

<span style="color: #000000"><span style="color: #0000BB"><?php<br /></span><span style="color: #FF8000">/**<br /> * Implementation of hook_ctools_plugin_directory()<br /> */<br /></span><span style="color: #007700">function </span><span style="color: #0000BB">yourmodule_panels_ctools_plugin_directory</span><span style="color: #007700">(</span><span style="color: #0000BB">$module</span><span style="color: #007700">, </span><span style="color: #0000BB">$plugin</span><span style="color: #007700">) {<br />  return </span><span style="color: #DD0000">'plugins/' </span><span style="color: #007700">. </span><span style="color: #0000BB">$plugin</span><span style="color: #007700">;<br />}<br /></span><span style="color: #0000BB">?></span></span>

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

<span style="color: #000000"><span style="color: #0000BB"><?php<br /></span><span style="color: #FF8000">/**<br /> * Implementation of hook_panels_layouts().<br /> */<br /></span><span style="color: #007700">function </span><span style="color: #0000BB">yourmodule_panels_super_bricks_panels_layouts</span><span style="color: #007700">() {<br />  </span><span style="color: #0000BB">$items</span><span style="color: #007700">[</span><span style="color: #DD0000">'super_bricks'</span><span style="color: #007700">] = array(<br />    </span><span style="color: #DD0000">'title' </span><span style="color: #007700">=> </span><span style="color: #0000BB">t</span><span style="color: #007700">(</span><span style="color: #DD0000">'yourmodule: super bricks'</span><span style="color: #007700">),<br />    </span><span style="color: #DD0000">'icon' </span><span style="color: #007700">=> </span><span style="color: #DD0000">'super_bricks.png'</span><span style="color: #007700">,<br />    </span><span style="color: #DD0000">'theme' </span><span style="color: #007700">=> </span><span style="color: #DD0000">'yourmodule_panels_super_bricks'</span><span style="color: #007700">,<br />    </span><span style="color: #DD0000">'css' </span><span style="color: #007700">=> </span><span style="color: #DD0000">'super_bricks.css'</span><span style="color: #007700">,<br />    </span><span style="color: #DD0000">'panels' </span><span style="color: #007700">=> array(<br />      </span><span style="color: #DD0000">'region1' </span><span style="color: #007700">=> </span><span style="color: #0000BB">t</span><span style="color: #007700">(</span><span style="color: #DD0000">'Region 1'</span><span style="color: #007700">),<br />      </span><span style="color: #DD0000">'region2' </span><span style="color: #007700">=> </span><span style="color: #0000BB">t</span><span style="color: #007700">(</span><span style="color: #DD0000">'Region 2'</span><span style="color: #007700">),<br />      </span><span style="color: #DD0000">'region3' </span><span style="color: #007700">=> </span><span style="color: #0000BB">t</span><span style="color: #007700">(</span><span style="color: #DD0000">'Region 3'</span><span style="color: #007700">),     <br />    ),<br />  );<br />  return </span><span style="color: #0000BB">$items</span><span style="color: #007700">;<br />}<br /></span><span style="color: #0000BB">?></span></span>

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

<span style="color: #000000"><span style="color: #0000BB"><?php<br /></span><span style="color: #FF8000">/**<br /> * @file<br /> *    Super Bricks Layout<br /> *   <br /> *   Variables:<br /> * - $id: An optional CSS id to use for the layout.<br /> * - $content: An array of content, each item in the array is keyed to one<br /> *   panel of the layout. This layout supports the following sections:<br /> *   - $content['region1']: Content in the top row.<br /> *   - $content['region2']: Content in the left column in row 2.<br /> *   - $content['region3']: Content in the right column in row 2.<br /> */<br /></span><span style="color: #0000BB">?></span></span><br /><span style="color: #000000"><span style="color: #0000BB"><?php </span><span style="color: #FF8000">// Note the conditional loading of the super-bricks class when NOT on an admin page. </span><span style="color: #0000BB">?></span></span><br /><div class="panel-display super-bricks-base  <span style="color: #000000"><span style="color: #0000BB"><?php </span><span style="color: #007700">if (</span><span style="color: #0000BB">arg</span><span style="color: #007700">(</span><span style="color: #0000BB">0</span><span style="color: #007700">) != </span><span style="color: #DD0000">'admin'</span><span style="color: #007700">) { print </span><span style="color: #DD0000">'super-bricks'</span><span style="color: #007700">; } </span><span style="color: #0000BB">?></span></span> clear-block" <span style="color: #000000"><span style="color: #0000BB"><?php </span><span style="color: #007700">if (!empty(</span><span style="color: #0000BB">$css_id</span><span style="color: #007700">)) { print </span><span style="color: #DD0000">"id=\"$css_id\""</span><span style="color: #007700">; } </span><span style="color: #0000BB">?></span></span>><br />  <div class="region1"><span style="color: #000000"><span style="color: #0000BB"><?php </span><span style="color: #007700">print </span><span style="color: #0000BB">$content</span><span style="color: #007700">[</span><span style="color: #DD0000">'region1'</span><span style="color: #007700">] </span><span style="color: #0000BB">?></span></span></div><br />  <div class="region2"><span style="color: #000000"><span style="color: #0000BB"><?php </span><span style="color: #007700">print </span><span style="color: #0000BB">$content</span><span style="color: #007700">[</span><span style="color: #DD0000">'region2'</span><span style="color: #007700">] </span><span style="color: #0000BB">?></span></span></div><br />  <div class="region3"><span style="color: #000000"><span style="color: #0000BB"><?php </span><span style="color: #007700">print </span><span style="color: #0000BB">$content</span><span style="color: #007700">[</span><span style="color: #DD0000">'region3'</span><span style="color: #007700">] </span><span style="color: #0000BB">?></span></span></div><br /></div>

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 */<br />.super-bricks-base .region1 {<br />  width:49%;<br />  float:left;<br />}<br />.super-bricks-base .region2 {<br />  width:49%;<br />  float: left;<br />}<br />.super-bricks-base .region3 {<br />  clear:both;<br />}<br /><br />/* Use the .super-bricks class for actual panel styles*/<br />.super-bricks .region1 {<br />  //highly customized styles, fixed width, background images, etc<br />}<br />.super-bricks .region2 {<br />  //highly customized styles, fixed width, background images, etc<br />}<br />.super-bricks .region3 {<br />  //highly customized styles, fixed width, background images, etc<br />}

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']

<!-- STATIC HTML --><br /><div class="panel-display super-bricks-base  super-bricks clear-block"><br />  <div class="region1"><br />    <br />    <!-- Insert DUMMY static panels content so that we aren't suprised later! --><br />    <div class="panel-pane"><br />      <h2 class="pane-title">Title of content #1</h2><br />      <div class="pane-content">Lorem ipsum phonius latinus </div><br />    </div><br />    <div class="panel-separator"> </div><br />    <div class="panel-pane"><br />      <h2 class="pane-title">Another piece of content #2</h2><br />      <div class="pane-content">Yet another piece of dummy Lorem ipsum phonius latinus </div><br />    </div><br />    <br />  </div><!-- region1 --><br /></div><!-- /.panel-display -->

becomes:

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

<span style="color: #000000"><span style="color: #0000BB"><?php </span><span style="color: #FF8000">// This is the tpl.php file </span><span style="color: #0000BB">?></span></span><br /><div class="panel-display super-bricks-base  <span style="color: #000000"><span style="color: #0000BB"><?php </span><span style="color: #007700">if (</span><span style="color: #0000BB">arg</span><span style="color: #007700">(</span><span style="color: #0000BB">0</span><span style="color: #007700">) != </span><span style="color: #DD0000">'admin'</span><span style="color: #007700">) { print </span><span style="color: #DD0000">'super-bricks'</span><span style="color: #007700">; } </span><span style="color: #0000BB">?></span></span> clear-block" <span style="color: #000000"><span style="color: #0000BB"><?php </span><span style="color: #007700">if (!empty(</span><span style="color: #0000BB">$css_id</span><span style="color: #007700">)) { print </span><span style="color: #DD0000">"id=\"$css_id\""</span><span style="color: #007700">; } </span><span style="color: #0000BB">?></span></span>><br />  <div class="region1"><br />    <span style="color: #000000"><span style="color: #0000BB"><?php </span><span style="color: #007700">print </span><span style="color: #0000BB">$content</span><span style="color: #007700">[</span><span style="color: #DD0000">'region1'</span><span style="color: #007700">] </span><span style="color: #0000BB">?></span></span><br />  </div><br /></div>

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.

Comments

Thanks! Another piece of the puzzle revealed..

You can provide alternate tpl.php and css files for layouts using 'admin theme' and 'admin css'. The use of 'admin css' can very nicely do away with the problems you have, and eliminate the need for that ugly arg(0) hack.

You have to save twice for the same reason that you have to save twice in Views. Unfortunately it's not as obvious as it is in Views. This is a UX WTF that I'm working on ways to deal with.

You should probably prefer page_manager_get_current_page() over panels_get_current_page_display() but I don't think there is any real difference. Also rather than just setting $is_panel I found it better to simply use a different template suggestion so you get page-is-panel.tpl.php instead which is very streamlined. If you're using Panels Everywhere you don't need this hack at all, it does that for you.

I love your writeup of how to turn design mockups into layout templates. That seems like something we could find nifty ways to streamline, though it may not even be necessary. Perhaps a function like:

<?php
print panels_print_dummy_pane('name-of-content-area');
?>

It would automatically print a pane full of lorem ipsum, and is very easy to search and replace into the print $content[] lines it will be in the final version. Alternately a simple panels module that uses the display but instead automatically fills it with dummy content in a consistent way would make it super quick to test layouts.

I've been using Flexible layouts a lot. For some reason, despite the fact that I've been writing CTools' content types and relationships for a very long time, the idea of creating my own layout scared me. I think it was more the having to do CSS part than the CTools part. LOL! But, reading this, it doesn't sound so bad. I think I'll finally give it a whirl.

Michelle

Panels styles and layouts can also be declared in your theme by adding these lines to the .info file.

plugins[panels][layouts] = plugins/layoutsplugins[panels][styles] = plugins/styles

Like you, however, I've almost always declared them as module plugins. However, if anyone is curious, there is a working example of theme based layouts and styles in acquia_marina i believe.

Where is the full post? I can't find it..The link returns back to the same page..

If folks are looking for examples, the latest release of Zen includes a handful of panel layouts, all of which integrate nicely with the overall Zen theming system.

I'm curious what the benefit of adding tpl files via a module (and keeping them in the module folder) is over having them in the theme? So the new panel layout is available to all themes, perhaps?

Thanks, this is helpful!

Amanda

@merlinofchaos: thanks for the tips. and yea! A little dummy pane function might just do the trick and save some time there.

@Nick Lewis: Good tip! That is much faster than the module method.

@Prashant: I added that because I wasn't sure if there was a "Read more" link on the teaser. If you can read this comment you are looking at the full post.

@Amanda: Yes, the only advantage is portability of the layout and maybe some continuity with other panels plugins. Also I wonder what happens if you define a panel layout in one theme, but then switch to a different theme for the administration of the panel content. My guess is it would open a hole in the space-time continuum or something.

This issue provides a panels plugin example. It should eventually go into the panels module. CTools also has a CTools Plugin Example that it ships with.

Thanks for the great article!

Hello, I am chiming in with what happens when you put your custom layouts in the theme folder and then use a different theme for admin: you get error messages when you try to administer your layouts in panels and you can't add or edit content.

I was thinking that perhaps the way to solve this issue and still put the layouts in the theme, which is where I like to put them, would be to define them in both themes. But then of course if you change something you would need to remember to change it in both themes.

One thing that has eluded me is a simple way to define different styles for the admin and front end, and so I'll be trying your method next. Thanks for the post!

I recently tried this with the most recent CTools module and it did work for me - I used Garland for managing the admin backend of the panel and a custom theme for the front, it recognized the layout without any hiccups. I did have to clear the cache a few times though.

Just a follow up, I didn't 100% understand what Merlin of Chaos was referring to when he was talking about specifying an admin theme and admin css. It turns out you can specify an admin css file and template directly in the super_bricks.inc file like this:

this

<?php
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',    'admin theme' => 'yourmodule_panels_super_bricks_admin',    'admin css' => 'super_bricks_admin.css',     'panels' => array(      'region1' => t('Region 1'),      'region2' => t('Region 2'),      'region3' => t('Region 3'),        ),  );  return $items;
?>

This is a much cleaner approach then using arg(0) to switch out the CSS file but requires a little more forethought.

Thanks, this post was solid gold. Absolute props to merlin and the panels team for making such a freaking awesome 3.x branch!

I'm trying to do this by cloning and modifying an existing panels layout, and I'm having problems getting my tpl.php to be recognized. In reading through the instructions, I'm seeing some inconsistencies in template naming.

First, you say that we should have a file /sites/all/modules/yourmodule/plugins/layouts/super_bricks/yourmodule-panels-super-bricks.tpl.php. Then you say "super_bricks.tpl.php is the template used for your layout;". Which is it: yourmodule-panels-super-bricks.tpl.php or just super-bricks.tpl.php? Also, are you sure the "-panels" is needed as part of the name? I noticed it's used in the default layout files, but I assumed that was because they are in the panels module.

Then, when setting the 'theme' setting in hook_panels_layouts, you have it as 'yourmodule_panels_super_bricks'. Is the "_panels" needed in that name since this is not in the panels module any more (i.e. should it just be yourmodule_super_bricks)?

Thanks.

I tried to follow this and was having problems with my layout not showing up in the panels admin. According to merlinofchaos (and verified in my case) the value for the theme array in the .inc file has to match the name of the template file with one variation; the name in the .inc file uses underscores, but the name of the .tpl.php file uses dashes (the underscores get converted to dashes when getting the tpl name). So, in my case, I had

'theme' => 'marketplace_home',

in the .inc file, and the name of my template file was marketplace-home.tpl.php.

This didn't work for me, after talking to Nicholas Thompson (global redirect and other modules) gave a me an easier method, so I hope it helps someone too;

Create a new module and put this in (change pw_custom_panel with the name of your module)

<?php/*** Implementation of hook_panels_layouts().*/function pw_custom_panel_layouts_panels_layouts()   $items['pwfrontpage'] = array(    'title' => t('pwfrontpage'),    'icon' => 'flexible.png',    'theme' => 'pw_custom_panel_layouts_panels_pwfrontpage',    'css' => 'pwfrontpage.css',    'panels' => array(      'region1' => t('Region 1'),      'region2' => t('Region 2'),      'region3' => t('Region 3'),        ),  );  return $items;

Then create a template (tpl.php) in the same directory as your module, so in this example the template is 'theme' => 'pw_custom_panel_layouts_panels_pwfrontpage' so the tpl.php file will be called pw-custom-panel-layouts-panels-pwfrontpage.tpl.php (note _ is replaced with -). Also create your png image of the panel and the css file, and that's it!!!

thanx for coming up with this great piece of information.It's one of the most well-written,clear and consice module tutorial i've come across,in recent time.

It could be interesting fo newdies like me to get a sample module : that would be a way to start customizing.

To dificult for the moment to build the module, the templates...

Post a comment

To prevent automated spam submissions leave this field empty.
CAPTCHA
Let us know you're human by typing in this code. The code is case sensitive.
Image CAPTCHA
Enter the characters shown in the image.