Home / AJAX

How Do Display an Accordion?

RSS
Modified on 2010/08/24 03:45 by James Chambers Categorized as Uncategorized

Problem

I have a number of pieces of content that I would like to display on a portion of my page. I'd like the content grouped into panels, which users can see one at a time. How do I accomplish this with the MVC Framework?

Solution

ASP.NET MVC projects easily integrate with the jQuery library. The library has a set of interface interface extensions called jQuery UI that contain an accordion control which may be exactly what you're looking for!

Squeezebox on a Web Page

The jQuery UI accordion control is used to display one title-content pair at a time. Using standard HTML elements, we structure our content in a way that meets the requirements of the control.

While we can override the default header element, our content must always be the element immediately after said header. The title must be placed in an A element and the title-content pairs must be in a container element, such as a DIV.

The Quick-and-Dirty Sample

Following those guidelines - pairs of titles and content in a container - you can put together a really quick sample by adding the following HTML to your page:
<div id="blocks">
    <h1><a href="#1">Section 1</a></h1>
    <div>The data, images or text for section 1.</div>
    <h1><a href="#2">Section 2</a></h1>
    <div>The content you want for the second block.</div>
</div>

Our container is the DIV with an ID of blocks. There are two pairs of title and content - section 1 and section 2 - with the title in a header tag and the content immediately following. A simple call to accordion() gets us rolling:

$(function () {
    $("#blocks").accordion();
});

Giving us the following result:

Image

Try Me!

We're not here for the straightforward stuff, though, so let's get into a more MVC-ish type sample.

Adding MVC to the Mix

You'll likely have a group of data that you'll want to feed into accordion(), and it's going to be coming from a repository. Let's consider a base class at the center of the user interface we're building:

Image

Our class would look something like this:
public class Season
{
    public int SeasonID { get; set; }
    public string Title { get; set; }
    public List<string> DescriptionParagraphs { get; set; }
}

On our controller, we'd put together an ActionMethod that sets up our view:
public ActionResult PrairieSeasons()
{
    ISeasonRepository repository = new SeasonRepository();
    
    var seasonList = repository.GetSeasons();

return View(seasonList); }

We'd compose our view with an IEnumerable. This will allow us to foreach over the Model on the view and use the seasonList we created in the above action. The core part of the view that will generate the required HTML is as follows:
<div id="seasons">
<% foreach (var season in Model)
   { %>
       <h3><a href="#<%: season.SeasonID %>"><%: season.Title %></a></h3>
       <div>
    <% foreach (var paragraph in season.DescriptionParagraphs)
       { %> 
        <p><%: paragraph %></p>
    <% } %>
       </div>
<% } %>
</div>

What we're doing here is looping through each of the seasons that were passed into the view and using season.Title with season.DescriptionParagraphs as the title-content pairs. Because season.DescriptionParagraphs is a collection of strings, we foreach over it to output a set of P elements with the content.

Finally, we've got to lay down a bit of jQuery. Here, I'm also passing in a simple option to supress the icons.
$(function () {
    $("#seasons").accordion({
       icons: false
    });
});

Try Me!

Introducing Partials to the Accordion

We have one more trick up our sleeves: partial page rendering. Let's suppose that the content we were loading in the panels took longer to load, and we wanted to improve the initial load time.

For the purpose of this article we'll update our code to render only the first paragraph of text, and then make an Ajax call to retrieve the subsequent text. Of course, this is a little contrived as we already have all the text in the first place, but stick with me folks.

Image

Let's add an ActionResult to our controller, called PrairieSeasonPreview, that has the same code as PrairieSeasons. We also need to add a corresponding view - also called PrairieSeasonPreview - to the Views folder in our project. As will our other page, this will have a model that is an IEnumerable. Here's what the code for the main portion of our view will look like:

<div id="seasons">
<% foreach (var season in Model)
   { %>
       <h3><a href="#<%: season.SeasonID %>"><%: season.Title %></a></h3>
       <div>
            <p><%: season.DescriptionParagraphs.First() %></p>
            <p>
                <button class="paragraphs" id="<%: season.SeasonID %>">Read more</button>
            </p>
       </div>
<% } %>
</div>

With the panels loading dynamically, we'll also want the accordion to size to fit the content we're adding to the sections. We're going to override the autoHeight property, which normally sizes the panels to the largest content on load. With our content coming after the fact, we don't want the size constrained. Here's our updated call to accordion():
$("#seasons").accordion({
    icons: false,
    autoHeight: false
});

We're also going to use the jQuery UI button(), and assign a click handler to the each button created. We use the JavaScript keyword this, wrapped in a jQuery object, to identify the button that was clicked and extract the season ID and the parent (container) control of the button. This allows us to load the content inline.
$(".paragraphs").button().click(function () {
    var $seasonId = $(this).attr("id");
    var $container = $(this).parent();
    $.get("AdditionalParagraphs", { seasonId: $seasonId },
        function (data) {
            $container.html(data);
        }
    );
});

Now, all we need is our PartialView action method on the controller, AdditionalParagraphs:
public PartialViewResult AdditionalParagraphs(int seasonId)
{
    ISeasonRepository repository = new SeasonRepository();
    var paragraphs = repository.GetParagraphs(seasonId).Skip(1);
    return PartialView(paragraphs);
}

This method is invoked by the above javascript and returns all paragraphs after the first, thanks to a little LINQ to objects call. As an overview, the parts we needed were:
  • A controller action to return the list of data
  • A view that rendered the important, fast-loading content
  • A call to accordion() to stylize the page
  • Some jQuery code to create a button and handle the click event
  • An Ajax call, in our click handler, that calls our controller for the rest of the content, and
  • A partial view to render the addtional text.

Try Me!

Wrapping Up

We've seen that by semantically describing our content we can leverage a great UI element to keep our content in a uniform-sized area on our page. By combining the MVC Framework with jQuery UI, we're also able to build a fast-loading solution that downloads content on-demand for our end users.

About the Author

James Chambers is a Senior Software Developer in Canada, where he harnesses solar power to energize his fully-automated, snowball launching anti-intruder home security system. His development passions are fueled by new tech, new tools and mentoring others. Follow his coding adventures at Mister James.
  Name Size
- seasons.PNG 74.08 KB
- seasons-ajax.PNG 18.35 KB
- simple.PNG 14.67 KB