Creating a jQueryUI Accordion with a Data View Web Part

We can do all sorts of fun stuff that makes the user experience (UX) in SharePoint far better than it is out of the box. One of those things is to use jQueryUI widgets like an accordion to put a large amount of content on the page, but broken into categorized, consumable chunks. Most people are very accustomed to using things like accordions from other Web sites they visit regularly, and even if they aren’t things like accordions seem to make sense quickly.

I had forgotten about it, but I did a post earlier this year where I showed how Carlos Nunes-Ueno approached this. It turns out that our XSL looks similar, but here is another example based on an accordion I recently set up myself.

To start building this, we created two lists. The first list, which we called Accordion Headers, contains the names of the accordion panels plus a column we can use to adjust the order of the panels.

clip_image002

The other list, called Accordion Content, contains the accordion panel content, and has a Lookup column into the Title in the Accordion Headers list. We also pulled the Order column from the Accordion Headers list in with the header name. There are several other columns in this list that we used to construct the content for each accordion panel.

clip_image002[4]

Once the lists were in place and populated, I just added a Data View Web Part (DVWP) to a page with the Accordion Content list as its DataSource.

If you look at the jQueryUI documentation page for the accordion widget, you can see that it wants the markup to look like this:

<div id="accordion">
 <h3><a href="#">First header</a></h3>
 <div>First content</div>
 <h3><a href="#">Second header</a></h3>
 <div>Second content</div>
</div>

The XSL is simple, but you have to roll your own; SharePoint Designer knows nothing about how to structure the rendered markup for this. To do this with the Accordion Content list, the XSL looked like this. Our panels contain unordered lists of links.

<xsl:template match="/" xmlns:x="http://www.w3.org/2001/XMLSchema" xmlns:d="http://schemas.microsoft.com/sharepoint/dsp" xmlns:asp="http://schemas.microsoft.com/ASPNET/20" xmlns:__designer="http://schemas.microsoft.com/WebParts/v2/DataView/designer" xmlns:SharePoint="Microsoft.SharePoint.WebControls">
  <xsl:call-template name="AccordionSetup"/>
</xsl:template>

<xsl:template name="AccordionSetup">
  <xsl:variable name="Rows" select="/dsQueryResponse/Rows/Row"/>
  <div id="left-accordion" style="width: 300px;">
    <xsl:for-each select="$Rows">
      <xsl:sort select="@AccHeading_x003a_Order" data-type="number"/>
      <xsl:sort select="@Order" data-type="number"/>
      <xsl:call-template name="AccordionPanels">
        <xsl:with-param name="Rows" select="$Rows"/>
      </xsl:call-template>
    </xsl:for-each>
  </div>
</xsl:template>

<xsl:template name="AccordionPanels">
  <xsl:param name="Rows"/>
  <xsl:variable name="NewHeading" select="ddwrt:NameChanged(string(@AccHeading.), 0)"/>
  <xsl:if test="string-length($NewHeading) &gt; 0">
    <h3>
      <a href="#{substring-before(@AccHeading., ';#')}">
        <xsl:value-of select="substring-after(@AccHeading., ';#')"/>
      </a>
    </h3>
    <div>
      <ul>
        <xsl:variable name="PanelRows" select="$Rows[@AccHeading. = current()/@AccHeading.]"/>
        <xsl:for-each select="$PanelRows">
          <xsl:call-template name="AccordionPanelContent"></xsl:call-template>
        </xsl:for-each>
      </ul>
    </div>
  </xsl:if>
</xsl:template>

<xsl:template name="AccordionPanelContent">
  <li>
    <xsl:value-of select="@Title"/> - <a href="{@ContentLink}"><xsl:value-of select="@ContentLink.desc"/></a>
  </li>
</xsl:template>

It’s pretty simple, really, and what we ended up with is a nicely organized, list-driven accordion which is easy to use and easy to maintain. No developer required to make changes whatsoever. It’s a win-win all around.

clip_image002[6]

<UPDATE dateTime=”2011-11-03T09:26″>

It occurred to me, though no one complained, that I hadn’t shown the jQuery to activate the accordion. Getting the markup right is really the hard part, as the jQuery can be as simple as this:

$("#left-accordion").accordion();

In this particular case, we set a few options as well:

$("#left-accordion").accordion({
  active:false,
  autoHeight: false,
  collapsible: true
});

I’ll leave it to you to check the jQueryUI docs to see what each option does.

</UPDATE>

Setting Up a DVWP to Use jQueryUI Accordion and Tabs

SPXSLTCarlos Nunes-Ueno posted some XSL you can use in a DVWP to the SPXSLT Codeplex Project a while back which allows you to easily utilize the jQueryUI accordion and tabs functions. I asked him if he’d mind if I did a blog post on it, and he agreed. (If you happen to read the thread, I admit that I had just gotten back from vacation at the time and thought that the post came from the SPServices Discussions. That’s why I sound confused – I was.)

Here’s what Carlos told me about himself:

I’ve been working with SharePoint pretty much exclusively for the past year.  I work for a major online retailer on internally facing sites and I’ve been focused primarily on getting processes off paper.  My previous background is mainly in MS Access.  I’m still working on the ins and outs of SharePoint and trying to wring as much functionality as I can out of it.  I have found jQuery and jQueryUI to be extremely useful tools for that.

Accordions and tabs are two of the popular widgets offered by jQueryUI. In case you haven’t seen them in action, here are some simple examples. (The links will take you to the jQueryUI site, where there are live examples.)

Accordion

accordionTabs

tabsBy structuring your markup in a specific way, you can make simple calls to jQueryUI to re-shape what you’ve rendered on the page into these cool user interface elements. Using a Data View Web Part (DVWP), you can pull the labels for the sections and content in the accordion or the tabs names and content from lists. All it takes is emitting some well-structured markup in your XSL.

Here are Carlos’ examples:

Accordion

<xsl:template name="dvt_1">
  <xsl:variable name="dvt_StyleName">Table</xsl:variable>
  <xsl:variable name="Rows" select="/dsQueryResponse/Rows/Row"/>
  <xsl:variable name="dvt_RowCount" select="count($Rows)" />
  <xsl:variable name="dvt_IsEmpty" select="$dvt_RowCount = 0" />
  <xsl:choose>
    <xsl:when test="$dvt_IsEmpty">
      <xsl:call-template name="dvt_1.empty" />
    </xsl:when>
    <xsl:otherwise>
        <div id="accordion">
          <xsl:call-template name="accordionMarkup">
            <xsl:with-param name="Rows" select="$Rows" />
          </xsl:call-template>
        </div>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

<xsl:template name="accordionMarkup">
  <xsl:param name="Rows" />
  <xsl:for-each select="$Rows">
    <h3>
      <a href="#">
        <xsl:value-of select="@Title" />
      </a>
    </h3>
    <div>
      <xsl:value-of select="@panelText" disable-output-escaping="yes" />
    </div>
  </xsl:for-each>
</xsl:template>

<xsl:template name="dvt_1.empty">
  <xsl:variable name="dvt_ViewEmptyText">No items.</xsl:variable>
  <table border="0" width="100%">
    <tr>
      <td class="ms-vb">
        <xsl:value-of select="$dvt_ViewEmptyText" />
      </td>
    </tr>
  </table>
</xsl:template>

Tabs

<xsl:template name="dvt_1">
  <xsl:variable name="dvt_StyleName">Table</xsl:variable>
  <xsl:variable name="Rows" select="/dsQueryResponse/Rows/Row"/>
  <xsl:variable name="dvt_RowCount" select="count($Rows)" />
  <xsl:variable name="dvt_IsEmpty" select="$dvt_RowCount = 0" />
  <xsl:choose>
    <xsl:when test="$dvt_IsEmpty">
      <xsl:call-template name="dvt_1.empty" />
    </xsl:when>
    <xsl:otherwise>
        <div id="tabs">
          <xsl:call-template name="tabsList">
            <xsl:with-param name="Rows" select="$Rows" />
          </xsl:call-template>
          <xsl:call-template name="tabsPanels">
            <xsl:with-param name="Rows" select="$Rows" />
          </xsl:call-template>
        </div>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

<xsl:template name="tabsList">
  <xsl:param name="Rows" />
  <ul>
    <xsl:for-each select="$Rows">
      <li><a href="{concat('#tabs-',@ID)}"><xsl:value-of select="@Title" /></a></li>
    </xsl:for-each>
  </ul>
</xsl:template>

<xsl:template name="tabsPanels">
  <xsl:param name="Rows" />
  <xsl:for-each select="$Rows">
    <div id="{concat('tabs-',@ID)}">
      <xsl:value-of select="@panelText" disable-output-escaping="yes" />
    </div>
  </xsl:for-each>
</xsl:template>

<xsl:template name="dvt_1.empty">
  <xsl:variable name="dvt_ViewEmptyText">No items.</xsl:variable>
  <table border="0" width="100%">
    <tr>
      <td class="ms-vb">
        <xsl:value-of select="$dvt_ViewEmptyText" />
      </td>
    </tr>
  </table>
</xsl:template>

One of the nice things about jQueryUI is that using the basic functionality once you have the markup in place requires very little script.

For an accordion:

$(document).ready(function(){
  $("#accordion").accordion();
});

For tabs:

$(document).ready(function(){
  $("#tabs").tabs();
});

There are various options for both functions which you can read about on the jQueryUI site, but the simple script above gets you the default functionality. Obviously, you’d need to reference the jQuery and jQueryUI files and link to the jQueryUI css, but if you’ve used jQueryUI, or even just jQuery, you are probably familiar with how to do that.

Thanks, Carlos!