Sorting in a DVWP by a Column in Another List

This is something that I’ve tried to solve many times and each time I’ve ended up doing something more complicated than I’ve needed to, but I knew would work.  I finally bit the bullet and solved it the right way.

Here’s the scenario:

  • You have a primary list with many items in it
  • One of the columns in this list is a lookup column into another list
  • The second list also has a column called SortOrder, which contains a number indicating where in the sort order the lookup value ought to be displayed
  • When you display the items from the first list, you want to sort them by the corresponding SortOrder value in the second list

Let’s run through the scenario with these two lists:

Primary List

Title Color
Title A Green
Title B Green
Title C Yellow
Title D Red

Secondary List

Color SortOrder
Red 1
Yellow 2
Green 3

In your DWVP, you’ll have a template to gather the right rows from the primary list, then an xsl:for-each where you will do the sort.  The xsl:sort needs to look something like this:

<xsl:sort select=”/dsQueryResponse/secondary_list/Rows/Row[@Color = current()/@Color]/@SortOrder”/>

Assuming that you have a secondary sort of the Title column (<xsl:sort select=”@Title”/>), the results will be returned like this:

  • Title D
  • Title C
  • Title A
  • Title B

Simple little line, but it was a dickens to figure out the exact syntax.  Here’s how it breaks down:

The /dsQueryResponse/secondary_list/Rows/Row says to go and get the rows from the secondary list.  Anything within brackets filters that result set, so with [@Color = current()/@Color] we’re saying rows where the Color in the secondary_list equals the Color column in the current result set (the result set from the primary_list). Finally, the /@SortOrder grabs the value in the SortOrder column.

For all of this to work properly, you need to know that your data model will support this – you want to be sure that there is only one row per Color in the secondary_list.  You also have to have your DVWP set up with an AggregateDataSource containing both lists (in this case, they are named primary_list and secondary_list in the DataSource).

Sorting Displays in Data View Web Parts

I figured out a great little trick for sorting a DVWP-based display based on a column name passed in the Query String.  You can see this in action in the default All Items or All Documents views in lists or libraries.  When you click on the column headers that allow sorting, the browser is sent to the same page with the Query String looking something like:

http://[servername]/Lists/[listname]Forms/AllItems.aspx?SortField=ContentType&SortDir=Asc

By passing the column name that you want to sort on in the Query String, the List View Web Part “knows” what to sort the display on and in what order.

If you want to build analogous functionality in a DVWP, you can easily rig the column headers to act the same way, but the trick is to get the items to then sort correctly.  In your body template, you need some XSL like the following:

<xsl:variable name="AscDesc">
 <xsl:choose>
  <xsl:when test="string-length($SortDir) = 0 or $SortDir = 'Asc'">ascending</xsl:when>
  <xsl:otherwise>descending</xsl:otherwise>
 </xsl:choose>  
</xsl:variable>
<xsl:for-each select="$Rows">
 <xsl:sort select="@*[name()=$SortField]" order="{$AscDesc}" />  
 <xsl:call-template name="Community_ContentDetailed.rowview">
  <xsl:with-param name="SiteType" select="$SiteType" /> 
  <xsl:with-param name="CommunityContentFilterValue" select="$CommunityContentFilterValue" /> 
 </xsl:call-template>
</xsl:for-each>

The cool line is #8.  It allows you to take the Query String value for SortField and use it as the column name by which to sort.  The select parameter in the xsl:sort evaluates to @ContentType (from the URL above).

I wanted to be able to easily add the sorting option to header columns in my DVWPs, so I created the following utility template to create the links.  It decides based on the Query String values whether this is the first or second click of the column header (ascending or descending) and shows the appropriate image indicator for the result.

 <xsl:template name="SortHeader">
  <xsl:param name="LinkText"/>
  <xsl:param name="ColumnName"/>
  <xsl:param name="SortField"/>
  <xsl:param name="SortDir"/>
  <xsl:variable name="ASortIMG" select="'/_layouts/images/sortaz.gif'"/>
  <xsl:variable name="DSortIMG" select="'/_layouts/images/sortza.gif'"/>
  <xsl:variable name="SortableIMG" select="'/SiteCollectionImages/sortable.gif'"/>
  <xsl:variable name="AscDesc">
   <xsl:choose>
    <xsl:when test="$ColumnName = $SortField and (string-length($SortDir) = 0 or $SortDir = 'Asc')">Desc</xsl:when>
    <xsl:otherwise>Asc</xsl:otherwise>
   </xsl:choose>
  </xsl:variable>
  <img alt="Sortable" border="0" src="{$SortableIMG}"/>
  <a href="#">
   <xsl:attribute name="onclick">
    document.location = '<xsl:value-of select="$URL"/>' +
     '?SortField=' + '<xsl:value-of select="$ColumnName"/>' +
     '&amp;SortDir=' + '<xsl:value-of select="$AscDesc"/>';
   </xsl:attribute>
   <xsl:value-of select="$LinkText"/>
   <span style="padding-left:2px;">
   <xsl:choose>
    <xsl:when test="$ColumnName = $SortField and ($SortDir = '' or $SortDir = 'Asc')">
     <img alt="Ascending - Click to Reverse" border="0" src="{$ASortIMG}"/>
    </xsl:when>
    <xsl:when test="$ColumnName = $SortField and $SortDir = 'Desc'">
     <img alt="Descending - Click to Reverse" border="0" src="{$DSortIMG}"/>
    </xsl:when>
    <xsl:otherwise>
    </xsl:otherwise>
   </xsl:choose>
   </span>
  </a>
 </xsl:template>

Sorting by Number in DVWPs

If you set up a sort in a DVWP that has an aggregate Data Source, you’ll need to use the <xsl:sort> tag.  If you want to sort by a list column that is numeric, the tag:
<xsl:sort select=”@JobFamilyRank” order=”ascending”/>
will sort your data like: 1, 10, 11, 12, 2, 20, 21, 3…  That is, SharePoint will treat your column as if it contains left justified text.
 
Be sure to add the data-type parameter:
<xsl:sort select=”@JobFamilyRank” order=”ascending” data-type=”number”/>
and then the sort will work as you wanted: 1, 2, 3, 10, 11, 12, 20, 21,…