Tutorial: A Horizontal jQuery Accordion using Custom Event Binding

January 21, 2010
Development

This is the second of two posts about a site we recently launched for a nonprofit called Striving for More. This is a great new organization that has started with the goal of improving overall care for young cancer patients. It was a fun and deserving project with a great design:

Striving for More

The first post was about how to build a custom jQuery Slideshow on top of some popular jQuery plugins. It covered the required html, css and js needed and it also gave a look into the through process that went into it. This post will take the same approach, but instead look at the custom jQuery horizontal accordion that is on the homepage. Unlike the last project, this one will be build without the help of any plugins. It was created entirely from scratch using jQuery custom events.

Here is a look at the final product.

Horizontal jQuery Accordion

What is it?

Essentially, this is a horizontal accordion that also changes the text below it on each transition. The one difference between this and a traditional accordion is that when a slide becomes active or inactive, it doesn't just shrink. Instead it slides behind the other slides. Click around on it above to see what I mean.

The HTML

The html for this project is really straightforward. There is a group of slides and a group of content:

<div id="slideshow">
	<ol id="slides">
		<li class="slide open" id="slide-[SLIDE NUMBER]">
			<a href="[SLIDE LINK]">
				<img src="[SLIDE IMAGE]" alt="" />
			</a>
			<a class="slidebutton" href="javascript:void(0);"><img src="[SLIDE BUTTON]" alt="" /></a>
		</li>
		...
	</ol>
	<ol id="slidecontents">
		<li class="slidecontent" id="content-slide-[SLIDE NUMBER]">
			[SLIDE CONTENT]
		</li>
		...
	</ol>
</div>

The html is in two logical groups, the slide images/navigation and the slide content. These are paired by convention using programatic id attributes. Notice that each of the image slides and the content slides has an id with the [SLIDE NUMBER] in it. This makes the javascript much simpler.

Ideally the content would be grouped in with the images. This would have made for better markup, as the related items would be put together. This would have required a lot of css positioning trickery since the content wasn't supposed to move with the slide. For the sake of overall simplicity, some html complexity was added.

The last thing worth noting here is that the slide button text is actually a rendered image. That text can't be rotated reliably in all browsers is really sad. I am confident that this will be accomplished shortly with Canvas. If you're wondering which browsers are the offenders here, you'll probably be surprised. Text can be rotated reliably in EVERY major browser except for Firefox 2.0. It even works in IE. Alas.

The CSS

The CSS for this project is probably the simplest part. You'll only notice one little quirk:

#slides { position: absolute; top: 21px; left: 22px; width: 577px; height: 285px; overflow: hidden; }
#slides .slide { position: absolute; top: 0px; width: 541px; }
#slides .slide img { position: absolute; top: 0px; left: 0px; }
#slides .slide .slidebutton { display: block; position: absolute; top: 0px; right: 0px; height: 285px; width: 21px; background: #693d5e; text-decoration: none; border-right: 1px solid white; }
#slides .active .slidebutton { background: #55354a; }
#slides .slide .slidebutton img { position: absolute; top: auto; display: block; bottom: 5px; left: 5px; }
/* Manually place slides to begin */
#slide-1 { position: absolute; top: 0; right: 44px; z-index: 3; }
#slide-2 { position: absolute; top: 0; right: 22px; z-index: 2; }
#slide-3 { position: absolute; top: 0; right: 0px; z-index: 1; }

It's all straighforward except for that bit at the bottom. I've hardcoded the number of slides. This is typically something I like to avoid, but there were a couple of reasons for it on this project:

  1. I was assured that these slides would be unchanged for quite some time.
  2. At least some CSS Positioning would be required so the page wouldn't look broken while the JS loaded. By being exact there would be no jarring readjustment at all.
  3. It was easier.

These reasons made the decision obvious. As a quick aside and bonus, here is the css that could be used to rotate the text in CSS. Note that this depends on slightly different markup and it just won't work in Firefox 2.0. So remeber that until FFox2.0 disappears this is useless, though fascinating.

#slides .slide .slidebutton span 
{ display: block; text-align: right; -webkit-transform: rotate(90deg);  -moz-transform: rotate(90deg);
						position: absolute; bottom: 100px; left: -90px; width: 200px;
						color: #f2ecd0; font-family: verdana, sans-serif; font-size: 12px; }
/* Put the following in your IE stylesheet */
#slides .slide .slidebutton span { bottom: 190px; left:2px; filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=1); }

The jQuery

The jQuery for this little project was the most fun. It ended up being very compact due to the intial positioning of the slides using CSS and because I used custom events.

What are custom events? In short, they are functions that you can bind to jQuery selectors. This lets you associate actions with the objects that do them, rather than with what triggers them. As an example, you could bind the event 'toggle' to a lightbulb and then trigger it from any number of switches. So if you have multiple switches, they all don't need to keep track of the lightbulb's state. They can just call toggle and the bulb will handle it. (If you like this example, don't credit me. It's borrowed from a great article on jQuery Custom Events written by Rebecca Murphey).

So here is the entirety of the jQuery code:

/* Binding Events to the slides */
$('.slide')
	.bind('open', function(){
		if(! $(this).hasClass('open')){
			$(this).next().trigger('open');
			$(this).addClass('open');
			$(this).animate({right: "-=511px"});
		}
		else{
			$(this).prev().trigger('close');
		}
		$(this).siblings().removeClass('active');
		$(this).addClass('active');
	})
	.bind('close', function(){
		if($(this).hasClass('open')){
			$(this).removeClass('open');
			$(this).animate({right: "+=511px"});
			$(this).prev().trigger('close');
		}
	});
		
/* Binding Events to the Slide Contents */
$('.slidecontent').bind('show', function(){
	$('.slidecontent').removeClass('open');
	$(this).addClass('open');
});

/* Triggering from the buttons */
$('.slidebutton').click(function(){
	$(this).parent().trigger('open');
	$('#content-' + $(this).parent().attr('id')).trigger('show');
});

This is certainly terse, so it is worth explaining. I'll start with the events we are binding to '.slide', open and close. To set the stage, remember that each slide is either open (navigation on the right), or closed (navigation on the left). I am keeping track of this by applying an 'open' or 'closed' class to each slide. This is a fairly standard technique.

Where things might get a bit tricky lie within how an accordion works. The visibile slide isn't the only slide that is open. The slides that come after it must be open as well (their navigation is on the right). So when the second slide is active, the first must be closed and the second and third must be open. This problem gets handled with a little big of recursion.

The Event Binding

Let's look at the close event, as it is the simpler of the two. When we close a slide, we first check that it is open. If it is closed, there is nothing to do. If it is open, we close it and then close the slide immediately to its left. This repeats until there isn't a slide to the left or it reaches a slide that has already been closed.

The open event is only a little more complicated. It works in the exact same way as the close event, except trickles to the right. It also updates which slide is active at each stop along the way.

Finally you can see how the 'show' event is bound to the slide contents. All this does is remove an open class from every slide content, and then add it back to the one that has been triggered.

As you can see, a lot of complexity has been encapsulated under these events. This makes it really easy to jump to a particular slide. All that needs to happen is to trigger the 'open' event on the desired slide and the 'show' event on the correct content. This is exactly what the '.slidebutton' code does.

Putting it all together

With all of the pieces in place you can see it in action below. Be sure to leave any questions or comments you have about this in our comments below.

jQuery Horizontal Accordion

Comments

rahul's avatar
rahul
hi,
Actully i want dynamic accordion which gives me "Domain name" as parent Div and on click of that realted
Customers should be displayed in child Div. if it is possible then pls give me the solution in ASP.NET MVC 4 .... or just tell me the logic..... thanks for help....
CSS sites's avatar
CSS sites
Great example of jquery, I like it very well.
VeronicaC's avatar
VeronicaC
Awesome looking slider, exactly what I'm looking for. However I am using Squarespace and am having trouble incorporating it into my site. I have determined that everything other than the jQuery code is working. (The animations & selection of sections)

Any suggestions/help?
jcwenzeldesign's avatar
jcwenzeldesign
Great stuff. Really helped me out of a jam. Easy to use and modify for personal use.

I have one question: I have been trying to develop a lower navigation which will also control the "accordion". Any ideas on how to make a lower link control the slide show as well?
Dejan Prole's avatar
Dejan Prole
Hi,
Could someone please send me complete code, my email is dprole[at]gmail.com. I have some trouble with this accordion - nothing happens when I point mouse on links on right side.
Thank you,
Dejan
ale_mar's avatar
ale_mar
Hi Joel! Thanks for your reply.
I was thinking about jQuery Cycle, too... which is very useful. But how can I use it in your accordion?
Is it enough what follows?

jQuery(document).ready(function($) {
$("#slideshow").css("overflow", "hidden");
$("#slides").cycle({
fx: 'fade',
speed:900,
timeout:3000,

});
});
/code>

I'll try and let you know. Thanks anyway, bye!
Joel Sutherland's avatar
Joel Sutherland NMC team member
ale_mar

Making this autoplay would just require more javascript. That said, it might make more sense to use something designed for that from the beginning. I like jQuery Cycle: http://jquery.malsup.com/cycle/
ale_mar's avatar
ale_mar
Hi guys!, How ya doing?
Let me ask... is there a way to apply an autoplay to this slideshow? Active triggers still running, too...
Thanks in advance! ;-)
Hawaii Web Design's avatar
Hawaii Web Design
Effective slideshow and simple to use. I look forward to more tutorials in the future. Thanks!
Andrew S. Roberts's avatar
Andrew S. Roberts
I used this on our new Office of Campus Visits homepage. Great work, thanks! http://visit.iupui.edu/
aaron b's avatar
aaron b

i agree with eric - do you know of a way that we could have the slides automatically scroll every few seconds?

trying to figure it out and can't quite get it.

thanks!

Wayne's avatar
Wayne

Hi Joel,

Nice accordian, is there a way to have the links on the left of the image?

Cheers

Wayne.

Eric C's avatar
Eric C

hey I realize this is an old tut, but is there any way to make the slides scroll automatically? with maybe a 4 or 5 second delay ?

Joel Sutherland's avatar
Joel Sutherland NMC team member

Jason,

You will need to add the code I provided after all of the code from this post. Basically it runs after the slides have been set up.

Jason  Hoare's avatar
Jason Hoare

Hi Joel,

I found this in you jquery code and assume it is what you are refering to above:

$('#startslides').click(function(){
$('#gallery-slides').fadeIn();
$('#gallery-intro').fadeOut();
$('#slide-1-nav').trigger('click');
});

I applied #slide-1-nav like so:

<a href="javascript:void(0);" id="slide-1-nav" class="slidebutton"><img alt="" src="/files/images/how-to-help/how-to-help.gif"/></a>

However it doesn't seem to do anything? Am I doing something wrong? Sorry to be a pain!

Joel Sutherland's avatar
Joel Sutherland NMC team member

Jason,

Just give the button of the slide you want to start with a unique id. Then you can say:

$("#uniqueid").trigger("click");

Jason  Hoare's avatar
Jason Hoare

Hi Joel,

Really appreciate you replying to my post. Unfortunately I'm a bit of a Jquery noob and am not sure what to do!

Where should I put this in your accordion jquery?
is the [SLIDE BUTTON TO OPEN] meant to be any class I like (to apply in the html)?

Again, really appreciate you help!

Joel Sutherland's avatar
Joel Sutherland NMC team member

Jason,

It is possible. Since we set up the events the way we did, you would just need to do the following in jQuery:

$("[SLIDE BUTTON TO OPEN]").trigger("click");

Jason  Hoare's avatar
Jason Hoare

Very nice and smooth compaired to all the other jumpy and jittery horizontal accordians out there! One question, is it possible to have one particular slide open initially?

website bouwen's avatar
website bouwen

Very well done and well explained! I'm not very sure about it though... I believe it's a bit hard to read what's on the tabs...

Leave a comment