Managed Metadata Web Part

update - 14/07/2011
I forgot to mention that you have to deploy the .wsp as a farm solution.
And another advice: if you are using this web part in an Enterprise Wiki site and you want to show the wiki categories, set the following properties like this:
  • ListName: pages
  • FieldName: Wiki_x0020_Page_x0020_Categories

update - 05/06/2011
The solution (.wsp) and source code are now available for download.


There is en excellent CodePlex project called SharePoint 2010 Managed Metadata WebPart (http://metadatawebpart.codeplex.com/.) It's a web part that displays a navigation tree based on a managed metadata column assigned to a list/library. I modified it a bit and now it works exactly as I need:
Taxonomy Tree
The changes I made are as follows:
  • link and item count are displayed only on terms that have items associated with them
  • the link supports variables such as term and list GUIDs, this way you can link to the Catergories page and dynamically get all the items at the specified category
  • a nice tag icon precedes the term
  • the web part now supports RTL languages (such as Hebrew)
  • added a fix to support internal field names (http://metadatawebpart.codeplex.com/discussions/220164)
The changes are in 2 places only: the web part code (Taxonomy_WebPart.cs) and the XSL you insert in the XSL String web part property. Here's what you need to change in the original project in order to get the same results:

Web part code - Taxonomy_WebPart.cs

I made some changes to the code to insert the variables for the URL.
Find this comment:
// get our term hierarchy
This should appear afterwards:
// get our term hierarchy
xmlOutput += "" + termSet.Name + "";
xmlOutput += "" + myWeb.Url + "";
xmlOutput += "" + myList.ID.ToString() + "";
xmlOutput += "" + FieldName + "";
xmlOutput += "";
GetRootTerms(termSet);
xmlOutput += "
";
Find this method:
private void outputTerm(Term t)
This should be its content:
private void outputTerm(Term t)
{
    xmlOutput += "<term>";
    xmlOutput += "<name>" + t.Name + "</name>";
    xmlOutput += "<rootTermID>" + rootTermCount.ToString() + "</rootTermID>";
    xmlOutput += "<itemcount>" + GetTermCount(t.Id) + "</itemcount>";
    xmlOutput += "<termGuid>" + t.Id.ToString() + "</termGuid>";
    if (t.Terms.Count > 0 && CurrDepth < Depth - 1)
    {
        GetTerms(t);
    }
    xmlOutput += "</term>";
}
Here's a sample of the XML this code generates:
<termset>
    <name>Wiki Categories</name>
    <webUrl>http://server/Wiki</webUrl>
    <listId>102afb60-f8fc-4755-96ff-e2045da851ba</listId>
    <fieldName>Wiki_x0020_Page_x0020_Categories</fieldName>
    <terms>
        <term>
            <name>Data Bases</name>
            <rootTermID>0</rootTermID>
            <itemcount>0</itemcount>
            <termGuid>2947210c-bb52-48b7-8511-212b25658b08</termGuid>
            <term>
                <name>SQL Server</name>
                <rootTermID>0</rootTermID>
                <itemcount>0</itemcount>
                <termGuid>08daa347-a5df-4f6f-9210-52a394ee6ae5</termGuid>
            </term>
            <term>
                <name>Oracle</name>
                <rootTermID>0</rootTermID>
                <itemcount>0</itemcount>
                <termGuid>c0bca60f-1bca-42a8-8f95-e1b37d56591d</termGuid>
            </term>
        </term>
        <term>
            <name>Operating Systems</name>
            <rootTermID>1</rootTermID>
            <itemcount>0</itemcount>
            <termGuid>f15f26ce-dd87-41bd-be25-2ca74e0a9085</termGuid>
            <term>
                <name>Windows Server 2008 R2</name>
                <rootTermID>1</rootTermID>
                <itemcount>1</itemcount>
                <termGuid>34341dc2-00b3-4e04-b6ee-b06adc2efaec</termGuid>
            </term>
            <term>
                <name>Ubuntu</name>
                <rootTermID>1</rootTermID>
                <itemcount>0</itemcount>
                <termGuid>3ab60c75-c74b-4e2e-b604-769551a1face</termGuid>
            </term>
        </term>
    </terms>
</termset>
I also incorporated this fix.

XSL

Now I modified the XSL to reflect the changes in the XML structure, and to design it a little. You can replace the value of the variable _xslString in the web part code with this snippet, or you can copy and paste it in the XSL String web part property (in the web part directly in the page.)
<?xml version='1.0' encoding='utf-8'?>
<xsl:stylesheet version='1.0' xmlns:xsl='http://www.w3.org/1999/XSL/Transform' xmlns:msxsl='urn:schemas-microsoft-com:xslt' exclude-result-prefixes='msxsl'>
    <xsl:output method='html' indent='yes'/>
    <xsl:template name='terms' match='//termset'>
        <xsl:param name='d'/>
        <xsl:variable name='webUrl' select='/termset/webUrl'/>
        <xsl:variable name='listId' select='/termset/listId'/>
        <xsl:variable name='fieldName' select='/termset/fieldName'/>
        <div id='accordion'>
            <xsl:for-each select='terms/term'>
                <xsl:call-template name='termtemplate'>
                    <xsl:with-param name='webUrl' select='$webUrl' />
                    <xsl:with-param name='listId' select='$listId' />
                    <xsl:with-param name='fieldName' select='$fieldName' />
                </xsl:call-template>
            </xsl:for-each>
        </div>
    </xsl:template>
    <xsl:template name ='termtemplate'>
        <xsl:param name='webUrl' />
        <xsl:param name='listId' />
        <xsl:param name='fieldName' />
        <xsl:element name='div'>
            <xsl:attribute name='style'>
                text-indent:<xsl:value-of select='count(ancestor::*)*20'/>px;
                padding-top: 2px;
            </xsl:attribute>
            <img alt='' src='/_layouts/Images/EMMTerm.png' style='vertical-align: middle;' />&#160;
            <xsl:choose>
                <xsl:when test='itemcount != 0'>
                    <a href='{$webUrl}/_layouts/Categories.aspx?FieldName={$fieldName}&amp;FieldValue={termGuid}&amp;ListId={$listId}'>
                        <xsl:value-of select='name'/>
                    </a>&#160;(<xsl:value-of select='itemcount'/>)
                </xsl:when>
                <xsl:otherwise>
                    <xsl:value-of select='name'/>
                </xsl:otherwise>
            </xsl:choose>
            <xsl:for-each select='term'>
                <xsl:call-template name='termtemplate'>
                    <xsl:with-param name='webUrl' select='$webUrl' />
                    <xsl:with-param name='listId' select='$listId' />
                    <xsl:with-param name='fieldName' select='$fieldName' />
                </xsl:call-template>
            </xsl:for-each>
        </xsl:element>
    </xsl:template>
</xsl:stylesheet>
Note that the terms are linked dynamically to the Categories page, the URL is populated with the necessary query string parameters:
http://<site address>/_layouts/Categories.aspx?FieldName=<field internal name>&FieldValue=<term id>&ListId=<list id>
The Categories page after a click on 'North America':
categories page

Conclusion

Now all you have to do is rebuild, repack and deploy. Enjoy!

Print Friendly and PDF

27 comments:

Vincent Defour said...

really great .. I was struggling with this for some time now .. can I ask you 1 question:
We use taxonomy on documents , so in a document library, instead of going to a categories.aspx page, I would like to just filer the document library on the same page, any suggestions on this? I would really appreciate as it seems that you are more an expert than I am :-)

Ami said...

Hi Vincent,
There is a built-in option to achieve this functionality in SharePoint lists and libraries. I suggest you try this first:

- go to the document library settings page
- under 'General Settings' click 'Metadata Navigation Settings'
- choose fields you want to build the tree by and filter by

Thank you for reading my blog!
Ami

Vincent Defour said...

really great .. I was struggling with this for some time now .. can I ask you 1 question:
We use taxonomy on documents , so in a document library, instead of going to a categories.aspx page, I would like to just filer the document library on the same page, any suggestions on this? I would really appreciate as it seems that you are more an expert than I am :-)

amaliagilad said...

Hi Vincent,
There is a built-in option to achieve this functionality in SharePoint lists and libraries. I suggest you try this first:

- go to the document library settings page
- under 'General Settings' click 'Metadata Navigation Settings'
- choose fields you want to build the tree by and filter by

Thank you for reading my blog!
Ami

Ami said...

Hi Vincent,
There is a built-in option to achieve this functionality in SharePoint lists and libraries. I suggest you try this first:

- go to the document library settings page
- under 'General Settings' click 'Metadata Navigation Settings'
- choose fields you want to build the tree by and filter by

Thank you for reading my blog!
Ami

Ezzeldin Mohamed said...

Ami, thanks for the enhancments but I have this problem, I get the error Error rendering web part: List '***' does not exist at site with URL 'http://******' and i'm not sure what the ListName should be.
I'm trying to use this webpart to show a category of the site's taxonomy to filter documents.

Ami said...

Hi Ezzeldin,
ListName should be the list name in the URL, that is the last part of the whole URL.
If this is the complete URL: http://somesitecollection/subsite/list1
then ListName is list1.

Thank you for reading my blog!
Ami

Ezzeldin Mohamed said...

I can't seem to get to listname, I always get Error rendering web part: List
'listname'
does not exist at site with URL 'http://sitename/sitecollection', its the same if i use the whole url or just the list name. My document library (the list i'm trying to filter) is in a document center, can that be the issue?

Ami said...

The URL I gave was just an example. You have to enter the last part of your list's URL. Can you send me your lists's URL and a screenshot of your web part settings?

Alekcarlsen said...

hey ami - i thought you might be able to help me out, and maybe make a post about it :) - im trying to get the taxonomy webpart to show as a dropdownlist that indexes based on selected value - but i cant quite get it down - keeps messing up -i was wondering if you maybe had any hints or links? :)

Ami said...

Hi,
I don't quite understand what you're trying to achieve... Can you please explain more or send me the code?

Alekcarlsen said...

   
   
       
       
        ()
           
               
            
           

               
           
       
   

Sid said...

Hi There,

Is there any way of representing the same data in heirarchy format but not display the terms which have not been tagged or used. Also want to display the level-1 and level-2 term if a level-2 term was tagged. Any suggestions..! 

Andreas Muß said...

Problem was that the site collection feature publishing infrastructure was not activated.

Ami Gilad said...

Sorry about the delay, I'm glad it works now!

Ami said...

Sorry about the delay, I'm glad it works now!

Ami said...

Hi,
You can do it in the XSL. In this clause I check if the term has been used (itemcount != 0):

                                                                                             ()                                                                                 

Just delete  and nothing will appear if the term hasn't been used.

vanessa said...

hey ami! thank you for your great work! :)

i want to hide unused categories too. but when i delete
it looks like this..
is there a way to completely hide empty / unused categories?

thank you!
vanessa

Jim said...

Just found this. I deployed the solution at the farm but don't see a feature to activate so that I can see & use this web part. Help?

Ami said...

Hi, it's a site collection feature called 'Enhanced Metadata WebPart'.

Leon Zandman said...

What's the easiest way to make this control support expanding and collapsing of nodes?

Ami said...

Hi Leon,
I think the simplest way is by a jQuery plugin. There are many of them, for example this one: http://www.webresourcesdepot.com/jquery-collapse-expanding-collapsing-content-with-javascript/

Zong said...

Hi Ami,

Thanks for your great web part.
I was wondering if it was possible to search for the tag once the tag link is clicked on through SharePoint search. Right now all it does is tags search under the Wiki categories but I want to change to specific search location to http://myWebPortal/_layouts/igc/OSSSearchResults.aspx?. Is it possible to edit it, can you guide me on this issue?

Adeeb said...

Hi Ami,


Great webpart you have here. I am trying to make it such that clicking on the link will automatically search for the documents tagged with the word. I have to do this because tags within folders are not detected for some reason (they are detected but item count always is 0).


However I am having trouble try to get the search to automatically select the tag under refinement. I am only able to do the basic search using this code:





The correct URL would be something like:


http://frontend-server/_layouts/igc/OSSSearchResults.aspx?k=hello&r=owstaxIdMetadataAllTagsInfo=%2308fb68db4-0954-4a3d-a160-764863407622:"hello"



The main problem I have here is that I do not know how to get the "08fb68db4-0954-4a3d-a160-764863407622" part. Do you have any ideas?


Regards,
Adeeb

Ben said...

Hi Ami,
This is brilliant and we have been using it for a little while, however we recently had to remove one of the terms (and its subterms) from our termstore and we are now getting the error "Error rendering web part: Object reference not set to an instance of an object." Any ideas where I could start looking to find a solution?

bangashboy said...

Hi Ami,


Great article. I have tried to implement the webpart and works perfectly. But i dont want to redirect to categories.aspx?.......
Instead i want to redirect to my custom page and display the results in a gridview. Any ideas how to achieve this?


Thanks.

Mudassar Ali said...

Solved it myself: Here is the changes in the code.






 ()




Now the query string will be dynamic and based on this query string results can be filtered and displayed in gridview.

Post a Comment