As part of the last fabrikar.com site redesign we created an article slider widget for our Joomla homepage. In this blog post I'm going to go over how I created this.
My aim was to use Joomla's front page layout as the basis for the content and use Joomla's template override features to alter the mark up and add javascript to produce the slider features.
When doing this type of work the first thing I do is to create a design in Photoshop showing what the final layout should look like. Below is the image I used as a base for my design:

Immediately there's a couple of issues in this design that I know I'm going to need to address in my markup. First of all the background is going to need to be split into three sections, the top and bottom curved edges and the center background. I initially did this so that the content area could expand to take in mixed height content, but in the end this feature was not implemented. The other thing that is going to require some fiddly styling is the active selection's overlapping button.
So with my design in mind the next thing to do is to make a copy of the beez template override files in my own template folder. My template is called "fabrik" , so I copied /templates/beez/html/com_content to /templates/fabrik/html/com_content. Now when my site renders any com_content page, it will use my fabrik template's com_content tmpl files rather than the standard Joomla com_content markup. This markup is of a better and clearer quality that the standard com_content markup and will facilitate our javascript coding
I then wanted to simplfy further the front page markup, so I replaced /templates/fabrik/html/com_content/frontpage/default.php with this code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
|
<?php // @version $Id: default.php 10498 2008-07-04 00:05:36Z ian $ defined('_JEXEC') or die('Restricted access'); ?> <?php if ($this->params->get('show_page_title',1)) : ?> <h1 class="componentheading"> </h1>
<?php $i = $this->pagination->limitstart; $rowcount = $this->params->def('num_leading_articles', 1); ?> <div id="content-slider"> <div class="content-slider-top"></div> <div class="content-slider-middle"> <ul class="content-slider-menu"> <?php for ($y = 0; $y < $rowcount && $i < $this->total; $y++, $i++) : $this->item =& $this->getItem($i, $this->params); ?> <li> <h2 class="contentheading"> </h2> </li>
</ul> <div class="content-slider-container"> <div> <div class="blog"> <?php $i = $this->pagination->limitstart; for ($y = 0; $y < $rowcount && $i < $this->total; $y++, $i++) : ?> <div id="content-slider-" class="leading"> <?php $this->item =& $this->getItem($i, $this->params); echo $this->loadTemplate('item'); ?> </div>
</div> </div> </div> </div> <div class="content-slider-bottom" /></div
|
Note I've put all of the front page article's headings in their own <ul> - this will be styled later to make the slider menu
Then I replace /templates/fabrik/html/com_content/frontpage/default_items.php with this code (basically here all I've changed from the beez template is to remove the headings from the content, as these are already rendered in the default.php file) :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113
|
<?php // @version $Id: default_item.php 11386 2009-01-04 02:34:35Z ian $ defined('_JEXEC') or die('Restricted access'); ?> <?php if ($this->user->authorize('com_content', 'edit', 'content', 'all') || $this->user->authorize('com_content', 'edit', 'content', 'own')) : ?> <div class="contentpaneopen_edit"> <?php echo JHTML::_('icon.edit', $this->item, $this->item->params, $this->access); ?> </div>
<?php if (!$this->item->params->get('show_intro')) : echo $this->item->event->afterDisplayTitle; endif; ?> <?php if ($this->item->params->get('show_pdf_icon') || $this->item->params->get('show_print_icon') || $this->item->params->get('show_email_icon')) : ?> <p class="buttonheading"> <img src="/templates//images/trans.gif" alt="<?php echo JText::_('attention open in a new window'); ?>" /> <?php if ($this->item->params->get('show_pdf_icon')) : echo JHTML::_('icon.pdf', $this->item, $this->item->params, $this->access); endif; if ($this->item->params->get('show_print_icon')) : echo JHTML::_('icon.print_popup', $this->item, $this->item->params, $this->access); endif; if ($this->item->params->get('show_email_icon')) : echo JHTML::_('icon.email', $this->item, $this->item->params, $this->access); endif; ?> </p>
<?php if (($this->item->params->get('show_section') && $this->item->sectionid) || ($this->item->params->get('show_category') && $this->item->catid)) : ?> <p class="pageinfo">
<span>
<?php echo '<a href="'.JRoute::_(ContentHelperRoute::getSectionRoute($this->item->sectionid)).'">'; ?>
<?php if ($this->item->params->get('link_section')) : ?> <?php echo '</a>'; ?>
<?php if ($this->item->params->get('show_category')) : ?> <?php echo ' - '; ?>
</span>
<?php if ($this->item->params->get('show_category') && $this->item->catid) : ?> <span>
<?php echo '<a href="'.JRoute::_(ContentHelperRoute::getCategoryRoute($this->item->catslug, $this->item->sectionid)).'">'; ?>
<?php if ($this->item->params->get('link_category')) : ?> <?php echo '</a>'; ?>
</span>
</p>
<?php if ((intval($this->item->modified) !=0 && $this->item->params->get('show_modify_date')) || ($this->item->params->get('show_author') && ($this->item->author != "")) || ($this->item->params->get('show_create_date'))) : ?> <p class="iteminfo">
<span class="modifydate">
</span>
<?php if (($this->item->params->get('show_author')) && ($this->item->author != "")) : ?> <span class="createdby">
</span>
<?php if ($this->item->params->get('show_create_date')) : ?> <span class="createdate"> <?php echo JHTML::_('date', $this->item->created, JText::_('DATE_FORMAT_LC2')); ?> </span>
</p>
<?php if ($this->item->params->get('show_url') && $this->item->urls) : ?> <span class="small"> <a href="/" target="_blank"> </a> </span>
<?php if (isset ($this->item->toc)) : echo $this->item->toc; endif; ?> <?php echo JFilterOutput::ampReplace($this->item->text); ?> <?php if ($this->item->params->get('show_readmore') && $this->item->readmore) : ?> <p> <a href="/" class="readon"> <?php if ($this->item->readmore_register) : echo JText::_('Register to read more...'); elseif ($readmore = $this->item->params->get('readmore')) : echo $readmore; else : echo JText::sprintf('Read more', $this->item->title); endif; ?></a> </p>
<?php echo $this->item->event->afterDisplayContent;
|
If we view our home page now we will see a <ul> of each of the front page's article headings and underneath their content.
Next I wanted to add a CSS and Javascript file to the template. To keep things tidy I'll make a new css file, called content-slider.css and store it in /templates/fabrik/css and a new javascript file content-slider.js and store that in /templates/fabrik/js. To call these files from you main template index file,only when in the front page, edit templates/fabrik/index.php and add this:
1 2 3 4
|
<?php if (JRequest::getCmd( 'view' ) == 'frontpage' ){?> <link href="<?php echo JURI::base()."templates/".$this->template; ?>/css/content-slider.css" rel="stylesheet" type="text/css" /> <script type="text/javascript" src="<?php echo JURI::base()."templates/".$this->template; ?>/js/content-slider.js"></script> <?php }?>
|
This CSS file will be used to allow the sliding of the content behind a view port. Below is a picture of how I'd need to set up the CSS to achieve this:

To set the view port dimensions and hide the content that appears out side of those dimensions we'll add the following CSS declaration:
1 2 3 4 5
|
.content-slider-container { overflow: hidden; width: 626px; height: 244px; }
|
Here's the complete CSS file, the rest of the CSS is for formatting purposes:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110
|
#content-slider .content-slider-top { background: transparent url(../images/content-slider/top.png) no-repeat top left; height: 13px; } #content-slider .content-slider-bottom { background: transparent url(../images/content-slider/bottom.png) no-repeat top left; height: 13px; clear: left; } #content-slider .content-slider-middle { background: transparent url(../images/content-slider/middle.png) no-repeat top left; padding: 0 12px; height: 244px; position: relative; } #content-slider { margin-bottom: 20px; } #content-slider h2 { color: #333333; height: 21px; width: 200px; margin-left: -45px; padding-left: 36px; } #content-slider h2.active { color: #f711f7; } #content-slider h2:hover,#content-slider h2.active { background: transparent url(../images/content-slider/pointer.png) no-repeat top left; } #content-slider-0 { background: transparent url(../images/content-slider/bg/form.png) no-repeat top left; } #content-slider-1 { background: transparent url(../images/content-slider/bg/report.png) no-repeat top left; } #content-slider-2 { background: transparent url(../images/content-slider/bg/viz.png) no-repeat top left; } #content-slider-3 { background: transparent url(../images/content-slider/bg/extend.png) no-repeat top left; } .content-slider-container { overflow: hidden; width: 626px; height: 244px; } .content-slider-container ul,.content-slider-container .blog .leading ul { list-style: none !important; list-style-image: none; } .content-slider-container ul li,.content-slider-container .blog .leading ul li { background-image: none !important; } #content-slider ul li img{ padding-left:15px; } .leading { text-align: right; } .leading ul { padding-right: 35px; } .leading ul li { padding: 10px 0; } .content-slider-menu { top: 0; right: 0; list-style: none; list-style-image: none; position: absolute; z-index: 99; width: 160px; } /* ie7 only **/ *:first-child+html .content-slider-menu { width: 200px; }
|
Laying out the leading stories in a horizontal line will be done in the javascript code. We'll make a mootools class for the content slider. Below is the skeleton of this class:
1 2 3 4 5 6 7 8 9 10 11
|
var ContentSlider = new Class({ }); ContentSlider.implement(new Events); ContentSlider.implement(new Options); window.addEvent('domready', function(){ new ContentSlider(); });
|
the domready event at the end of this script will create a new instance of the content slider class.
The implement(new Events) and implement(new Options) says that our class will implement the features found in mootools Events and Options classes.
here's the full class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
|
var ContentSlider = new Class({ presets: { pagewidth:626, pageheight:244, padding:0, linklocation:'right' }, initialize: function(options) { this.presets = $merge(this.presets, options) this.setOptions(this.presets); this.pages = document.getElement('.blog').getElements('.leading'); var maxheight = 0; this.headings = document.getElement('.content-slider-menu').getElements('h2'); this.headings.each(function(h, i){ h.addEvent('click', this.doScroll.bindAsEventListener(this, [i])).setStyle('cursor','pointer'); }.bind(this)); this.pages.each(function(p, i){ p.setStyles({'width':this.options.pagewidth+'px','float':'left','padding':this.options.padding+'px','height':this.options.pageheight + 'px'}); if(p.getStyle('height').toInt() > maxheight){ maxheight = p.getStyle('height').toInt(); } }.bind(this)); this.headings[0].toggleClass('active'); this.container = document.getElement('.content-slider-container'); this.container.setStyles({'width':this.options.pagewidth+'px','height':maxheight+ (2 * this.options.padding)+'px'}); this.container.getElement('div').setStyles({'width':((this.options.pagewidth + (2 * this.options.padding)) * this.pages.length)+'px'}); this.fx = new Fx.Scroll(this.container); }, doScroll: function(e, x){ e = new Event(e).stop(); this.headings.each(function(h){h.removeClass('active')}); $(e.target).toggleClass('active'); this.fx.toElement(this.pages[x]); } }); ContentSlider.implement(new Events); ContentSlider.implement(new Options); window.addEvent('domready', function(){ new ContentSlider(); });
|
The initialize function is called when an instance of the ContentSlider class is created. When creating the new instance we could pass in a Javascrpt object to alter the set up parameters, as we haven't here the paramters are taken from the classes presets object. These parameters are stored in this.options
Once the object parameters have been set we can get the DOM objects for the headings and the cotent articles.:
13 14 15
|
this.pages = document.getElement('.blog').getElements('.leading'); var maxheight = 0; this.headings = document.getElement('.content-slider-menu').getElements('h2');
|
now we will iterate over the headings to add the onlclick events to them:
16 17 18
|
this.headings.each(function(h, i){ h.addEvent('click', this.doScroll.bindAsEventListener(this, [i])).setStyle('cursor','pointer'); }.bind(this));
|
For each heading we are calling an anonymous function on it. The variable 'h' refers to the heading you are iterating over, the "i" to the number of iterations the each function has gone over.
To ensure that in line 17 we can still reference the object with the keyword "this" we add the ".bind(this)" the the end of the function. If we didn't do that the variable "this" would apply to the dom heading element that you are applying the function to.
The next thing to note in this line is how you can chain function calls together in one line. First of all we are calling "addEvent" on the heading, and on the same line we are calling the "setStyle" function to edit the CSS applied to the element.
Finally the "this.doScroll.bindAsEventListener(this, [i])" function will call the ContentSlider class "doSroll" method passing in as variables the event and the variable i.
19 20 21 22 23 24
|
this.pages.each(function(p, i){ p.setStyles({'width':this.options.pagewidth+'px','float':'left','padding':this.options.padding+'px','height':this.options.pageheight + 'px'}); if(p.getStyle('height').toInt() > maxheight){ maxheight = p.getStyle('height').toInt(); } }.bind(this));
|
We'll now iterate over the pages and set some CSS styes based on the options we set with the presets object. By floating left the pages we achieve a horizontal scroll effect. Without this the pages would scroll vertically.
25 26 27 28 29
|
this.headings[0].toggleClass('active'); this.container = document.getElement('.content-slider-container'); this.container.setStyles({'width':this.options.pagewidth+'px','height':maxheight+ (2 * this.options.padding)+'px'}); this.container.getElement('div').setStyles({'width':((this.options.pagewidth + (2 * this.options.padding)) * this.pages.length)+'px'});
|
Now we'll just apply the active class to the first heading as this heading's content will be the active content when the page is loaded
Then we need to set up the view port dimensions. The view port is contained by the variable this.container, remember that in our CSS we set the content-slider-container's overflow setting to hidden. Between the view port and the articles we have a div which we set to the total width of the articles, this in conjunction with left floating the artcles ensures that the articles scroll horizontally.
30
|
this.fx = new Fx.Scroll(this.container);
|
Finally, for the initialize function, we create one mootools effect to scroll the content. This is used each time one of the headings is clicked on.
33 34 35 36 37 38
|
doScroll: function(e, x){ e = new Event(e).stop(); this.headings.each(function(h){h.removeClass('active')}); $(e.target).toggleClass('active'); this.fx.toElement(this.pages[x]); }
|
Remember we bound the heading click event the the doScroll function with the line "this.doScroll.bindAsEventListener(this, [i])"? Well above is the function that is called. The variable "e" represents the click event and the "x" is the page number.
First of all we should make a mootools event object from the standard event. This is done with the "e = new Event(e)". As we can then chain further functions on the same line we call the mootools event's "stop" method to stop the event propergating. In reality this means that the page wont jerk back up to the top when you click on the heading.
On line 35 we iterate over all the headings and remove the 'active' class, and then on line 36 we apply the active class to the clicked on heading.
Fianlly on line 37 we tell our object mootools effect to scroll the page into view, using the toElement() function.
{rokcomments}