Enhancing the sidebar module structure
If you have used iGoogle you know the small windows you can hide or show. There is, of course, more like the option to move the windows dynamically to any place but we will do the hide/show part of the game. What we will do is add a module header as a CMS module to each of the sidebar modules with different parameters.
We will start by creating a module title extension which contains an optional icon, the module title and the buttons:

In this case the module is positioned inside another module and has a template of its own. As it is ofthen the case it is a good idea to start with the template. After creating the template you know what kind of data you have send to it.
The template
<div class="small_module_header"> {if $module_icon_class ne ""} <div class="module_icon {$module_icon_class}"></div> {/if} <div class="small_module_title">{$title|default:"Title"}</div> {if $showbuttons} {* the collapse button first *} <div class="mod_minimize" style="display:block"
onclick="toggleModuleFromButtons(this);"> </div> {* the restore button *} <div class="mod_restore" style="display:none"
onclick="toggleModuleFromButtons(this);" > </div> {/if} </div> {* /module header *}
As you notice we are calling a funny function ToggleModuleFromButtons with the calling DIV as the parameter. This function will launch the hide/restore process which is a bit tricky but will work if we use the Document Object Model properly. We will write the Javascript function later.
The Style Sheet
As you can see much of the stuff is wisely defined in the CSS. The icon div it is only shown if defined. The title will always be shown shown and it must be defined in the CSS as well. The same goes for the buttons so we have quite a few things to bear in mind. So let's add the style definitions first to our infamous styles.css:
.small_module_header {
background-color:#F8E19F;
color:#60490E;
font-weight:bold;
padding:4px;
border-color : #FFE4B5 #9B6E00 #9B6E00 #FFE4B5;
border-width : 1px 2px 2px 1px;
border-style : solid solid solid solid;
height:20px;
width:100%;
}
.small_module_title {
background-color:#F8E19F;
color:#60490E;
font-weight:bold;
padding:4px;
position:relative;
margin-right:30px;
text-align:left;
float:left;
}
.mod_minimize {
float:right;
margin:0px;
height: 21px;
width: 23px;
background-image:url(img/minus_22_gray.png);
overflow:hidden;
cursor:pointer;
z-index:3;
}
.mod_restore {
float:right;
margin:0px;
height: 20px;
width: 22px;
background-image:url(img/plus_22_gray.png);
overflow:hidden;
position:relative;
cursor:pointer;
z-index:3;
}
The Module
The module code simply copies the definitions to the module header:
<?php class module_header_module extends CmsModule { // the template var $template = "module_header.tpl"; // default title if we forget it var $title = "Module title"; // buttons shown by default var $showbuttons = true; // the icon class empty by defaul var $module_icon_class = ""; function init(){ if ($this->vars['title'] != '') $this->title = $this->vars['title']; if ($this->vars['showbuttons'] != '') $this->showbuttons = $this->vars['showbuttons']; if ($this->vars['module_icon_class'] != '') $this->module_icon_class = $this->vars['module_icon_class']; } function fetch(){ $mySmarty = new Smarty(); $mySmarty->assign("showbuttons",$this->showbuttons); $mySmarty->assign("title",$this->title); $mySmarty->assign("module_icon_class",$this->module_icon_class); $mySmarty->assign("content_container",$this->content_container); return $mySmarty->fetch($this->template); } } ?>
Including the header inside another module
Okay. Now we have some of the building blocks ready and time to hit the hard part and take the RSS module as an example. We do not need to make changes in the PHP code, just the template.. Let's have a quick look at the HTML code we want to create (so this is our XHTML target):
<div class="small_module_frame"> <div class="small_module_header"> <div class="module_icon rss_module_icon"></div> <div class="small_module_title">Module title</div> <div class="mod_minimize" style="display:block;"
onclick="toggleModuleFromButtons(this);"> </div> <div class="mod_restore" style="display:none;"
onclick="toggleModuleFromButtons(this);" > </div> </div> <div id='idebff14bef4dded2fcbf1a2c7886dc35d3eada565'
class="module_content rss_box"> <p> <a class="rss_item" href="http:/link..." target="_blank"> Man and woman die in fire at flat </a> </p> </div> </div> <script type="text/javascript"> <!-- setModuleTitle('idebff14bef4dded2fcbf1a2c7886dc35d3eada565','BBC Scotland'); --> </script>
Well that looks weird - what are those funny strings?. Let's have a closer look at it
Problem doing it on-the-fly
The problem is that when we include a Smarty extension with dynamic parameters (like title) inside another container (rss module) there is no simple way of transferring the parameters of the outer module to the inner. To change the module title we can, however, use some simple javascript and use unique container ids for that. We can assume the following:
- Each module title is unique and we can rely on that and use
- The title may contain characters illegal in XHTML id values (like spaces) which will upset DOM
That is why we are creating an "id"+SHA1 hash which will be formally legal.. In order to make thins as simple as possible we need an SHA1 modifier for Smarty and write it a bit later.
Now we can write a small JavaScript function that sets the module title. Create file js/ajaxfuncs.js and include this code in it:
function setModuleTitle(containerID,title){ // find parent container of the param container var childcontainer = document.getElementById(containerID); if (!childcontainer) alert("No childcontainer found: "+containerID) var small_module_frame = childcontainer.parentNode; // find all divs in the parent var divs = small_module_frame.getElementsByTagName('div'); // find the title div and set its content for (i=0; i<divs.length; i++){ if (divs[i].className.indexOf("small_module_title") >=0 ){ divs[i].innerHTML = title; break; } } }
Now the module title should work, at least. Let's modify the RSS template code:
<div class="small_module_frame"> {* the module header cms module is included here *} {cmsmodule name="module_header" title="" module_icon_class="rss_module_icon" path="modules/module" showbuttons="1"} <div id='id{$title|sha1}' class="module_content rss_box""> {foreach from=$items item=rssitem} <p><a class="rss_item" href="{$rssitem.url}" target="{$target|default:"_blank"}"> {$rssitem.title} </a></p> {/foreach} </div> </div> <script type="text/javascript"> <!-- setModuleTitle('id{$title|sha1}','{$title}'); --> </script>
Every module that contians the module_header functionality must be enclosed inside a div with class name "small_module_frame". The content must reside in a div the class name of which contains string "module_content". When these conditions are met we can safely assume that the DOM search for the various elements will work.
Rest of he Javascript
To be able to write the Javascript functionality for toggling the buttons we must write down the chain of events. Let's start from the 'module visbile' situation:
- The user clicks on the 'minus' div
- Function T
oggleModuleFromButton(<div>)is called - We locate the
parentand thegrandparentof the parameter div - From the grandparent div we search a child div containing class name "
module_content" and toggle its visibillity and in our example case the module content is hidden - From the parent div (the module header) we toggle the visibiility of divs with class name "
mod_minimize" and "mod_restore" so the minus is hidden and the plus is shown.
Actually that is not too difficult when you write it down what you have to do . So let's create the Javascript and break the code to smaller pieces:
function toggleModuleFromButtons(buttonDiv){ // find content container var contentContainer = getContentContainer(buttonDiv); // get header container var headerContainer = buttonDiv.parentNode; if (contentContainer){ if (contentContainer.style.display != 'none'){ contentContainer.style.display = 'none'; } else { contentContainer.style.display = 'inline'; } toggleModuleButtonsVisibility(headerContainer); } }
The next thing to do is to write the getContentContainer:
function getContentContainer(container){ // get parent container two levels up from the button // which should be the module frame var headerContainer = container.parentNode; var moduleContainer= headerContainer.parentNode; // find all divs in the module frame var divs = moduleContainer.getElementsByTagName('div'); // search the content div for (i=0; i < divs.length; i++){ if (divs[i].className.indexOf("module_content")>=0){ return divs[i]; } } // not found, return null return null; }
And finally we will write the toggleModuleButtonsVisibility():
function toggleModuleButtonsVisibility(container){ // get all divs and perform a walkthrough var links=container.getElementsByTagName('div'); for (i=links.length-2; i<links.length; i++){ if ((links[i].className.indexOf('mod_minimize')>=0) || (links[i].className.indexOf('mod_restore' )>=0)) { if (links[i].style.display=='block') links[i].style.display='none'; else links[i].style.display='block'; } } }
Add all these to the ajaxfuncs.js JavaScript include file you created. There is just one thing left: you must include it in the main template file in the <head> section:
<script type="text/javascript" src="js/ajaxfuncs.js"></script>
The bottom line and what we forgot
Why on earth do it like this? That is a good question, really. If we have just a couple of module types it will be just as easy to have the module take care of its own titles and hide/restore buttons. Let us do the math, however. How much extra code did we have to write when including the header module into the RSS module. Let's see... actually it is only the one-liner of Javascript we on the bottom of the RSS template plus the funny SHA1 div name. Plus, of course, the necessity to use a certain discipline in the module template structure.
That is not much! Now that you write another module you only need to copy the structure from a working one. After setting its title in the main template you have the working collapsible menu.
Oh, there is one thing we have forgotten: the sha1 modifier for Smarty. The system will not work if we do not write it. Here it is in its entirety, save it as lib/smarty/libs/plugins/modifier.sha1.php.
<?php /**
* Smarty plugin
* @package Smarty
* @subpackage plugins
*/ /**
* Smarty SHA1modifier plugin
*
* Type: modifier<br>
* Name: sha1<br>
* Purpose: Calculate the SHA1 sum for the string
* @author Markku Niskanen <markku dot niskanen at gmail dot com>
* @param string
* @param string
* @return string
*/ function smarty_modifier_sha1($string, $default = '') { if (!isset($string) || $string === '') return $default; else return sha1($string); } ?>
More to come
The ajaxfuncs is there not just for the purpose of hiding or showing divs. We will use it with the upcoming calendar and some snappy functions and write a few functions more.
The validity of this site may vary while it is being
developed.
Feel free to test it, though :)