Expanding/Contracting Blocks Persistently

Last modified: August 18, 2009 - 05:34

Description

This allows users to click on an "expand" or "contract" icon in a block, and change it from the contracted to the expanded state (and vice versa). The state is stored on the user's machine as a cookie, so that it remains as specified by the user, on all pages the block appears. You have complete control over what appears in the "expanded" and "contracted" states--if you want, "contracted" can mean "just show the block's title," but it could also mean "show a very different version of this block." One way I use it: a "Subscribe" block that, in contracted form, displays only an RSS link, but in expanded form provides icons and links to allow users to subscribe with one click via a variety of popular RSS readers.

Step 1 of 4

In your theme, create a subdirectory called "Js" (e.g., themes/custom_theme/js). Cut and paste the following, and save it as a file in this directory, naming it something like mysitesjavascript.js:

function toggleme(myblock, whichway) {
  var expireDate = new Date()
  var expstring=expireDate.setDate(expireDate.getDate()+30)

  if (whichway == 'bigger') {

     // show big
    document.getElementById('toggleme-' + myblock + '-big' ).style.display = '';
    // hide small
    document.getElementById('toggleme-' + myblock + '-small' ).style.display = 'none';
    //set cookie for myblock to big
    document.cookie = myblock + "=big; expires="+expireDate.toGMTString()

  }
  else {


     // show small
    document.getElementById('toggleme-' + myblock + '-small' ).style.display = '';
    // hide big
    document.getElementById('toggleme-' + myblock + '-big' ).style.display = 'none';
    //set cookie for myblock to small
    document.cookie = myblock + "=small; expires="+expireDate.toGMTString()
  }

}

Note: The "30" on the third line sets our cookies to expire after 30 days from last stored (so if a user doesn't show up for 30 days, blocks will appear in the default manner, rather than as last specified by the user). You can change that if you want.

Step 2 of 4

Next, you need to modify page.tpl.php (in your custom theme directory) to recognize and load this Javascript file. To do so, add the following lines directly above the </head> tag:

  <?php /* custom stuff for block display/closure */ ?>
  <script type="text/javascript" src="<?php print $GLOBALS['base_url']."/"; print $directory; ?>/js/YOURJAVASCRIPTFILENAME.js"></script>

Change "YOURJAVASCRIPTFILENAME" to the name under which you save the Javascript from step 1.

Step 3 of 4

You need to place, in the same directory as the Javascript file (e.g., themes/custom_theme/js) two images: one to say "expand this block" and one to say "contract it." You can use any image you like, but our code assumes that the first is called "plus.gif" and the second "minus.gif" (see Downloads below).

Step 4 of 4

You now need to create a file, which goes in your custom theme directory, called something like block-(module)-(delta).tpl.php. For simple blocks that are not generated by other modules, you can figure this out by editing the block and seeing what number appears after the final slash in the URL when you do; this is the delta, and the filename will be block-block-THATNUMBER.tpl.php. For blocks generated by Views, the filename will instead be block-views-MYVIEWNAME.tpl.php. Figuring this out for blocks generated by other modules is, ahem, left as an exercise for the reader (meaning I don't know.)

To create this file, start by copying this template and saving it under the name suggested above:

<?php
$mymoduledelta
= $block->module .'-'. $block->delta;
?>


<div id="block-<?php print $mymoduledelta; ?>" class="block block-<?php print $block->module ?>">

<div class="toggleme" id="toggleme-<?php print $mymoduledelta; ?>-small" <?php if ((empty($_COOKIE[$mymoduledelta])) OR ($_COOKIE[$mymoduledelta] == "big")){print "style=\"display:none\"";}?>><img src="<?php print $GLOBALS['base_url']."/"; print $directory; ?>/js/plus.gif" class="toggleimage" onClick="toggleme('<?php print $block->module .'-'. $block->delta; ?>', 'bigger')" />
<?php if ($block->subject): ?>
  <h2><?php print $block->subject ?></h2>
<?php endif;?>

<div class="content">
THIS IS MY SMALL STUFF
</div>

<div class="toggleme" id="toggleme-<?php print $mymoduledelta; ?>-big" <?php if ($_COOKIE[$mymoduledelta]  == "small"){print "style=\"display:none\"";}?> ><img src="<?php print $GLOBALS['base_url']."/"; print $directory; ?>/js/minus.gif"  class="toggleimage" onClick="toggleme('<?php print $block->module .'-'. $block->delta; ?>', 'smaller')" />
<?php if ($block->subject): ?>
  <h2><?php print $block->subject ?></h2>
<?php endif;?>

<div class="content">
THIS<br />
IS<br />
MY<br />
BIG<br />
STUFF<br />
</div>
</div>
</div>

  1. replace "THIS IS MY SMALL STUFF" with whatever gets shown in the contracted block, and "THIS IS MY BIG STUFF" with whatever gets shown in the expanded version. In the case of a simple generic block, you probably copy whatever you had in the block previously to "MY BIG STUFF" and figure out what you want in "MY SMALL STUFF" separately. In the case of a block generated by a module, it could get more complicated.
  2. If you are using different expand/contract images, replace "plus.gif" above with the name of the expand image, and "minus.gif" with the name of the contract image for this block. Might also have to change path names depending on where you put these files. Note that you're creating a different .tpl.php file for each of your blocks, so you can use different expand/contract images for different blocks if you like, by changing these filenames in the .tpl.php file for different blocks.
  3. If you wish the toggle to be between nothing but the title of the block and the full block, then replace "THIS IS MY SMALL STUFF" with nothing (that is, simply delete that text), and replace "THIS IS MY BIG STUFF" with whatever was in the block previously.
  4. If you wish the contracted version to be the default instead of the expanded version, move the text "(empty($_COOKIE[$mymoduledelta])) OR" from the if statement in the first div to the if statement in the second div.
  5. If this is a block supplied by a View, and you wish the expanded state to be the full view, then replace "THIS IS MY BIG STUFF" with:
    <?php if ($block->subject): ?>
      <h2><?php print $block->subject ?></h2>
    <?php endif;?>

What's Going On Here

  1. The PHP in the .tpl.php file is generating two different versions of your block; a contracted version, and a full version. Both will show up in the source of the generated HTML page, but only one gets displayed at any given time.
  2. I've set it up so that the expanded version is the default--that is, shown to any user who hasn't changed the state of the block--but in "Step 4" above I tell you how to change things if you want the default to be the contracted state.
  3. Whenever a user changes the state of the block, a cookie indicating his/her preferred state is stored on his machine. Since cookies also go back to the server on an http request, the cookie is available to our server-side PHP. The PHP in your .tpl.php block looks at the cookie to determine what state is preferred, and when the page is served to the user, the right div is displayed, while the other is hidden--though both are in the page's full HTML, so it's easy to switch if the user wants to do so.
  4. More common Javascript toggles either don't store a cookie (so that navigating away from the page loses the user's desired state), or restore the state from the cookie (so that there's sometimes a "flash" as the state supplied by the server is replaced by that desired by the user). Our version loads and displays stably.
  5. Most Javascript toggles simply reveal some hidden information when invoked; ours allows complete control over the "small" and "big" versions, which can be quite different. This is useful in some cases.

Downloads

You can get a zip file containing a) the Javascript template from 1, b) the PHP template from 4, and c) some simple minus.gif and plus.gif images from here. This is useful but not sufficient; you still need to modify page.tlp.php as specified in 2, and the PHP template needs to be modified to your needs, and saved as a filename suggested in 4.

Caveats

This code creates a separate cookie for every block that can be toggled. Browsers have a limit (which varies by browser) on the number of cookies they will allow from a single site. IIRC, the chintziest is IE, which only allows 20 cookies from a single site (and a total of 4K cookie data from a single site). So if your site generates a LOT of cookies, and you are toggling a lot of blocks, you might run up against this limit. I thought about modifying the code to generate a single cookie for all blocks and parsing it to determine appropriate block statuses, but it isn't necessary for my site, and I figure that if your site is running up against these limits, it's probably pretty sophisticated, and you can probably figure out how to do that anyway.

 
 

Drupal is a registered trademark of Dries Buytaert.