Sam Croft

Full-stack developer

A super-lightweight mobile webkit accordion jQuery plugin, great for iOS/Android PhoneGap apps

  • 16 comments

Filed in: android, iOS, jQuery, PhoneGap, Uncategorized

Ahhh, accordions. Their use and acceptance in web design has been varied over the years, but right now I think we can safely say they are en vogue. Unfortunately, as developers (myself included), when we use an accordion we probably use the same old chunk of code and non-semantic tag-soup that we used last season. And the time before that.

For about a year I’ve been meaning to write my own lightweight accordion that we can use across the board on our various projects at RITH. And at the moment, that largely entails creating reusable webkit optimised code for use on iOS and Android mobile devices deployed as native apps using PhoneGap.

The problem(s) with accordions

Your basic accordion will consist of a series of hidden content elements and some corresponding anchor tags that when clicked will reveal the aforementioned content. In essence the markup you’d associate with this should be pretty simple, but often the plugins you find will greatly overcomplicate things. jQuery ui library, I’m looking at you my old friend.

I think this just comes from a bit of laziness in failure at not updating code over years of iterations. That’s cool, maybe it only bothers sematic’ists! But for me, the problems are:

  • Non-semantic markup used for the accordion foundation
  • Lots of unnecessary ID and class attributes
  • CSS specificity bloat
  • Reliance on Javascript for transition effects

While the first three points are arguably down to preference, the final point is important when considering performance on mobile devices. Use of pure Javascript will work of course, but Javascript animations are not hardware accelerated. Using a pure Javascript driven accordion on an iPhone 3G, or even the more powerful iPad, will yield passable—but far from perfect results.

And what about ‘pure’ CSS accordions?

Well, my opinion on this may divide some. There are some interesting techniques out there for pure CSS accordions, but I’m of the belief that this is moving too much behaviour into CSS and away from Javascript. I still very much like to work with three very different areas in projects; markup, style and behaviour.

These solutions are also more reliant on additional elements, extra attributes and more lines of CSS. But most notedly, is the ultimate lack of control of the accordion and confined bounds for future extensibility.

Breaking down an accordion to its core

Right, all one needs for an accordion is a simple ordered, unordered or definition list (actually, maybe it should always be a definition list?), an anchor tag and some content. Markup done.

Following up a comment by my friend Simon Richardson and my own initial thoughts, I’ve revisited this plugin and the base markup consists of just a definition list—making it as semantic as possible.

The foundation accordion markup

<dl>
	<dt><a href="#">Knight Rider</a></dt>
	<dd>
		<p>Knight Rider, a shadowy flight into the dangerous world of a man who does not exist. Michael Knight, a young loner on a crusade to champion the cause of the innocent, the helpless in a world of criminals who operate above the law.</p>
	</dd>
	<dt><a href="#">Thundercats</a></dt>
	<dd>
		<p>Thunder, thunder, thundercats, Ho! Thundercats are on the move, Thundercats are loose. Feel the magic, hear the roar, Thundercats are loose. Thunder, thunder, thunder, Thundercats! Thunder, thunder, thunder, Thundercats! Thunder, thunder, thunder, Thundercats! Thunder, thunder, thunder, Thundercats! Thundercats!</p>
	</dd>
	<dt><a href="#">The A-Team</a></dt>
	<dd>
		<p>Ten years ago a crack commando unit was sent to prison by a military court for a crime they didn't commit. These men promptly escaped from a maximum security stockade to the Los Angeles underground. Today, still wanted by the government, they survive as soldiers of fortune. If you have a problem and no one else can help, and if you can find them, maybe you can hire the A-team.</p>
	</dd>
</dl>

Alright, so we might need a spatter of attributes in there, but this is the core and the only necessary elements required for now.

Planning how the accordion will work

So now the foundation markup is down, this is how it’s going to work:

  1. Page will load and render the above markup
  2. Plugin will accordion-ise the specified elements
  3. Plugin will loop through each child dd element and determine the total height and the height for just the anchor tag and store the values in an array
  4. Plugin will hide the content for each child dd element, by changing the height value to just the height of the anchor tag 0, leaving the block level anchor tag dt title/link visible
  5. Plugin will watch for an anchor tag being used and toggle the content visibility
  6. Plugin will fetch the corresponding height value from the array and alter the style for the selected child corresponding dd element
  7. Webkit-transitions will be used to add a smooth appearance animation

Perhaps the most striking difference with this approach is that I’m not wrapping the content, that will appear, in its own element and just using something like jQuery’s hide/show() or slideUp/Down(). I feel that’s unnecessary and the wrong approach. It might be easier from a technical standpoint but out of context it’s ugly and of course the the wrapping element bears little semantic meaning.

Creating the webkit accordion jQuery plugin

The first thing I’m going to do is create three global variables that I can use throughout the plugin.

var el = this;
var ddHeight;
ddHeight = new Array();

el.addClass('enhance');

Line 1: el refers to the accordion element.

Line 2: ddHeight will refer to the entire height of each accordion dd element.

Line 3: because I’ll be storing the height values for an unknown number of dd elements I need to ensure that they are stored in an array.

Line 5: purely to demonstrate the employed principles of progressive enhancement and allow greater control of only applying styles to the enhanced accordion, I give the accordion element a class of ‘enhance’.

Next I need to loop through each accordion dd element and store their height values.

el.find('dd').each(function(i){
	var dd = $(this);
	ddHeight[i] = dd.height();
	dd.addClass('closed')
});

Line 1: using jQuery’s each method I can easily loop through all dd elements within the accordion element, passing an index (i) for use in the function during each iteration.

Line 2: for repeated use of $(this) I store the element in a variable.

Line 3: I store the height of the entire dd element.

Line 4: for the last part of the setup I add a class called ‘closed’ to each dd element. This class has a height of 0, as well as an overflow property of hidden.

Finally I need to bind an event to the anchor tag within each dt element of the accordion element.

el.find('dt a').bind('touchstart', function(e) {
	e.preventDefault();

	var toExpand = $(this).parent().next('dd');
	var i = toExpand.index('dd');

	if (toExpand.attr('id') == 'active') {
		toExpand
			.removeAttr('id')
			.removeAttr('style')
			.addClass('closed');
	} else {
		var active = toExpand.parent().find('#active');

		if (active) {
			active
				.removeAttr('id')
				.removeAttr('style')
				.addClass('closed');
		}

		toExpand
			.attr('id', 'active')
			.css('height', ddHeight[i]+'px')
			.removeClass('closed');
	}
});

Line 1: I am only watching for touchstart events on anchor tags within dt elements of the accordion. I’m using the mobile webkit touchstart event in place of the click event as it’s some 300ms more responsive on mobile devices.

Line 2: firstly I prevent the default action (following the href) of touching the anchor tag within the dt element.

Lines 4-5: next I store the corresponding dd element, traversing using jQuery’s parent() and next() methods, as I will be using it repeatedly in this function. I also set the current index of the corresponding dd element in another variable, for use with the array created previously.

Lines 7-11: now this next part first of all checks to see whether the corresponding dd element for the anchor tag being touched is already ‘open’. It does this by checking for an id attribute of ‘active’ for this particular dd element. If the id exists then we know that the same anchor tag has been touched twice in succession and we should ‘close’ the dd element. To do this I remove the id, style and add the class ‘closed’.

Lines 13-25: here I am dealing with the opposite of the above condition. The touched anchor tag does not have an ‘open’ corresponding dd element and therefore I must ‘open’ and reveal the content. This can involve two aspects; closing an already open dd element of and then opening the selected element of content. If there already is an ‘open’ dd element then I need to remove the id, style and add the class ‘closed’. And finally to open the corresponding dd element I need to update its height that I stored in aforementioned array and give it an id of ‘active’.

Applying some simple CSS to the accordion

Finally I’m just going to add some bare bones CSS in order for the accordion to look as you’d expect one to appear.

dl {
	margin: 20px;
	padding: 0;
	width: 250px;
}
	dl dt {
		margin: 10px 0;
	}
	dl dd {
		margin: 0;
	}
		dl.enhance dd {
			-webkit-transition: height 0.4s ease;
			overflow: hidden;
		}
		dl.enhance dd.closed {
			height: 0;
		}

Nothing complicated here at all, other than two lines perhaps:

Line 13: here I am using the magical -webkit-transition property to apply a 0.4s easing transition when the height is changed. Note, for desktop browser adoption we could simply use a browser vendor stack of transition properties, -moz and -o for example.

Line 14: because I am changing the height of various dd elements I need to set overflow property to hidden, to prevent content from spilling out of the element when the height is smaller than the actual content.

Note I am only applying these styles to a dl that has been successfully accordion’ised and has the class of ‘enhance’.

A few things to note

This plugin is not a finished product, there is room for improvement and there are some areas worth noting.

Progressive enhancement

Although this is for mobile webkit I’m still applying principles of progressive enhancement; this is an approach that gracefully degrades. “Progressive enhancement for mobile webkit, why?!” I hear you say. Granted—it’s not fundamental when dealing with a known deployment target, but straying from the principle of progressive enhancement is a dangerous thing to consider, like when Luke Skywaker shunned Yoda and Obi-Wan on Dagobah and went to try and be a hero and pew pew Vader on Bespin. He lost his hand pulling that little stunt. No need to go losing hands with this accordion though.

Hardware accelerated transitions

I mentioned this above somewhere; it would be easy to use jQuery’s built in show/hide() or slideUp/Down() functions. But using webkit transitions ensures that the animations are smooth because they are hardware accelerated, unlike methods that use pure Javascript—which can appear very bad on iOS/Android devices, especially older models like my rusty iPhone 3G.

Adopting the plugin for use on desktop browsers

This plugin was built with the intention of being used in mobile webkit browsers, but it wouldn’t be very hard to modify the plugin for use on desktop browsers. Simply changing the anchor event to watch for a click instead of a touchstart would be the most important change. And then adding in the various vendor prefixes for the CSS3 transition property. Old browsers would just see a jump in height change instead of a nice transition.

Currently it’s not RESTful

I haven’t quite gotten around to using hash fragments to ensure the plugin is fully RESTful, but watch the repo and I’ll be making regular commits to improve this, and some other, aspects. This mobile webkit jQuery accordion plugin now supports URL hash fragments.

Please fork and send a pull request if you’d like to make a change.

About the author

I'm Sam Croft a full-stack developer with over 15 years experience in web and app development. For the last six years I have been a partner of Running in the Halls, an app and game design studio based in Huddersfield, UK. During this time I have developed many web and applications. Highlights include a node.js/socket.io app to create the worlds largest crowd-based game on the first series of Channel 4's Gadget Man with Stephen Fry and Librarygame, a Library gamification platform for Universities. Librarygame is being played by over 6,000 students at The Open University, The University of Manchester, The University of Glasgow and The University of Huddersfield.

In my spare time I enjoy long distance running, watching all sports (especially F1) and playing video games. I live in the Holme Valley in West Yorkshire with my wife, Alex.

Sometimes I tweet.

  • If you’re going for semantics they why not use a <dl&#62?

    http://www.w3schools.com/tags/tag_dl.asp

    <dl>
    <dt>Coffee</dt>
    <dd>- black hot drink</dd>
    <dt>Milk</dt>
    <dd>- white cold drink</dd>
    </dl>

  • sam

    Absolutely – I mentioned this in paragraph 8.

  • Nice… but why don’t you add a -moz-transition: and transition: to be compliant ?

  • Sam Croft

    @molokoloco – this was built just for mobile webkit, so there is no need. But for desktop browser use, then of course -moz-transition, -o-transition and transition could be used.

  • Pingback: Creating a lightweight and semantic jQuery tabs plugin()

  • Pingback: HTML lists with Jquery for mobile devices | Guillermo Cruz - Web Developer()

  • thank you very much 😉

    • Guest

      bytw, bind(“touchstart, click”… works well

      • Interesting, I have never tried binding multiple events. Thanks for sharing.

  • Yury Kovalev

    thank you very much 😉

    • Guest

      bytw, bind(“touchstart, click”… works well

      • Interesting, I have never tried binding multiple events. Thanks for sharing.

  • Hey Sam,
    unfortunately the height property doesnt get hardware accelerated even if you use the transition property (google). Therefore your accordion is also not hardware accelerated.

    • Hi Jack,

      That’s right. I need to update this article!

      Not sure if you’re just pointing out or looking for a solution. Using…

      -webkit-transform: translate3d(0, heightPosition, 0);

      and

      -webkit-transition: -webkit-transform 0.4s ease;

      should get it moving with hardware acceleration.

      – Sam

  • Jack Dribble

    Hey Sam,
    unfortunately the height property doesnt get hardware accelerated even if you use the transition property (google). Therefore your accordion is also not hardware accelerated.

    • Hi Jack,

      That’s right. I need to update this article!

      Not sure if you’re just pointing out or looking for a solution. Using…

      -webkit-transform: translate3d(0, heightPosition, 0);

      and

      -webkit-transition: -webkit-transform 0.4s ease;

      should get it moving with hardware acceleration.

      – Sam