Take the 2-minute tour ×
SharePoint Stack Exchange is a question and answer site for SharePoint enthusiasts. It's 100% free, no registration required.

I am using a linked datasource in MOSS to create a dynamic table of contents page based on a metadata associated with files in a document library. The library contains multilookup columns for the following: Audience, Category, Subcategory. Users get to the toc page by clicking from another page to pass the Audience in the query string. The toc page will filter the categories based on Audience.

The categories are displayed using the categories list and then the subcategories are matched to the respective category based on the join.

The original problem was that these were delimited strings. I broke the strings out using a recursive template. Now my problem is removing duplicates. I am able to remove the subcategory if it matches up exactly, but because of the multiple values this almost never happens. Is there a way to do this using xslt or will I need to look into some sort of jquery.

<xsl:stylesheet xmlns:xs="http://www.w3.org/2001/XMLSchema" 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:param name="Audience" />
<xsl:param name="HelpURL" />
<xsl:variable name="dvt_1_automode">0</xsl:variable>

<xsl:template match="/" xmlns:xs="http://www.w3.org/2001/XMLSchema" 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="CategoryList"/>
</xsl:template>

<xsl:template name="CategoryList">
    <xsl:variable name="dvt_StyleName">2ColCma</xsl:variable>
    <xsl:variable name="Rows" select="/dsQueryResponse/Categories/Rows/Row" />

    <link rel="stylesheet" type="text/css" href="/_layouts/1033/styles/controls.css"/>

    <table border="0" width="100%">
        <tr>
            <xsl:call-template name="CategoryList.body">
                <xsl:with-param name="Rows" select="$Rows" />
            </xsl:call-template>
        </tr>
    </table>
</xsl:template>

<xsl:template name="CategoryList.body">
    <xsl:param name="Rows" />
    <xsl:for-each select="$Rows">
        <xsl:sort select="../../../Rows/Row/@Title" order="ascending" />
        <xsl:call-template name="CategoryList.rowview" />
    </xsl:for-each>             
</xsl:template>

<xsl:template name="CategoryList.rowview">
<xsl:variable name="ParentCategory" select="@Title" />
<xsl:variable name="DocumentRows" select="../../../Documents/Rows/Row[contains(@Category, $ParentCategory) and contains(@Audience, $Audience)]" />
<xsl:variable name="RowCount" select="count($DocumentRows)" />
<xsl:variable name="dvt_IsEmpty" select="$RowCount = 0" />
<xsl:if test="$RowCount &gt; 0">
    <td valign="top" width="50%">
        <table border="0" cellspacing="0" width="100%">
            <tr class="ms-WPHeader" >
                <td width="75%" class="ms-standardheader ms-WPTitle">
                    <xsl:value-of select="@Title" /></td>
            </tr>
            <xsl:if test="$dvt_1_automode = '1'" ddwrt:cf_ignore="1">
                <tr>
                    <td colspan="99" class="ms-vb">
                        <span ddwrt:amkeyfield="" ddwrt:amkeyvalue="string($XPath)" ddwrt:ammode="view" />
                    </td>
                </tr>
            </xsl:if>
            <tr>
                <td width="75%" class="ms-vb">
                    <xsl:call-template name="Documents" />
                </td>
            </tr>
        </table>
    </td>

    <xsl:if test="position() mod 2 = 0" ddwrt:cf_ignore="1">
        <xsl:text disable-output-escaping="yes">&lt;/tr&gt;</xsl:text>
            <xsl:if test="position() != last()" ddwrt:cf_ignore="1">
                <xsl:text disable-output-escaping="yes">&lt;tr&gt;</xsl:text>
            </xsl:if>
    </xsl:if>
    </xsl:if>
</xsl:template>

<xsl:variable name="dvt_2_automode">0</xsl:variable>
<xsl:template name="Documents">
    <xsl:variable name="dvt_StyleName">Table</xsl:variable>
    <xsl:variable name="dvt_ParentRow" select="current()" />
    <xsl:variable name="Rows" select="../../../Documents/Rows/Row[contains(@Category, $dvt_ParentRow/@Title) and contains(@Audience, $Audience)]" />
    <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>
            <table border="0" width="100%" cellpadding="2" cellspacing="0">

            <xsl:call-template name="Documents.body">
                    <xsl:with-param name="Rows" select="$Rows" />
                    <xsl:with-param name="dvt_ParentRow" select="$dvt_ParentRow" />
                </xsl:call-template>
            </table>    
        </xsl:otherwise>
    </xsl:choose>
</xsl:template>

<xsl:template name="Documents.body">
    <xsl:param name="Rows" />
    <xsl:param name="dvt_ParentRow" />
    <xsl:for-each select="$Rows">
    <xsl:sort select="@Subcategory" />
        <xsl:call-template name="Documents.rowview" >
                        <xsl:with-param name="dvt_ParentRow" select="$dvt_ParentRow/@Title" />
        </xsl:call-template>
    </xsl:for-each>
</xsl:template>

<xsl:template name="Documents.rowview">
<xsl:param name="dvt_ParentRow" />
<xsl:variable name="SubcatRow" select="ddwrt:NameChanged(string(@Subcategory), 0)" />
    <xsl:if test="string-length($SubcatRow)">
        <tr>
            <td class="ms-vb">
                <xsl:call-template name="subcategoryLinks">
                    <xsl:with-param name="subcategory" select="@Subcategory" />
                    <xsl:with-param name="SingleCategory" select="$dvt_ParentRow" />
                </xsl:call-template>
            </td>                   
        </tr>
    </xsl:if>
</xsl:template>

<!--Template used to determine subcategories -->
<xsl:template name="subcategoryLinks">
    <xsl:param name="SingleCategory" />
    <xsl:param name="subcategory" />
    <xsl:variable name="firstSubcat" select="substring-before($subcategory, ';')" />
    <xsl:variable name="remainSubcat" select="substring-after($subcategory, ';')" />

    <xsl:choose>
        <!--Tests to see if there are more than 1 subcategory-->
        <xsl:when test="not(contains(@Subcategory, ';'))">
            <div id="subLink" class="bullet" style="font-size:10pt;">
                <a href="{$HelpURL}{$Audience}&amp;Category={$SingleCategory}&amp;SubCategory={@Subcategory}"><xsl:value-of select="@Subcategory" /></a>
            </div>          
        </xsl:when>
        <!--Runs if more than 1 subcategory-->
        <xsl:otherwise>
            <!--Creates subcategory row-->
            <div id="subLink" class="bullet" style="font-size:10pt;">
                <a href="{$HelpURL}{$Audience}&amp;Category={$SingleCategory}&amp;SubCategory={$firstSubcat}">
                    <xsl:value-of select="$firstSubcat" />                  </a>
            </div>
            <xsl:choose>
                <!--Tests to see if more than 1 subcategory remains, if so it will rerun the template-->
                <xsl:when test="contains($remainSubcat, ';')" >
                    <xsl:call-template name="subcategoryLinks">
                        <xsl:with-param name="subcategory" select="$remainSubcat" />
                        <xsl:with-param name="SingleCategory" select="$SingleCategory" />
                    </xsl:call-template>
                </xsl:when>
                <xsl:otherwise>         
                    <div id="subLink" class="bullet" style="font-size:10pt;">
                        <a href="{$HelpURL}{$Audience}&amp;Category={$SingleCategory}&amp;SubCategory={$remainSubcat}"><xsl:value-of select="$remainSubcat" /></a>
                    </div>              
                </xsl:otherwise>
            </xsl:choose>   
        </xsl:otherwise>
    </xsl:choose>
</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" style="height: 17px">
                <xsl:value-of select="$dvt_ViewEmptyText" />
            </td>
        </tr>
    </table>
</xsl:template>

share|improve this question

2 Answers 2

It seems like you should stick with XSLT. You are almost there with your template, you just have to remove dupes. What you could within your template is keep track of the categories that have already been passed through your template. For example adding a parameter to your template called AllCategories:

<xsl:template name="subcategoryLinks">
    <xsl:param name="AllCategories" />
    <xsl:param name="SingleCategory" />
    <xsl:param name="subcategory" />
    <xsl:variable name="firstSubcat" select="substring-before($subcategory, ';')" />
    <xsl:variable name="remainSubcat" select="substring-after($subcategory, ';')" />

Then you'll be able to stuff a variable full of the text that has already passed through your template. When you emit elements to the DOM, you can check to see if this variable "contains" an existing string. If it does, then simply skip creating an element.

Here's some sample code that I've not tested, but should get you up and running:

            <xsl:variable name="categoryTracker">
                <xsl:value-of select="$AllCategories" /><xsl:text>;</xsl:text><xsl:value-of select="$firstSubcat" />
            </xsl:variable>

            <!--Tests to see if more than 1 subcategory remains, if so it will rerun the template-->
            <xsl:when test="contains($remainSubcat, ';')" >
                <xsl:call-template name="subcategoryLinks">
                    <xsl:with-param name="subcategory" select="$remainSubcat" />
                    <xsl:with-param name="SingleCategory" select="$SingleCategory" />
                </xsl:call-template>
            </xsl:when>
share|improve this answer
    
Thanks for the insight. I was actually able to get rid of the recusive template using a third join. I will post my solution, though I am sure this would have worked as well. The only thing I am left with is fighting a two column layout on the page when a category doesnt exist, but thats a separate thread. –  Perplexed Mar 15 '12 at 13:10
    
I think that's where jQuery will be of assistance. Glad you got this sorted out. Looks like a very neat solution! –  iOnline247 Mar 15 '12 at 15:11
up vote 1 down vote accepted

I was able to change this up a bit by adding another join using my subcategories list. I basically added in a filter to check the count to see if any subcategories existed within that category.

        <xsl:call-template name="CategoryList"/>
</xsl:template>

<xsl:template name="CategoryList">
    <xsl:variable name="dvt_StyleName">2ColCma</xsl:variable>
    <xsl:variable name="Rows" select="/dsQueryResponse/Categories/Rows/Row" />

    <link rel="stylesheet" type="text/css" href="/_layouts/1033/styles/controls.css"/>

    <table border="0" width="100%">
        <tr>
            <xsl:call-template name="CategoryList.body">
                <xsl:with-param name="Rows" select="$Rows" />
            </xsl:call-template>
        </tr>
    </table>
</xsl:template>

<xsl:template name="CategoryList.body">
    <xsl:param name="Rows" />
    <xsl:for-each select="$Rows">
        <xsl:sort select="../../../Rows/Row/@Title" order="ascending" />
        <xsl:call-template name="CategoryList.rowview" />
    </xsl:for-each>             
</xsl:template>

<xsl:template name="CategoryList.rowview">
<xsl:variable name="ParentCategory" select="@Title" />
<xsl:variable name="DocumentRows" select="../../../Documents/Rows/Row[contains(@Category, $ParentCategory) and contains(@Audience, $Audience)]" />
<xsl:variable name="RowCount" select="count($DocumentRows)" />
<xsl:if test="$RowCount &gt; 0">
    <td valign="top" width="50%">
        <table border="0" cellspacing="0" width="100%">
            <tr class="ms-WPHeader" >
                <td width="75%" class="ms-standardheader ms-WPTitle">
                    <xsl:value-of select="@Title" /></td>
            </tr>
            <xsl:if test="$dvt_1_automode = '1'" ddwrt:cf_ignore="1">
                <tr>
                    <td colspan="99" class="ms-vb">
                        <span ddwrt:amkeyfield="" ddwrt:amkeyvalue="string($XPath)" ddwrt:ammode="view" />
                    </td>
                </tr>
            </xsl:if>
            <tr>
                <td>
                    <xsl:call-template name="SubcategoryList" />
                </td>
            </tr>
        </table>
    </td>
    <xsl:if test="position() mod 2 = 0" ddwrt:cf_ignore="1">
        <xsl:text disable-output-escaping="yes">&lt;/tr&gt;</xsl:text>
            <xsl:if test="position() != last()" ddwrt:cf_ignore="1">
                <xsl:text disable-output-escaping="yes">&lt;tr&gt;</xsl:text>
            </xsl:if>
    </xsl:if>                                                       
    </xsl:if>
</xsl:template>

<xsl:template name="SubcategoryList" >
    <xsl:variable name="dvt_ParentRow" select="current()" />
    <xsl:variable name="Rows" select="../../../Subcategories/Rows/Row" />
    <xsl:call-template name="SubcategoryList.body">
        <xsl:with-param name="Rows" select="$Rows" />
        <xsl:with-param name="dvt_ParentRow" select="$dvt_ParentRow" /> 
    </xsl:call-template>
</xsl:template>

<xsl:template name="SubcategoryList.body" >
    <xsl:param name="Rows" />
    <xsl:param name="dvt_ParentRow" />
    <xsl:for-each select="$Rows">
    <xsl:sort select="@Title" />
        <xsl:call-template name="SubcategoryList.rowview" >
                        <xsl:with-param name="dvt_ParentRow" select="$dvt_ParentRow" /> 
        </xsl:call-template>
    </xsl:for-each>
</xsl:template>

<xsl:template name="SubcategoryList.rowview" >
<xsl:param name="dvt_ParentRow" />
<xsl:variable name="ParentSubcategory" select="@Title" />
<xsl:variable name="DocumentRows" select="../../../Documents/Rows/Row[contains(@Subcategory, $ParentSubcategory) and contains (@Category, $dvt_ParentRow/@Title) and contains(@Audience, $Audience)]" />
<xsl:variable name="RowCount" select="count($DocumentRows)" />
<xsl:if test="$RowCount &gt; 0">
    <tr>
        <td class="bullet" style="font-size:10pt;">
            <a href="{$HelpURL}{$Audience}&amp;Category={$dvt_ParentRow/@Title}&amp;SubCategory={@Title}">
            <xsl:value-of select="@Title" /></a>
        </td>
    </tr>                           
</xsl:if>                                                   
</xsl:template>

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.