Improving an accordion script - Part 1

I was sitting in my office at work a while back, and a co-worker came by and asked me to take a look at some of their jQuery code. He had written some fairly simple code to create an accordion effect for a FAQ (Frequently Asked Questions to those who don't know) he was working on. Of course, I was glad to help. Anyone who is taking the time to have someone else validate their code is going to get my help. There are too few people like this and I wanted to promote this behavior.

Now, before I started, I informed him that this could hurt his feelings. If you write crappy code, I am more than happy to call you out, but it comes from a good place. I want you to get better and if I can help by showing you where you went wrong I will. He was more than happy to take the constructive criticism.

Our Intial Code

So let's start with the HTML:

<div class="expand-all">Expand All</div>
<div class="collapse-all">Collapse All</div>
<ul class="accordion">
    <li class="accordion-item">
        <div class="accordion-header">Question one?</div>
        <div class="accordion-content">Answer to this question.</div>
    </li>
    <li class="accordion-item">
        <div class="accordion-header">Question two?</div>
        <div class="accordion-content">Answer to this question.</div>
    </li>
    <li class="accordion-item">
        <div class="accordion-header">Question three?</div>
        <div class="accordion-content">Answer to this question.</div>
    </li>
</ul>

And here is the jQuery:

function accordionLoad() {
    $(".accordion-header").removeClass("expanded");
    $(".accordion-content").hide();

    $(".accordion-header").bind("click", function(){
        $(this).toggleClass("expanded");
        $(this).siblings(".accordion-content").slideToggle();  
    })

    $(".expand-all").bind("click",function(){
        $(this).siblings(".accordion").find(".accordion-content").slideDown();
        $(this).siblings(".accordion").find(".accordion-header").addClass("expanded");

    })

    $(".collapse-all").bind("click",function(){
        $(this).siblings(".accordion").find(".accordion-content").slideUp();
        $(this).siblings(".accordion").find(".accordion-header").removeClass("expanded");
    })
}

$(document).ready(function(){ accordionLoad(); });

So... not too bad. I can see a few things right off the bat that need to be done but I have seen much worse. My first two suggestions were related to the HTML. I suggested we create a div to hold every part of the accordion and to preface each of our class names with the word accordion- so we would be less likely to collide with other peoples' CSS. This basically sets up the equivalent of a namespace in our CSS. Our HTML now looked like this:

<div class="accordion">
    <div class="accordion-expand-all">Expand All</div>
    <div class="accordion-collapse-all">Collapse All</div>
    <ul>
      <li class="accordion-item">
          <div class="accordion-header">Question one?</div>
          <div class="accordion-content">Answer to this question.</div>
      </li>
      <li class="accordion-item">
          <div class="accordion-header">Question two?</div>
          <div class="accordion-content">Answer to this question.</div>
      </li>
      <li class="accordion-item">
          <div class="accordion-header">Question three?</div>
          <div class="accordion-content">Answer to this question.</div>
      </li>
    </ul>
</div>

So now, with these changes made to our HTML, it was time to move into the jQuery code. My first suggestion was to move the .accordion-expanded class to the li element. This would allow us to style both the .accordion-header and the .accordion-content by adding the class to their parent element. This will also allow us to use CSS for showing and hiding the answers instead of javascript.

Also, since everything is now self-contained in the accordion div, we can use the jQuery .find() method and chain all the selections and methods together. Once we use the .find() method we will use the .end() method to return to our previous selection. For more information on this method, check out the jquery documentation.

So now we have this code:

function accordionLoad() {
 // Our single selector
  var accordion =$('.accordion');

  accordion
    .find('.accordion-header')  // Get our questions
        .on('click', function(){ 
            $(this).parent().toggleClass('accordion-expanded')
        })
        .end()
    .find('.accordion-expand-all') // Expand All
        .on('click', function(){
            accordion.find('.accordion-item').addClass('accordion-expanded');
        })
        .end()
    .find('.accordion-collpase-all')  // Collpase All
        .on('click', function(){
            accordion.find('.accordion-item').removeClass('accordion-expanded');
        })
        .end(); // Not required but added for consistency
}

$(function() {
    accordionLoad();  // Execute our script on page load
});

Now, we can add these classes into our CSS for styling:

.accordion-header {
    /* basic styling for questions go here */
}
.accordion-content {
    display: none;  /* hide our answers */
    /* basic styling for answers go here */
}
.accordion-expanded > .accordion-header {
    /* change how the question looks when answer is displayed */
}
.accordion-expanded > .accordion-content {
    display:block; /*show our answer */
}

Obviously, I am not going into the actual styling of the Questions and Answers here but you get the idea. Overall, I think that is a drastic improvement over our intial code. However, the are still many issues and optimizations we can do to this code and we will explore all of that in Part Two.

'Til next time,
-G