Contact Us
Please fill out the form below and we will get in touch within four business hours.

*
*
*
*
*
*
*Interested In:
(Check all that apply)



 
 
Synergy Corporate Technologies
Synergyonline > Blog > Posts > Building an Accordion with jQuery and SharePoint 2010
November 04

Building an Accordion with jQuery and SharePoint 2010

This is the 2nd post in a series that talks about various areas of our redesigned corporate website using technologies like jQuery.  These technologies helped us extend the out of box look of SharePoint 2010 for a public facing SharePoint driven website.

User experience (UX) is a big buzzword these days when building websites.  How many clicks the user has to perform and how much scrolling will be done should always factor in the design process.  In many cases content can be so large that your user could have finger cramps when scrolling (or kill a tree when printing the page!).  Have you ever given thought to using a jQuery Accordion?  This method works well, because it makes use of the horizontal space to store massive amounts of content in “panels” that are hidden until the user wants to see it.  It also adds a nice interact experience that people normally get only when viewing Flash content.

Here is one example of a page on our corporate website  http://www.synergyonline.com/sharepoint:

AccordionClosed AccordionOpen

What you are presented with here is a series of 7 panels, each representing a list item in a SharePoint List.  The first panel is shown by default.  Arrows on each panel offer the user a way to expand content in each panel while collapsing the panel currently being viewed.

To build such a webpart, I initially defined a SharePoint List that contained all the elements used by the visual webpart:

ListSchema 

Title Name of panel section
SlideOrder Display Order of the panels
SlideHtml Raw HTML for the panel content
HeaderUrl Image for the vertical text on the panel
PanelBackgroundImage Background image for the panel (optional)
PanelBackgroundHex General background color for the panel

 

Now that the list is defined, the next step was to build the visual webpart.  The basic shell of the panels is as follows:

<div> <!-- container -->
    <ul>
        <li> <!-- list item for each panel -->
            <div>panel left shadow image</div>
            <div>panel container</div>
                <div>left vertical banner</div>
                    <div>vertical text image</div>
                    <div>navigation arrow</div>
                    <div>left vertical shadow</div>
                </div>
                <div>panel content</div>
            </div>
        </li>
    </ul>
</div>

Here is the corresponding html used in the visual webpart:

<asp:Repeater ID="rptSlider" runat="server" Visible="true" onitemdatabound="rptSlider_ItemDataBound">
    <HeaderTemplate>
        <asp:Literal ID="ltContainerBegin" runat="server"></asp:Literal>
        <ul class="accordion">
    </HeaderTemplate>
    <ItemTemplate>
            <li id="liItem" runat="server" class="accordion-top-li">
                <div class="li-content-vert-shadow"><img src="/SiteCollectionImages/img_vert_shadow.png" alt="vertical shadow" /></div>
                <div id="liContent" class="li-content" runat="server">
                    <div class="accordion-vert-header">
                        <div class="vert-header-text"><asp:Literal ID="ltVerticalHeaderImg" runat="server"></asp:Literal></div>
                        <div class="vert-header-arrow"><asp:Literal ID="ltArrow" runat="server"></asp:Literal></div>
                        <div class="vert-header-shadow"><asp:Literal ID="ltVerticalShadowImg" runat="server"></asp:Literal></div>
                    </div>
                    <div class="accordion-html-content">
                        <asp:Literal ID="ltSlideHtml" runat="server"></asp:Literal>
                    </div>
                </div>
            </li>
    </ItemTemplate>
    <FooterTemplate>
        </ul>
        </div>
    </FooterTemplate>
</asp:Repeater>

Since the panels are all the same structure, I used an ASP:Repeater control to build the unordered list items dynamically.  After setting up the structure in html, I began to tie the data to the controls, through the ItemDataBound event of my Repeater:

protected void rptSlider_ItemDataBound(object sender, RepeaterItemEventArgs e)
{
    if (e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem)
    {
        DataRowView drv = (DataRowView)e.Item.DataItem;
 
        Literal ltContainerBegin = (Literal)rptSlider.Controls[0].FindControl("ltContainerBegin");
        ltContainerBegin.Text = string.Format("<div id='accordionBKG' style='background-color: {0};'>", drv["PanelBackgroundHex"]);
 
        HtmlGenericControl liItem = (HtmlGenericControl)e.Item.FindControl("liItem");
 
        if (nthSlide == 0)
        {
            liItem.Attributes.Add("class", "accordion-top-li-first");
        }
        else
        {
            liItem.Attributes.Add("style", "width: " + slideClosedWidth + "px");
 
            Literal ltArrow = (Literal)e.Item.FindControl("ltArrow");
            ltArrow.Text = "<img src='/SiteCollectionImages/but_arrow_lft_off.png' width='40px' height='40px' alt='accordion arrow' class='accordion_arrow' />";
 
            Literal ltVerticalShadowImg = (Literal)e.Item.FindControl("ltVerticalShadowImg");
            ltVerticalShadowImg.Text = "<img src='/SiteCollectionImages/img_vert_shadow.png' alt='vertical shadow' class='accordion_vertical_shadow' />";
        }
                
        HtmlGenericControl liContent = (HtmlGenericControl)e.Item.FindControl("liContent");
                
        // Set background image.  If it doesn't exist, set it to the background hex value in the column
        if (drv["PanelBackgroundImage"].ToString() != string.Empty)
            liItem.Attributes.Add("style", "background-image: url( " + drv["PanelBackgroundImage"] + "); background-position: bottom left; background-repeat: no-repeat;");
 
        if (drv["HeaderUrl"].ToString() != "")
        {
            Literal ltVerticalHeaderImg = (Literal)e.Item.FindControl("ltVerticalHeaderImg");
            ltVerticalHeaderImg.Text = string.Format("<img src='{0}' alt='{1}' /><br /><br />", drv["HeaderUrl"].ToString(), drv["Title"].ToString());
        }
 
        Literal ltSlideHtml = (Literal)e.Item.FindControl("ltSlideHtml");
        ltSlideHtml.Text = drv["SlideHtml"].ToString();
 
        nthSlide++;
    }
 
}

There are a few things here to notice.  I used ASP:Literal controls in this webpart instead of ASP:Image controls.  This is purely a matter of preference.  You can use either.  The important thing I am doing here is dynamically building my panels and then let the CSS and jQuery handle the “magic”. 

I built this accordion with future addition/subtractions in mind.  In my Page_Load method, I made a determination of what the closed panel widths should be and stored that value in a global variable called “dblSlideClosedWidth”:

ListItems = dt;
if (ListItems.Rows.Count > 0)
{
    // Set widths dynamically based on how many rows are currently in the list
    double rows = (double)ListItems.Rows.Count;
    dblSlideClosedWidth = Math.Floor(dblSlideClosedContainerWidth / (rows - 1));
    // Determine Slide Width, but add 9px onto it because there is a -9px margin assigned through css
    slideClosedWidth = (int)dblSlideClosedWidth + 9;
 
    rptSlider.DataSource = ListItems;
    rptSlider.DataBind();
 
}

Then in the repeater event you’ll notice I set the width of each panel dynamically.  If this is not the first time through the loop (designated by the global variable “nthSlide”), I set the width panel equal to “dblSlideClosedWidth”.  The first panel is ignored, as it needs to be displayed in full width specified in the css:

if (nthSlide == 0)
{
    liItem.Attributes.Add("class", "accordion-top-li-first");
}
else
{
    liItem.Attributes.Add("style", "width: " + slideClosedWidth + "px");
 
    Literal ltArrow = (Literal)e.Item.FindControl("ltArrow");
    ltArrow.Text = "<img src='/SiteCollectionImages/but_arrow_lft_off.png' width='40px' height='40px' alt='accordion arrow' class='accordion_arrow' />";
 
    Literal ltVerticalShadowImg = (Literal)e.Item.FindControl("ltVerticalShadowImg");
    ltVerticalShadowImg.Text = "<img src='/SiteCollectionImages/img_vert_shadow.png' alt='vertical shadow' class='accordion_vertical_shadow' />";
}

Now that I got the webpart built, it’s time to talk about the css and jQuery.  To describe the css in a nutshell, I basically have a container of unordered list items (the panels) that are displayed inline.  There is a left vertical shadow to the left of each panel, so I had to also position each panel relative to each other and then set a negative margin on each panel equal to the width of the shadow image.  This creates an effect of each panel overlapping each other.

The jQuery portion is really where the “magic” happens.  From a usability standpoint, a user may click on the entire closed panel itself or only on the arrow on the closed panel.  So I made a determination to add the “click” event to the entire close panel.  This way, it doesn’t matter if the user clicks on the panel or the arrow…the event will still fire.  The main idea when animating the opening and closing of panels is to do 2 events in parallel:  open up the panel that was clicked on, and close the panel that is currently open.  I needed to perform these actions with the same animation duration.  If they aren’t occurring at the same time, the panels will float outside of the container, as the total widths of all panels in the container during the process will be greater than the overall container width.

To create the sliding effect, I made use of the jQuery animate() method.  The two key attributes is the “width” and “duration”.  Setting the “width” attribute specifies what width it needs to become.  In my case I needed to close the currently opened panel, so I set the “width” equal to a predetermined variable called “minWidth”:

// Close Previously Clicked Panel before Opening the current clicked panel
if (lastOpenedBlock.index() > 0)
{
    // Hide last clicked element
    $(lastOpenedBlock).animate(
        {width: minWidth}, 
        {queue:true, duration:1000, 
            complete: function()
            {
                closeAnimationIsRunning = false;
            },
            step: function()
            {
            }
        }
    );
}

Then I opened the selected panel using the same animate() method, but this is using a predetermined value called “maxWidth”:

$(currentBlock).animate(
    {width: minWidth}, 
    {queue:true, duration:1000, 
        complete: function()
        {
            closeAnimationIsRunning = false;
        },
        step: function()
        {
        }
    }
);

The “minWidth” and “maxWidth” variables are determined dynamically based on the first panel as this is the space we need to fill.  It is necessary to close a panel before opening another because these are sequential events.  If you begin to open a panel first, the combined total of panel widths will exceed the container widths and you will see an undesirable effect of the panels floating outside of the container.

One thing I had to consider was the possibility that the user may click on multiple panels during transition.  I found that during this scenario, there was a period of time when the page seemed to play “catch up”…the panels appeared to have a life of their own (opening and closing multiple times after the initial click).  To overcome this, I set two variables called “openAnimationIsRunning” and “closeAnimationIsRunning” that get set when the user clicks on a panel.  Within my “click” event, I first check these variables.  If either are true, then I stop the “click” event.  This prevents the user from clicking multiple panels while the animation is in motion:

$(".accordion-vert-header").click(
    function()
    {    
        // This allows one animation to run at a time.
        if (openAnimationIsRunning || closeAnimationIsRunning)
        {
            return;
        }
        
        openAnimationIsRunning = true;
        closeAnimationIsRunning = true;
}

The last consideration was what happens to the content within the panels during the transition.  I found that when I animated the panel opened/closed, the content within the panel needed to also animate open/close.  To solve this, I found that a nice fade worked well:

// Open current block content
$(currentBlock).children('.li-content').children('.accordion-html-content').fadeIn();

Accordions are a great way to consolidate content using minimal space, while creating an interactive experience for your users.  I provided a complex use of an accordion in my example, but I hope it allowed you to understand the elements and the process involved when building your own!

If you’d like to download the code, I am providing the webpart, jQuery, css, and list template I used in my example.

Comments

GetListData

I keep getting the error: The name 'Common' does not exist in teh current context. Its related to the line:
dt = Common.Common.GetListData(this.WebPart.SiteUrl, this.WebPart.ListUrl, queryXML);

Any help on this error? Thanks
 on 1/30/2012 1:18 PM

Re: Building an Accordion with jQuery and SharePoint 2010

The datatable is gathering data from the SharePoint list via a method I called "GetListData" in a separate class called "Common".  I updated the attached code to include this class file.
System Account on 1/31/2012 12:18 PM

Jquery is not getting called

Hi, I have added a reference to accordin.js but its still not getting called .Do you  know why
Thanks
 on 3/9/2012 10:54 AM

Re: Building an Accordion with jQuery and SharePoint 2010

If you're reference is correct, then you should be able to view the source of your rendered html, copy the accorion.js path into your browser and see the javascript.  If it doesn't render, then the path is incorrect.

Also, I use the Firebug addon in Firefox to view errors in the javascript (the console tab) when the page loads.

Hope this helps.
System Account on 3/9/2012 11:08 AM

accorion .js file syntax error

Hi, the accordion.js file has a syntax error. Need to remove the closing brace } after the closeAnimationIsRunning = true; in the click function. There were a couple of other things but I think this should fix the problem. Also, make sure you reference the jquery library.
 on 3/12/2012 11:41 PM

Re: Building an Accordion with jQuery and SharePoint 2010

Thank you so much I did changed the juery and it had a  "remove the closing brace } after the closeAnimationIsRunning = true" as you mentioned
 on 3/14/2012 5:18 PM

Re: Building an Accordion with jQuery and SharePoint 2010

Good catch on the syntax error in the code.  I revised it and put up a new copy, so that it doesn't happen to anyone else.

Thank you!
Tyler
System Account on 3/19/2012 9:30 AM

SharePoint Experts

Wow, I’ve definitely learned the techniques in this comprehensive tutorial. Thank you for sharing this information.
 on 4/3/2012 3:22 AM

Create WSP for deployment

Hi

Is it possible to make it as an wsp package so it can be deployed ?


Tried to upload the stp file - but can't create a accordian from that..
 on 4/17/2012 6:43 AM

WSP Package

Hi

Do you perhaps have a deployable package for this wonderful solution?
 on 6/14/2012 4:04 AM
1 - 10Next

Add Comment

Items on this list require content approval. Your submission will not appear in public views until approved by someone with proper rights. More information on content approval.

Title


Body *


Attachments


United States
518 Riverside Ave
Westport, CT 06880
 
1050 Bishop St.
Suite 176
Honolulu, HI 96813
United Kingdom
Unit 13 Elder Way Waterside Drive Langley Berkshire SL3 6EP
United Kingdom
Singapore
Level 37
Ocean Financial Centre
10 Collyer Quay
Singapore 049315
Asia Pacific
Level 6
115 Pitt Street
Sydney NSW 2000
Australia 
+1800-930-4771
+44 (0)1753 541 000
+(65) 6232 2329
+61 2 9113 7243
United States
United Kingdom
Singapore
Australia
This web page conforms to W3C's "Web Content Accessibility Guidelines 1.0" Level "A" © 2014 Synergy Corporate Technologies
This site is best viewed in IE8 or above. Some features may not render properly if you are using an older browser.