Group items in a data view with the ability to nest items
SharePoint Designer’s typical Sort&Group is good for basic needs. But if you need to do some nesting (like nested bulleted lists of groupped items) the standard (GUI) way with SharePoint Designer just won’t do, since all the elements (even the members of a group) are not nested. Let me explain a bit more in detail what I have in mind. Let’s have a simple list with fields Full Name and Company. (both single line of text, or you can have the "Company" as lookup. Doesn’t matter at this stage.
So we want to create a list like follows:
- B4Contact
- Boris Gomiunik
- Jose Morales
- Company1
- John Doe
- John Dooe
- Company2
- Jane Doe
The example above ofcourse is done by having the nested bulleted lists (<ul>). To achieve this let’s open the site in SharePoint Designer and insert the list’s Data View into the page. (I usually do this by dragging the list from Data Source Library to the page.
Now let’s change the layout to something very simple – a bulleted list.
Before we dig in, we need to sort according to the field we want it groupped by.
Nothing fancy untill this point. Now it’s time to switch to the Code or Split View.
We want the first level to display company name, so let’s locate the
<xsl:value-of select="@Title" />
(I’ve renamed the Title Field to Full Name. In case you’re replacing any other field, locate it and then change it to
<xsl:value-of select="@Company" />
(or whichever field you’ll want displayed in the first level).
Let’s do some grouping now. Next in your code locate the following part
<xsl:for-each select="$Rows"> <xsl:call-template name="dvt_1.rowview" /> </xsl:for-each>
And change the first line of that part to:
<xsl:for-each select="$Rows[@Company != preceding-sibling::Row[1]/@Company or position() = 1]">
Note the filter in square brackets that is added. Ofcourse replace @Company for the SharePoint field name you’re grouping with. This will result in a groupped first level – Companies.
Now let’s start building the nested subview. First let’s move the <li> element from template dvt_1.rowview to dvt_1.body to avoid too many xsl:templates and add a nested <ul>. In the code below the first sample is the original and the other is changed.
<xsl:template name="dvt_1.body">
<xsl:param name="Rows" />
<xsl:for-each select="$Rows[@Company != preceding-sibling::Row[1]/@Company or position() = 1]">
<xsl:call-template name="dvt_1.rowview" />
</xsl:for-each>
</xsl:template>
<xsl:template name="dvt_1.rowview">
<li class="ms-vb">
<xsl:value-of select="@Company" />
<xsl:if test="$dvt_1_automode = '1'" ddwrt:cf_ignore="1">
<br /><span ddwrt:amkeyfield="ID" ddwrt:amkeyvalue="ddwrt:EscapeDelims(string(@ID))" ddwrt:ammode="view" />
</xsl:if>
</li></xsl:template>
<xsl:template name="dvt_1.body"> <xsl:param name="Rows" /> <xsl:for-each select="$Rows[@Company != preceding-sibling::Row[1]/@Company or position() = 1]"> <li class="ms-vb"><xsl:value-of select="@Company" /> <ul> <xsl:call-template name="dvt_1.rowview" /> </ul></li> </xsl:for-each> </xsl:template> <xsl:template name="dvt_1.rowview"> </xsl:template>
Now that we’ve cleared the xsl template dvt_1.rowview we can use it for the nested list of employees. But first we need to pass some parameters to that template – more specifically – all the rows and company name for filtering. So replace the line
<xsl:call-template name="dvt_1.rowview" />
with
<xsl:call-template name="dvt_1.rowview"> <xsl:with-param name="Rows" select="$Rows" /> <xsl:with-param name="company" select="@Company" /> </xsl:call-template>
Now let’s edit the dvt_1.rowview template. Locate the
<xsl:template name="dvt_1.rowview">
and add the following below:
<xsl:param name="Rows" /> <xsl:param name="company" />
After those two add the following:
<xsl:for-each select="$Rows[@Company = $company]"> <li class="ms-vb"><xsl:value-of select="@Title" /></li> </xsl:for-each>
and you’re done. Now you have a nested grouped data view
To sum up the whole XSL code is:
<XSL> <xsl:stylesheet xmlns:x="http://www.w3.org/2001/XMLSchema" xmlns:d="http://schemas.microsoft.com/sharepoint/dsp" version="1.0" exclude-result-prefixes="xsl msxsl ddwrt" xmlns:ddwrt="http://schemas.microsoft.com/WebParts/v2/DataView/runtime" xmlns:asp="http://schemas.microsoft.com/ASPNET/20" xmlns:__designer="http://schemas.microsoft.com/WebParts/v2/DataView/designer" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:SharePoint="Microsoft.SharePoint.WebControls" xmlns:ddwrt2="urn:frontpage:internal"> <xsl:output method="html" indent="no"/> <xsl:decimal-format NaN=""/> <xsl:param name="dvt_apos">'</xsl:param> <xsl:variable name="dvt_1_automode">0</xsl:variable> <xsl:template match="/"> <xsl:call-template name="dvt_1"/> </xsl:template> <xsl:template name="dvt_1"> <xsl:variable name="dvt_StyleName">BulTitl</xsl:variable> <xsl:variable name="Rows" select="/dsQueryResponse/Rows/Row" /> <ul> <xsl:call-template name="dvt_1.body"> <xsl:with-param name="Rows" select="$Rows" /> </xsl:call-template> </ul> </xsl:template> <xsl:template name="dvt_1.body"> <xsl:param name="Rows" /> <xsl:for-each select="$Rows[@Company != preceding-sibling::Row[1]/@Company or position() = 1]"> <li class="ms-vb"><xsl:value-of select="@Company" /> <ul> <xsl:call-template name="dvt_1.rowview"> <xsl:with-param name="Rows" select="$Rows" /> <xsl:with-param name="company" select="@Company" /> </xsl:call-template> </ul></li> </xsl:for-each> </xsl:template> <xsl:template name="dvt_1.rowview"> <xsl:param name="Rows" /> <xsl:param name="company" /> <xsl:for-each select="$Rows[@Company = $company]"> <li class="ms-vb"><xsl:value-of select="@Title" /></li> </xsl:for-each> </xsl:template> </xsl:stylesheet> </XSL>
In the code above pay special attention to the part from <xsl:template name="dvt_1.body"> section.
This way you can nest lists, layers or any elements you want.



Nice Blog Boris!
I like it more than the previous one
Great stuff. Is it possible to add a third level to group by? Using your example could you add phone numbers to employees that are grouped by employees. The full hierarchy would be Company -> Employee -> Contact Number. This third level and beyond seems to be the breaking point of XSLT data views.
Thanks
Do you have multiple contact numbers per employee? If not, you can just make a sub-list. For example if you’re using a nested bulleted list <ul>, you can just add inside of <li> another list with the contact number. Otherwise you can group them by creating a third xsl:template and call it with param of $Rows, $Company and $Employee.
Then in that template you can create
<xsl:for-each select=”$Rows[@Company = $Company and @Employee = $Employee]“>
<xsl:value-of select=”@ContactNumber”/>
</xsl:for-each>
Hope I didn’t complicate too much.
I’m using an ASP.NET connection to a .mdb, do i just use the “list” markup around the asp code – do you have an example? Forgive – newbie to designer, web dev, etc. Goal is to create a nested list – using sql server data base
Basically what is important is the XSLT part. If you’re using DVWP (dataviewwebpart) the use is very similar. What might come in handy in SharePoint designer is the “Data View Details” taskpane where you can find all the nodes info.
Hi Boris, Do you have an updated version for SharePoint and Designer 2010?