“Drawing” from Our Learning: How to Build a Lesson Drawer in jQuery, HTML, and CSS

In the spirit of showing how things are done at SitePoint‘s sister company Learnable—a new site where anyone can create an online course about anything at all, and then sell access to that course—I’m going to run you through a small component of Learnable’s student view: the lesson drawer. I’ve simplified the layout and styling a little so that you can easily understand what’s going on, but the JavaScript is exactly the same.

In this post, I’ll cover the HTML (including some of the new HTML5 elements), CSS, and JavaScript (with a little help from jQuery) needed to build a drawer. The drawer is a large container that holds a list of the lessons in a course, which can be opened by a button. The lessons are represented by a clickable thumbnail. To view an example of what we’ll be building, check it out here.

The HTML: the Structure

<section class="clearfix" id="lessonBuilder">
  <span id="lessonsTabTarget"><a href="#">Show Lessons</a></span>
  <nav id="lessonsTab" style="display: none;">
    <h1>Lessons</h1>
    <ul class="clearfix">
      <li class="tab"><a href="#"><img src="thumbnail.jpg" /></a></li>
      <li class="tab"><a href="#"><img src="thumbnail.jpg" /></a></li>
      <li class="tab"><a href="#"><img src="thumbnail.jpg" /></a></li>
      <li class="tab"><a href="#"><img src="thumbnail.jpg" /></a></li>
    </ul>
  </nav>
</section>

If you’re unfamiliar with HTML5, you’ll see I’ve used a few tags that will be new to you. The section is used to describe sections of your site, and the nav tag is used to contain navigation elements. The HTML5 tags help to reduce “div-itis”; for example, where you may have <div id="section"></div> and <div id="nav"></div>.

Apart from the new tags, it’s basically standard HTML. The unordered list would contain the linked thumbnails.

The JavaScript: the Magic

var LEARNABLE = {};
/**
  * Initialises the Lesson Drawer
  */
LEARNABLE.lessonDrawer = (function () {
    var init = function () {
        $("#lessonsTab").hide();
        $('#lessonsTabTarget').toggle(function() {
            $(this).addClass("shown").children('a').html("Hide Lesson Navigator");
            $("nav#lessonsTab").show("slow");
            return false;
        }, function() {
            $("nav#lessonsTab").hide("slow");
            $(this).removeClass("shown").children('a').html("Show Lesson Navigator");
            return false;
        });
    };

    // Public API
    return {
        init: init
    };
})();

$(document).ready(function() {
    LEARNABLE.lessonDrawer.init();
});

The JavaScript is where it becomes interesting. I’m going to proceed through this step by step, building it the way I would write it while explaining along the way.

var LEARNABLE = {};

This creates an object that will contain the functions for your page. We’ve named it LEARNABLE, as it “namespaces” your functions. This form of writing JavaScript is called the module pattern. It’s a great way to structure your JavaScript for a number of reasons:

  • It reduces the number of global variables, as they are evil.
  • You can easily have lots of functions working together without affecting other scripts you may have running.
  • It’s relatively straightforward to maintain and work on.
  • Other developers involved in the project will be able to see what’s going on easily.
var LEARNABLE = {};
/**
  * Initialises the Lesson Drawer
  */
LEARNABLE.lessonDrawer = (function () {
    var init = function () {

    ...

    };

    // Public API
    return {
        init: init
    };
})();

Now we create our functions. I’ve named it lessonDrawer, and as it’s a part of the LEARNABLE codebase, it becomes LEARNABLE.lessonDrawer.

LEARNABLE.lessonDrawer is a function, so it is written as = (function (){, then the code, and is closed with })();.

Here are the outside bits of the function:

LEARNABLE.lessonDrawer = (function () {

    ...

})();

Count the parentheses, and you’ll see that there’s an extra set of () at the end. This means it is self-executing; when your browser has downloaded this script, it will run it, allowing it to be accessed by any other JavaScript function in your script. Well, sort of.

    var init = function () {

    ...

    };

    // Public API
    return {
        init: init
    };

This little bit of trickery means that within the lessonDrawer function there are further functions, in this case called init. The tricky part is that at the end of the lessonDrawer function, it will return to your script a way to gain access to the init function. You may ask, what’s the big deal? Let’s say we changed it a little:

var LEARNABLE = {};
/**
  * Initialises the Lesson Drawer
  */
LEARNABLE.lessonDrawer = (function () {
    var init = function () {

    ...

    };
    var otherThing = function () {

    ...

    };

    // Public API
    return {
        init: init
    };
})();

Because we’ve specifically said that we only want the init function available, no function outside of lessonDrawer can touch the function named otherThing. It’s safe inside its parent function. You can’t accidentally run it and, more importantly, when you give it to other developers, they can only use it the way you’ve specified.

Now to the insides of the init function:

$("#lessonsTab").hide();

First, we hide the #lessonsTab, which is our nav container. We do that in this function so that JavaScript-less users can still see the lesson drawer:

$('#lessonsTabTarget').toggle(function() {

    ....

}, function() {

    ....

});

The jQuery Toggle method displays or hides elements that it’s told about. The first half between the $('#lessonsTabTarget').toggle(function() { and }); means that when it’s clicked, it will show the lesson drawer.

Its counterpart, between the function() { and closing });, will hide the lesson drawer if it’s open:

    $(this).addClass("shown").children('a').html("Hide Lesson Navigator");
    $("nav#lessonsTab").show("slow");
    return false;

This is the code for showing the lesson drawer. I’ll now explain it line by line for you:

$(this).addClass("shown").children('a').html("Hide Lesson Navigator");

$(this) means whatever its parent is talking about (in this case, it’s the $('#lessonsTabTarget'). I’ve been assuming you know what $('#lessonsTabTarget') means, but for those who’ve been playing at home, it asks jQuery (represented by the $) for any elements that have an ID of lessonsTabTarget.

We add a class called “shown" to $(this), and then look through its children for an anchor. Looking back at the HTML structure we’re using, this is <span id="lessonsTabTarget"><a href="#">Show Lessons</a></span>, and on the child anchor we change the text (or its HTML) to the next text (“Hide Lesson Navigator”) as an instruction to the user that if they click the button, they will hide it).

$("nav#lessonsTab").show("slow");

Now we tell it to find the nav#lessonsTab and make it appear slowly. The jQuery Show method accepts a few arguments, the first being how long you want it to take to show an element. Instead of "slow", you can use "fast" or the number of milliseconds, where 1,000 means one second (as this is typed as an integer, no quotation marks are necessary, unlike "fast" and "slow").

return false;

We return false to stop the browser from following the link. (As it is, it doesn’t go anywhere, but the <a href="#" would make the page jump to the top without returning false).

To summarize:

  • Set up an unobstructive JavaScript function any time the page contains a #lessonsTabTarget element.
  • Add a class (telling the CSS that it’s “shown“).
  • Change the button text.
  • Open the lesson tab drawer.
  • Stop the page from following the link.

The second half is exactly the opposite! It hides the lesson drawer, removes the “shown” class name, and changes the text back. It also stops the browser from following the link. On a side note, the return false always needs to be the last thing in your function.

Lastly, we need to run the code. Our final task is to tell the document that when it’s downloaded all its resources (JavaScript and CSS, as well as images), or when it’s ready, to run the init() function. To access the init() function, you have to use its full name: LEARNABLE.lessonDrawer.init();.

$(document).ready(function() {
    LEARNABLE.lessonDrawer.init();
});

The CSS: the Polish

Adding the CSS is the last bit needed to make this a really great component.

/* HTML5 tags */
header, section, footer, aside, nav, article, figure {
	display: block;
}

#lessonBuilder {
  min-height: 200px;
  width: 940px;
  margin: 0 auto;
  position: relative;
}

nav#lessonsTab {
  background-color: #26414A;
  width: 940px !important;
  margin: 0 auto;
  opacity: 1 !important;
  padding: 30px 0;
  z-index: 1;
  display: none;
}

nav#lessonsTab h1 {
  color: white;
  font-size: 18px;
  margin: 0 0 0 45px;
}

nav#lessonsTab ul {
  list-style: none;
  padding: 20px 0 0 45px;
  margin: 0;
}

nav#lessonsTab ul li {
  float: left;
  margin: 0 15px 40px 0;
  padding: 4px;
  height: 70px;
}

nav#lessonsTab ul li:hover {
  background-color: #C9EFFB;
}

nav#lessonsTab ul li a {
  width: 100px;
  display: block;
  text-decoration: none;
}

nav#lessonsTab ul li a img {
  border: none;
}

#lessonsTabTarget {
  border-top: 2px solid #3F6D7B;
  width: 940px;
  display: block;
  z-index: 2;
  margin-top: -2px;
  text-decoration: none;
}

#lessonsTabTarget a {
  background: #283D44 url('lessonnav.png') top left repeat-x;
  display: block;
  padding: 7px 20px;
  position: absolute;
  right: 0;
  top: 0;
  color: white;
  width: 170px;
  text-align: center;

  -moz-border-radius: 0 0 5px 5px;
  -webkit-border-radius: 0 0 5px 5px;
  border-radius: 0 0 5px 5px;

  font-weight: bold;
  font-size: 15px;
  text-decoration: none;
  text-shadow: 0 1px 1px #333;
}

#lessonsTabTarget.shown a,
#lessonsTabTarget a:hover {
  background: #283D44 url('lessonnav.png') 0 -35px repeat-x;
}

/* http://perishablepress.com/press/2008/02/05/lessons-learned-concerning-the-clearfix-css-hack */
.clearfix:after {
	clear: both;
	content: ' ';
	display: block;
	font-size: 0;
	line-height: 0;
	visibility: hidden;
	width: 0;
	height: 0;
}

.clearfix {
	display: inline-block;
}

* html .clearfix {
	height: 1%;
}

.clearfix {
	display: block;
}

In Conclusion

This nice modular component is a real-world example of effectively using JavaScript (in this case, jQuery), HTML (with some HTML5 stuff), and CSS together. As I mentioned, it’s in use in Learnable, the place where you can take or create a course online on any subject.

note:Want more?

If you want to read more from Mark, subscribe to our weekly web design newsletter, The SitePoint Design View.

I hope you enjoyed (and understood) it. If you have questions, please feel free to ask via the comments below.

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • BrenFM

    Thanks for this… very helpful stuff! I’m pretty new to jQuery, and it’s great to see an easy way to isolate my functions from each other… and who knew that you could toggle something like that without using a .click() function?

    Cheers Sitepoint! Making my job easier and more fun all at once!

  • notnuts

    This is all very nice and well, but unfortunately inaccessible when javascript is disabled.
    I do hope the live version at least makes use of some sort of progressive enhancement / graceful degradation!

    • Mark Cipolla

      Indeed. And the live version does progressively enhance; I didn’t go into it as that would be too much to fit into a single post!

      For the live version, non-js users see the lesson drawer open by default, with no ‘show/hide drawer’ button.

      Good spot, though!

  • thompsonay

    Gosh, you know I love this HTML5. It is clean and easy to use. It works well. Now if there was a way to make webpages load faster while the ads are loading….:)

    • Stephen Hill

      Well, the only HTML5 used was a Section and Nav tag.

  • http://www.cemerson.co.uk Stormrider

    I like these looks into Javascript… something I’m not an expert on, but want to be. There’s a severe lack of tutorials on how to write good JavaScript out there I find. The vast majority of ones I find are things like how to disable right click, how to make an alert box pop up, how to change status bar text… nothing on proper JavaScript, all 90s stuff!

  • Aaron

    The code is clean and its a great tutorial but what I would be interested in a tutorial about how SitePoint made the bar at the top of the site with new product information. The rounded corners the button to close it. Like the bar for the cloud computing book with clouds. Thanks!

  • http://pixelsheaven.com Ronny

    the jQuery ‘ready’ event (or ‘domready’ on mootools) isn’t fired when all resources are downloaded but when the DOM is ready (so it’s safe to query/manipulate it).