OMG! Dynamic jCarousel with Themed Panels 2 pane node parts!

Panels 2, the quiet revolution indeed! This tutorial will go through a series of detailed exploratory steps to achieve a dynamic jCarousel panel whose content is derived from the attached files of the node context of a panel page configured for node override. That is, the jCarousel pictured in the following image contains the thumbnails of all the images attached to the node providing the context to the shown panel page. And this is achieved by overriding theme_panels_pane(), the default theming function for individual panels in a panel page.

Dynamic jCarousel with Themed Panels 2 pane node parts!

Dynamic jCarousel with Themed Panels 2 pane node parts!

In plain English, what I want is a panel page which overrides nodes of type "vehicle". On the top I want one pane which has a jCarousel slideshow of all the images I have uploaded by attaching files to the node using the glorious killes masterpiece Upload Image module. Then just below this I want to include a teaser like field which is also included in the cck generated content type. And on the bottom will come the rest of the node's content.

Panels 2 References

This is an intermediate level tutorial, if you are completely new to Panels 2 (practically everyone), you would be well served by checking out the following background info, tid-bits and tutorials:

Project page: http://drupal.org/project/panels


panels 5.x-2.0-beta2: http://drupal.org/node/210923


Panels 2 Documentation & Handbook on drupal.org: http://drupal.org/node/201914


Group on g.d.o.: http://groups.drupal.org/panels


Early but interesting "Panels 2 documentation" on g.d.o: http://groups.drupal.org/node/7350


Panels 2 Tabs module (Wim Leers): http://drupal.org/project/panels_tabs


Demo: http://nodequeue.demo.logrus.com/

merlinofchaos snippet on creating panels templates: http://drupal.org/node/206317

Step 1: How does jCarousel work on Drupal, anyway?

A very convenient way of making Drupal jCarousel-ready is by installing Wim Leers thoughtful jCarousel module. As advertised, this simply installs (no small measure of convenience) the version of the jQuery jCarousel plugin which is compatible with jjeff's jQuery Update module (version 1.1.2 instead of the ancient 1.0.1 which comes bundled with Drupal 5.x), and provides a convenience function for modules or theming templates to call.

So first you install jQuery Update and then jCarousel.

Step 2: Let's give jCarousel a dry-run test on Drupal

Just to make sure we have all our ducks in a row, let's just set up a test page, shall we? Create a page content type, and invoke the php input filter, and paste in the following:

<?php
    jcarousel_add('tango');
    drupal_add_js (
      '$(document).ready(function(){
         $("#carousel-cars").jcarousel();
}); ',
      'inline');
?>
 
<div id="carousel-cars-horiz">
<ul id="carousel-cars"  class="jcarousel-skin-tango">
<li><a href="http://example.com" target="_blank"><img src="/files/images/01 2008 Elantra.jpg" border="0" width="80" height="45" /></a></li>
<li><a href="http://example.com" target="_blank"><img src="/files/images/04 2008 Elantra.jpg" border="0" width="80" height="45" /></a></li>
<li><a href="http://example.com" target="_blank"><img src="/files/images/07 2008 Elantra.jpg" border="0" width="80" height="45" /></a></li>
<li><a href="http://example.com" target="_blank"><img src="/files/images/10 2008 Elantra.jpg" border="0" width="80" height="45" /></a></li>
<li><a href="http://example.com" target="_blank"><img src="/files/images/19 2008 Elantra.jpg" border="0" width="80" height="45" /></a></li>
<li><a href="http://example.com" target="_blank"><img src="/files/images/20 2008 Elantra.jpg" border="0" width="80" height="45" /></a></li>
</ul>
</div>

Just hit submit, and we should have that working.

Let's modify it a bit just to get the autoscrolling going:

<?php
    jcarousel_add('ie7');
    drupal_add_js (
      '$(document).ready(function(){
         $("#carousel-cars").jcarousel({
           scroll: 1,
           auto: 2,
           wrap: "last"
         });
}); ',
      'inline');
?>
 
<div id="carousel-cars-horiz">
<ul id="carousel-cars"  class="jcarousel-skin-ie7">
<li><a href="http://example.com" target="_blank"><img src="/files/images/01 2008 Elantra.jpg" border="0" width="80" height="45" /></a></li>
<li><a href="http://example.com" target="_blank"><img src="/files/images/04 2008 Elantra.jpg" border="0" width="80" height="45" /></a></li>
<li><a href="http://example.com" target="_blank"><img src="/files/images/07 2008 Elantra.jpg" border="0" width="80" height="45" /></a></li>
<li><a href="http://example.com" target="_blank"><img src="/files/images/10 2008 Elantra.jpg" border="0" width="80" height="45" /></a></li>
<li><a href="http://example.com" target="_blank"><img src="/files/images/19 2008 Elantra.jpg" border="0" width="80" height="45" /></a></li>
<li><a href="http://example.com" target="_blank"><img src="/files/images/20 2008 Elantra.jpg" border="0" width="80" height="45" /></a></li>
</ul>
</div>

To see more fun possibilities, check out the source code (jquery.jcarousel.js) to see "var defaults".

Step 3: Preparing the panel page

The panel page takes after the node override example in the Drupal Handbook. The main thing is to set the path to "node/%" in the settings tab, then in the context tab, add a "Node ID" argument, enabling the content type under "Node types" and "Own display". After choosing suitable layout settings according to the layout of your choice, go to the main content tab, where we have to click on the "Node ID 1 Page" tab (not the default tab) in order to add content by clicking on a pane's "+" sign. In the pop-up "Add content to ...", under "Node context" click on "Node content" for one pane; and, in another pane, click on "Attached files". It is the latter that is of interest in this tutorial.

The whole idea is to be able to distribute bits and pieces of the node satisfying the panel argument in different panes, together with content related to it as well; this is the idea of "context" (the context of the panel is the argument) and a whole revolution in total layout control.

It is assumed that several images have already been uploaded to some nodes of the type. That being the case, when you navigate to http://example.com/node/123 (where 123 is the nid of a node of the kind specified in the panel page, and has attached files already uploaded) in the "attached files" pane a list of attached files will be seen, while in the "Node content" pane, the node content will be visualized.

What we now want to do is theme the simple list of files into a dynamic jCarousel loaded with that list, according to which node is being displayed.

Step 4: We know about overriding theme functions, but how do you override panels theming?

In the Panels issues queue there is an intriguing interchange between merlinofchaos himself, and an unsung hero pioneer, jockox3@drupal.org, who was basically asking himself this question. The gist (apart from the fascinating discussion about letting Panels 2 take over all Drupal layout duties, very interesting...) is:

"Okay - I spent the day reading the panels module code and assume I have
to override the function theme_panels_pane($content, $pane, $display)
function from the panels.module file."

Cool. So I went to panels.module and dutifully copied over this theming function to my phptemplate file, renaming it to "phptemplate_panels_pane" as I would any theming function I was going to override.

Step 5: Many print_r()'s later, we are in control, folks!

I'm Josh Koenig (joshk) and Drupal Dojo trained, and damn proud of it! I know for a fact from my hatchet bearing mentor (can't find that video online that shows Josh splitting trunks to show how productive his Drupal shop is, very original! It is hard work!) what he taught us in those first heady free-for-all days of drupal dojo: you can do anything with a bit of nerve, print_r() and running things till the error messages just go away!

So basically I examined the 3 parameters to the theming function and came up with the following code (explained below) to get what I wanted (see image at start of article). Of course, it would have been neater to bundle up everything in terms of a phptemplate callback function, but I was pretty happy with what I got so far:

function phptemplate_panels_pane($content, $pane, $display) {
  // start of modified section for node "Attached files" pane
  if ($pane -> pid == '32') {
   if ($pane -> context -> data -> files) {
    jcarousel_add('ie7');
    drupal_add_js (
      '$(document).ready(function(){
         $("#carousel-cars").jcarousel({
           scroll: 1,
           auto: 3,
           wrap: "last"
         });
       }); ',
      'inline');
    $cars = '<ul id="carousel-cars"  class="jcarousel-skin-ie7">';
    foreach ($pane -> context -> data -> files as $car_img) {
      $car_thumbnail = substr($car_img -> filename, 0, -4) . '.thumbnail.jpg'; 
      $car_href = '/files/images/' . $car_img -> filename;
      $cars .= '<li><a href="'. $car_href . '" target="_blank"><img src="/files/images/' . $car_thumbnail . '" width="100" height="100"/></a></li>';   
    }
    $cars .= '</ul>';
   }
   $content -> content = $cars;
  }
// end of modified section for node "Attached files" pane
// from this point on, the original theming function is left intact
  if (!empty($content->content)) {
    $idstr = $classstr = '';
    if (!empty($content->css_id)) {
      $idstr = ' id="' . $content->css_id . '"';
    }
    if (!empty($content->css_class)) {
      $classstr = ' ' . $content->css_class;
    }
 
    $output = "<div class=\"panel-pane$classstr\"$idstr>\n";
    if (user_access('view pane admin links') && !empty($content->admin_links)) {
      $output .= "<div class=\"admin-links panel-hide\">" . theme('links', $content->admin_links) . "</div>\n";
    }
    if (!empty($content->title)) {
      $output .= "<h2 class=\"title\">$content->title</h2>\n";
    }
 
    if (!empty($content->feeds)) {
      $output .= "<div class=\"feed\">" . implode(' ', $content->feeds) . "</div>\n";
    }
 
    $output .= "<div class=\"content\">$content->content</div>\n";
 
    if (!empty($content->links)) {
      $output .= "<div class=\"links\">" . theme('links', $content->links) . "</div>\n";
    }
 
 
    if (!empty($content->more)) {
      if (empty($content->more['title'])) {
        $content->more['title'] = t('more');
      }
      $output .= "<div class=\"more-link\">" . l($content->more['title'], $content->more['href']) . "</div>\n";
    }
 
    $output .= "</div>\n";
    return $output;
  }

Explanation of code

It is important to bear in mind that theme_panels_pane() does just that: it themes a pane in a panel. In order to render a complete panel page, each pane is rendered via a separate call to this function. Here we are on the lookout for the pane with the list of attached images.

We start with a simple if statement looking for the unique panel id of the "Attached files" panel. If there are any files (if the parameter $pane shows there are files under $pane -> $pane -> context -> data -> files) then we enable jcarousel with the jcarousel_add function, invoking the ie7 theme instead of the default tango theme. This has to match the class element "jcarousel-skin-ie7" of the <ul> jcarousel container tag.

Next comes the inline javascript actually invoking the jcarousel function itself, with the parameters necessary for autoscrolling.

We then iterate over the list of attached files present in the parameters and set up the necessary <li> tags as wrappers around the file paths.

We then assigned the accumulated html to the $content -> content variable, and let nature run its course...

I hope this will be of use to someone, I found it fascinating as a small step towards experimenting with this revolutionary way of thinking about layout and contexts in Drupal.

Gracias por el post, era muy

Your codew doesnt work

Hi i am getting an error when i am pasting u r code.I installed both modules sucessfully but when i am creating a new content then after submitting i am getting
Fatal error: Call to undefined function jcarousel_add() in /var/www/includes/common.inc(1352) : eval()'d code on line 2
Any suggestion?

The Devel and Jcarousel Clash

Problem: Call to undefined function jcarousel_add()

Solution: Disable Devel module completely.

Apparently the creators of Devel imported code directly into the module so it tends to clash with other modules utilizing jquery.

Once disabled the errors should cease to show.

If you are only seeing error page and can't disable the devel module, you can comment out that particular line of code as described in the error message, upload it, then disable the devel module and finally uncomment that line of code and upload.

Hopefully this will help someone.

Good on your

Good on your

Awkward because displaying images associated with a node

I didn't know it was this awkward to set up. I guess it's because you're not displaying nodes, but images associated with a node. If you're displaying nodes, you can use a Mini panel + the Carousel Panel Style module.

Also, if you did not know yet, I'm going to consolidate the jCarousel + Carousel Panel Style in a single module: Carousel. It will also include a module for Views support (this has been committed already, actually).

The intruiging part here is panels theming

Right, you certainly have provided tools for "non-awkward" use of jCarousel, and your plans for the carousel module sound awesome.

The thing is that here I was actually delving into the question of theming panels, which amazes me. I am very interested in all the layout possibilities, in fact, how layout in Drupal will never be the same again.

So jCarousel was simply a random objective.

The rest of the awkwardness has nothing to do with jCarousel per se, but rather in the huge mess with the obsolete character of jQuery versions in Drupal, and we will see if this is overcome in Drupal 6.

problem with upload_image module

Another part of the awkwardness in this example comes from an idiosyncracy of the http://drupal.org/project/upload_image module, which converts image file attachments to nodes automatically, which is great.

What is not so great is, that, in doing so it dutifully moves the files from ./files/name_of_image to ./files/images/name_of_image (where it belongs as a node), but the upload module thinks it's still in ./files/name_of_image

That's why not dealing with nodes. I'm going to put that in the issue queue right away.

Thanks for you feedback!

File & image handling in Drupal can and *will* improve

Well, as we all know, file and image handling can be much better in Drupal. In truly *all* areas that is:
- single module instead of a dozen of incompatible ones (especially for images this is a pain)
- files/images are NOT nodes, while some make them into nodes
- when they're not nodes, we should still have the ability to display them in queues, views and of course panels. Once that's in place, what you've done here will become trivial and fully configurable through the UI.

Of course, what I'm writing here is pretty obvious. But the the pieces of the puzzle *are* falling into place:
- Drupal 6 has a newer jQuery version, which is much more mature and will offer much more exciting features (jQuery UI and Enchant are without any doubt significantly better than jQuery 1.1 + Interface)
- Drupal 7 will get a much improved File API
- Drupal 7 will have much better image handling

So, I expect this to become trivial by the time of Drupal 7 (and probably Panels 3 by that time).

Really amazing, thanks for

Really amazing, thanks for sharing.

thanks for that, great intro to panels context........

.....one thing i would say though, instead of overiding the theme function in your template.php file, I would create a module and use http://api.drupal.org/api/function/hook_theme_registry_alter/6 . But that's just me :)

Well this was with an early panels release

it didn't have all the modern conveniences provided in later version.

And I'm really excited about Panels 3, Panels everywhere and the ctools page manager!

Victor

dsm

If you use devel function, it includes the dsm function. dsm is the new and improved print_r. It's much easier to read than the output of print_r.

dsm is cool, thanks

I always forget... because actually I use the log4drupal module which is very similar to what I am used to coming from the java world... and that prints everything to a log, and print_r is cool enough for what I need to get out of a form or a panel or a node or what have you...

But I think you are right in recommending dsm to the people, yes, let's use it!

Victor