Customising the way modules are displayed
m (Added heading (temporary fix for TOC problem)) |
m (advanced category) |
||
| Line 16: | Line 16: | ||
{{:Cascading module chrome styles|Cascading module chrome styles}} | {{:Cascading module chrome styles|Cascading module chrome styles}} | ||
| − | <noinclude>[[Category:Modules]] | + | <noinclude>[[Category:Modules]][[Category:Templates]][[Category:Tutorials]][[Category:Advanced]]</noinclude> |
| − | + | ||
| − | + | ||
Revision as of 07:12, 13 September 2012
This document explains different ways of customising your template to change the way modules are displayed.
Contents |
Counting modules in a given module position
The countModules method can be used within a template to determine the number of modules enabled in a given module position. This is commonly used to include HTML around modules in a certain position only if at least one module is enabled for that position. This prevents empty regions from being defined in the template output and is a technique sometimes referred to as “collapsing columns”.
For example, the following code includes modules in the 'user1' position only if at least one module is enabled for that position.
<?php if ($this->countModules( 'user1' )) : ?> <div class="user1"> <jdoc:include type="modules" name="user1" style="rounded" /> </div> <?php endif; ?>
The countModules method can be used to determine the number of Modules in more than one Module position. More advanced calculations can also be performed.
The argument to the countModules function is normally just the name of a single Module position. The function will return the number of Modules currently enabled for that Module position. But you can also do simple logical and arithmetic operations on two or more Module positions.
For example, to determine the total number of Modules enabled in the 'user1' and 'user2' positions together, you can use the function call:
$this->countModules( 'user1 + user2' );
Although the usual arithmetic operators, +. -. *, / will work as expected, these are not as useful as the logical operators 'and' and 'or'.
For example, to determine if the 'user1' position and the 'user2' position both have at least one Module enabled, you can use the function call:
$this->countModules( 'user1 and user2' );
Careful: A common mistake is to try something like this:
$this->countModules( 'user1' and 'user2' );
This is pretty much guaranteed to always return false regardless of the number of Modules enabled in either position, so check what you are passing to countModules carefully.
You must have exactly one space character separating each item in the string. For example, 'user1+user2' will not produce the desired result as there must be a space character either side of the '+' sign. Also, 'user1 + user2' will produce a PHP error message as there is more than one space separating each element.
Example: The user1 and user2 Module positions are to be displayed in the region, but you want the region to not appear at all if no Modules are enabled in either position.
<?php if ($this->countModules( 'user1 or user2' )) : ?> <div class="rightcolumn"> <jdoc:include type="modules" name="user1" style="xhtml" /> <jdoc:include type="modules" name="user2" style="xhtml" /> </div> <?php endif; ?>
Example: The user1 and user2 Module positions are to be displayed side-by-side with a separator between them. However, if only one of the Module positions has any Modules enabled then the separator is not needed. Furthermore, if neither user1 or user2 has any Modules enabled then nothing is output.
<?php if ($this->countModules( 'user1 or user2' )) : ?> <div class="user1user2"> <?php if ($this->countModules( 'user1' )) : ?> <jdoc:include type="modules" name="user1" style="xhtml" /> <?php endif; ?> <?php if ($this->countModules( 'user1 and user2' )) : ?> <div class="greyline"></div> <?php endif; ?> <?php if ($this->countModules( 'user2' )) : ?> <jdoc:include type="modules" name="user2" style="xhtml" /> <?php endif; ?> </div> <?php endif; ?>
Notice how the first countModules call determines if there any Modules to display at all. The second determines if there are any in the 'user1' position and if there are it displays them. The third call determines if both user1 and user2 positions have any Modules enabled and if they do then if provides a separator between them. Finally, the fourth call determines if there are any enabled Modules in the 'user2' position and displays them if there are any.
Collapsing columns
A common requirement when designing web pages in Joomla! is for a Module position to be removed when no Modules are enabled in that position so that the space is available for other page elements. The region removed is referred to as a "collapsed column". This can be achieved using the countModules function.
For example, if you want to include a "user1" module position only if there are modules enabled in that position, then you could use this code:
<?php if ($this->countModules( 'user1' )) : ?> <div class="user1"> <jdoc:include type="modules" name="user1" style="xhtml" /> </div> <?php endif; ?>
Notice that the <jdoc:include /> tag and its surrounding <div> are only included if the countModules call returns a non-zero value (the PHP if statement treats zero as being false and any non-zero value as being true.
Sometimes you may want a pair of module positions to collapse either singly or together.
<?php if ($this->countModules( 'user1 or user2' )) : ?> <div class="user1user2"> <?php if ($this->countModules( 'user1' )) : ?> <jdoc:include type="modules" name="user1" style="xhtml" /> <?php endif; ?> <?php if ($this->countModules( 'user2' )) : ?> <jdoc:include type="modules" name="user2" style="xhtml" /> <?php endif; ?> </div> <?php endif; ?>
Notice how the region (which is styled by the CSS class "user1user2") is only output if either 'user1' or 'user2', or both, have at least one Module enabled.
If you want a divider to separate the two Module positions then you must be careful to only output the divider if both Module positions have Modules enabled in them. For example,
<?php if ($this->countModules( 'user1 or user2' )) : ?> <div class="user1user2"> <?php if ($this->countModules( 'user1' )) : ?> <jdoc:include type="modules" name="user1" style="xhtml" /> <?php endif; ?> <?php if ($this->countModules( 'user1 and user2' )) : ?> <div class="divider"></div> <?php endif; ?> <?php if ($this->countModules( 'user2' )) : ?> <jdoc:include type="modules" name="user2" style="xhtml" /> <?php endif; ?> </div> <?php endif; ?>
See also
- PHP if statement
- PHP else statement
- PHP elseif statement
- Alternative syntax for PHP control structures
- Operators for use with the countModules function
Creating rounded corners
Introduction
Using (X)HTML and CSS, it is easy to place rectangular borders around parts of a web page. Usually, this is done by incorporating the code for that part of the web page in <div>...</div> tags (or similar), and then applying a border to the <div> using the CSS border property.
However, it is not currently possible to create a non-rectangular border using just (X)HTML and CSS. (Non-rectangular borders are referred to in this tutorial as 'rounded corners', since that is the most common implementation. However, the corners do not need to be rounded - this technique can be used to create many different shapes and styles of border.)
In order to overcome the limitations of CSS borders, we can use one or more images to provide the border appearance. This is very easy to do when you know how big your <div> is going to be - you simply create an image of the correct size showing the border you want and set it as the background for your <div>. In general, however, we will want web page elements to change their dimensions (width, height, or both) according to their contents. This is particularly true for template designers using a CMS such as Joomla! - if we don't know what content is going to be placed on the page, we can't possibly know what size the <div> needs to be! The main focus of this tutorial is therefore on creating rounded corners that are 'fluid' - that is, that can resize to accommodate different sizes of contents.
The theory
In order to avoid the problems described above, we use separate images to provide the four corners of our rounded box. In order to ensure that the border of our rounded box is always continuous, these images need to be big enough to completely fill our container at the maximum permissible size. To provide our rounded box at sizes below this maximum, we place the images into four layers, overlapping one another, to create the illusion that the border of our rounded box is unbroken.
The process is illustrated by the animation shown on the right. At each stage, an image is applied on top of the last.- Firstly, a large image containing the left-hand and top borders, and the top-left rounded corner, is placed at the top left corner of our container
<div>(outlined in red for clarity). The image is bigger than required for this container, and so extends beyond the bottom and right-hand edges; this is shown in partial transparency in the animation to illustrate the process, but would not be seen in practice. - Next, a thin horizontal image containing the bottom border and lower-left rounded corner is placed at the bottom left corner of the the container
<div>, on top of the first image. This image has been given a thin border in a darker pink to illustrate the process; this border would not normally be used. Again, it can be seen that the image extends beyond the right-hand edge of the container. - Thirdly, a thin vertical image containing the right-hand border and top-right rounded corner is placed at the top right corner of the container, on top of the second image.
- Finally, a square image containing just the lower-right rounded corner is placed at the lower-right corner of the container.
Putting it into practice - creating the images
To create the images we need, we will use the open source vector drawing program Inkscape. Unlike raster drawing software (such as the GIMP), which deals in individual pixels, vector drawing software works with geometric shapes. This makes it much easier to create rounded corners having exactly the geometry we want.
We will use Inkscape to create a simple rounded box with a pink foreground and a white background, and a drop shadow at the lower right corner. This is the same box as shown in the animation above.
- To begin, we need to decide what maximum size we want for our box. As stated above, this will determine the size of image we need to use. Since a larger image will have a greater file size, and hence cause a website to load more slowly, there is no point making the image any bigger than is necessary. For the purposes of this example, we will use a maximum size of 800px wide by 600px high. We therefore create a new document in Inkscape having these dimensions. The document dimensions are set by choosing
File > Document propertiesfrom the menu, and entering the correct values in the dialogue box that appears. Also in this dialogue box, we need to change the default background from transparent to white (ffffffff). After closing the dialogue, we will change the name of the default layer (go toLayer > Rename layer...) to "Rounded rectangle". - We will leave a 5px margin around our image. In addition, we will need 5px width of shadow on the right-hand and bottom edges of our rounded box. We therefore need to draw a rounded rectangle with a width of 785px and a height of 585px. We will also give the rounded corners a 5px radius. To do this, select the rectangle tool from the toolbar at the left, and left-click-drag anywhere within the blank document. The exact size of rectangle you draw doesn't matter at this stage. Then go to the options toolbar and enter the following settings: W:783.00 H:583.00 Rx:5.000 Ry:5.000 px. Why the difference in sizes? These dimensions don't include the width of the border. We are going to give our rectangle a 2px border, which will therefore increase each dimension by 2px.
- Next, we need to adjust that border, and set the colour of our rectangle. With the rectangle selected, go to Object > Fill and Stroke on the menu bar, or press Shift + Ctrl + F, to bring up the Fill and Stroke dialogue. Under the 'Fill' tab, choose the colour of the foreground fill. We are going to use the hex colour #ffd7eb, so enter 'ffd7ebff' in the RGBA input. (The final 'ff' hex value sets the alpha transparency level for the fill - in our case, completely opaque). Under the 'Stroke paint' tab we set the border colour. In our case, we want a black border, so we can leave the RGBA input at the default value of 000000ff. Finally, under the 'stroke style' tab, we set the stroke (border) width to be 2.000px.
- We now have our rectangle, but it is not in the correct location. Inkscape measures positions from the lower left corner of the document, so we need to place our rectangle at x=5 and y=10. Choose the selection tool from the toolbar at the left, left-click anywhere inside the rounded rectangle, and enter the position values in the options toolbar.
- Next, we need to create a new layer to hold the drop shadow. First, hide the original layer ("Rounded rectangle") by clicking the eye icon at the bottom of the main window. Then, go to Layer > Add Layer... on the menu bar, and create a new layer named "Shadow" below the current layer. We can then create another rounded box in this layer. Make this one 785px wide, 585px high, with corner radii of 5px, and located at x=10 and y=5. In the 'Fill and Stroke dialogue, set the fill RGBA to '00000081', and the blur slider to '0.6'. On the 'stroke paint' tab, select the 'no paint' icon. This will produce a rounded rectangle in a grey colour, partially transparent, and with slightly blurred edges.
- Finally, unhide the "Rounded rectangle" layer, by selecting it in the drop-down box at the bottom of the main window, and left-clicking the eye icon again. Congratulations - you can now see how your rounded box will look! You may want to save your image at this point as an Inkscape .svg file so that you can easily come back and change it later.
- Now we need to create the four separate images we will need, and save them in a web format. Inkscape also makes this very easy - simply go to File > Export Bitmap... and select the 'Custom' button in the dialogue box. We can export the relevant parts of our image by entering the coordinates in the x0/y0 and x1/y1 boxes. Remember that all coordinates are measured from the bottom left corner! The best place to cut the image is just before the curve of the rounded corners on the bottom and right-hand edges. We might therefore use the following settings:
| Image | x0 | x1 | y0 | y1 |
|---|---|---|---|---|
| Top left | 0 | 785 | 15 | 600 |
| Top right | 785 | 800 | 15 | 600 |
| Bottom left | 0 | 785 | 0 | 15 |
| Bottom right | 785 | 800 | 0 | 15 |
The resulting images are as follows:
|
|
| |
|
Putting it into practice - Editing your template
To implement the rounded corners on a Module, we will use the 'rounded' Module chrome, by including the following <jdoc /> statement:
<jdoc:include type="modules" name="POSITION" style="rounded" />This creates the following code in the final web page:
<div class="module_menu"> <div> <div> <div> <h3>Main Menu</h3> <ul class="menu"> <li><!-- various menu items --></li> </ul> </div> </div> </div> </div>
The four nested <div>s give us the layers we need for our four images. We can use the class name of the outer <div> to ensure that we really are dealing with the correct <div>s, and then use the nesting relationship to apply the correct image to each layer.
This is most easily done using an external style sheet, having the following rules:
div.module_menu { background: url(../images/rounded_topleft.png) 0 0 no-repeat; padding: 0; } div.module_menu div { background: url(../images/rounded_bottomleft.png) 0 100% no-repeat; margin: 0; border: 0; } div.module_menu div div{ background: url(../images/rounded_topright.png) 100% 0 no-repeat; } div.module_menu div div div { background: url(../images/rounded_bottomright.png) 100% 100% no-repeat; }
The padding, margin and border are needed to ensure that each <div> completely fills the one above. The image URLs are specified relative to the CSS file location.
There is just one more slight problem to be aware of: CSS rules are not exhausted, they are only overridden. This means that, if our Module content includes a <div>, these CSS rules will also apply to that <div> (since it is a <div> inside a <div> inside a <div> inside a <div class="module_menu"> - regardless of the fact that there was an intervening <div>). To overcome this, we need to add another, more specific rule:
div.module_menu div div div div{ background: none; }
And that's it - happy coding!
What is Module chrome?
Module chrome allows template designers to have a certain amount of control over the way the output from a Module is displayed in their template. Essentially, it consists of a small amount of predefined HTML which is inserted before, after, or around the output from each module, and which can then be styled using CSS. Module chrome is commonly used to provide borders around modules, especially with rounded corners, but it can be used for much more than that.
Module chrome is determined by using the 'style' attribute in the statement calling the module. For example, the following statement may be used in the index.php file of a template to insert the Modules in the 'user1' position and apply the 'custom' Module chrome:
<jdoc:include type="modules" name="user1" style="custom" />
It can be seen that the same Module chrome is applied to every Module in that position - in other words, if you want to have two Modules in a column, but want them to have different Module chrome, then they would need to be set up as two different 'positions' (e.g. 'user1' and 'user2').
The standard Joomla! 1.5+ package includes six default Module chrome styles. However, the flexibility of the template system means that you are not limited to these styles - it's very easy to create as many new styles as you want!
Note that this example includes class additions because the examples are taken using mod_mainmenu. The suffix "_menu" to the div class and the "menu" class on the ul tag are not present with other modules.
| Style | Output | Appearance |
|---|---|---|
| rounded (default for menus on milkyway) |
<div class="module_menu"> <div> <div> <div> <h3>Main Menu</h3> <ul class="menu"> <li><!-- various menu items --></li> </ul> </div> </div> </div> </div> |
|
| none |
<ul class="menu"> <li><!-- various menu items --></li> </ul> |
|
| table |
<table cellpadding="0" cellspacing="0" class="moduletable_menu"> <tr> <th valign="top">Main Menu</th> </tr> <tr> <td> <ul class="menu"> <li><!-- various menu items --></li> </ul> </td> </tr> </table> |
|
| horz |
<table cellspacing="1" cellpadding="0" border="0" width="100%"> <tr> <td valign="top"> <table cellpadding="0" cellspacing="0" class="moduletable_menu"> <tr> <th valign="top">Main Menu</th> </tr> <tr> <td> <ul class="menu"> <li><!-- various menu items --></li> </ul> </td> </tr> </table> </td> </tr> </table> |
|
| xhtml |
<div class="moduletable_menu"> <h3>Main Menu</h3> <ul class="menu"> <li><!-- various menu items --></li> </ul> </div> |
|
| outline |
<div class="mod-preview"> <div class="mod-preview-info">left[outline]</div> <div class="mod-preview-wrapper"> <ul class="menu"> <li><!-- various menu items --></li> </ul> </div> </div> |
Note that the Module chrome doesn't necessarily change the appearance all that much - this depends on the CSS used in the template. For example, the 'none' and 'horz' chromes look very similar, although the underlying HTML code is very different.
Applying custom Module chrome
To define custom Module chrome in your template you need to create a file called modules.php in your template html directory. For example, this might be PATH_TO_JOOMLA/templates/TEMPLATE_NAME/html/modules.php.
In this file you should define a function called modChrome_STYLE where 'STYLE' is the name of your custom Module chrome. This function will take three arguments, $module, &$params, and &$attribs, as shown:
<?php function modChrome_STYLE( $module, &$params, &$attribs ) { /* chromed Module output goes here */ } ?>
Within this function you can make use of any of the available Module properties (i.e. the fields in the jos_modules table in the Joomla! database on your server) for that Module, but the main ones you are likely to need are $module->content, $module->showtitle and $module->title. $module->showtitle is a Boolean variable, so is either true (when the Module title should be shown) or false (when it shouldn't be shown). $module->content and $module->title will return the main Module content and the Module title respectively.
The function is a normal PHP function and so can use any regular PHP code. One common example is to use an if statement to check the value of $module->showtitle, and then include the title or not accordingly:
<?php if ($module->showtitle) { echo '<h2>' .$module->title .'</h2>'; } ?>
The Module parameters are accessed using the $params object. For example, it is possible to assign a Module class suffix to a Module in the backend of your Joomla! site; this is then stored in the parameters for that Module as moduleclass_sfx. To create a <div> with a class determined by the Module class suffix, you would use:
<div class="<?php echo $params->get( 'moduleclass_sfx' ); ?>"> <!-- div contents --> </div>
Custom chrome attributes
It is also possible to pass further attributes into the Module chrome function using the same <jdoc:include /> statement that sets the Module chrome. These additional attributes can be anything you like, and are stored in the $attribs array. Take the following example Module chrome function:
<?php function modChrome_custom( $module, &$params, &$attribs ) { if (isset( $attribs['headerLevel'] )) { $headerLevel = $attribs['headerLevel']; } else { $headerLevel = 3; } if (isset( $attribs['background'] )) { $background = $attribs['background']; } else { $background = 'blue'; } echo '<div class="' .$params->get( 'moduleclass_sfx' ) .'" >'; if ($module->showtitle) { echo '<h' .$headerLevel .'>' .$module->title .'</h' .$headerLevel .'>'; } echo '<div class="' .$background .'">'; echo $module->content; echo '</div>'; echo '</div>'; } ?>
You would then set the values for background and headerLevel in the <jdoc:include /> statement as shown below. If no values are set, the attributes default to 'blue' and '3' respectively.
<jdoc:include /> statement
|
Output |
|---|---|
|
|
<div> <h3><!-- Module title --></h3> <div class="blue"> <!-- Module content --> </div> </div> |
|
|
<div> <h3><!-- Module title --></h3> <div class="green"> <!-- Module content --> </div> </div> |
|
|
<div> <h1><!-- Module title --></h1> <div class="yellow"> <!-- Module content --> </div> </div> |
Further information about passing attributes to Module chrome can be found in jtopic:115953.















