Thursday, April 26, 2012

Customize Search Results with Custom Links

Adding: View Properties, Edit Document, Show Folder, Permalink, and Email Link

A request came my way to add some custom links to the search results of a site collection.  They wanted to view the properties of documents straight from the search results.  There are a few good blogs posts on this topic, and much of what I will share is derived from their good work.  
Once I got started adding the "View Properties" link.  I realized it would be cool to have a few more links for actions such as:
  • Edit Document
  • Show Folder
  • Permalink
  • Email Link

Step 1: Adding Managed Properties to Search

Anne Stenberg has a great article on the "Basic" property category.  I leveraged her information in building my custom links.  Her article can be found at:

To add the needed managed properties open the Farm Search Administration and select your search service application; then, click on the Metadata Properties.  We are going to need to add a couple to do what we need.

The first metadata property we need to add I called ItemID.  This property maps to the List Item "ID" column found in every SharePoint list.  This property will be used to create the URL for the "View Property" link.

The second metadata property need is a property I called host.  This maps to the Basic:4 property  

The third metadata property needed is my "Permalink" property which is the Document link created as part of the Document ID Service feature (read more: http://msdn.microsoft.com/en-us/library/ee559302.aspx).

These new properties will not be present in the search results until the next full crawl.  You can confirm if they are attached to a given result by viewing the search results XML (http://msdn.microsoft.com/en-us/library/ms546985.aspx).

Step 2: Customizing the Search Core Results web part columns.

If you edit the results.aspx file in a search center site, you will find the primary web part on the page is called Search Core Results.  This web part is easily customizable.  We are going to be changing the properties under the Display Properties section.  In order to customize the look and feel of the results the Use Location Visualization check-box must be unchecked.  

You can then modify the Fetched Properties text-box.  We need to add our three new properties to the list so that the web part can consume them.  Each column is declared by an XML tag that looks like this:

 <Column Name="Title"/>  

To add our three new columns we just add them using the same format and end up with a list of Properties:

 <Columns> <Column Name="WorkId"/> <Column Name="Rank"/> <Column Name="Title"/> <Column Name="Author"/>   
 <Column Name="Size"/> <Column Name="Path"/> <Column Name="Description"/> <Column Name="Write"/>   
 <Column Name="SiteName"/> <Column Name="CollapsingStatus"/> <Column Name="HitHighlightedSummary"/>   
 <Column Name="HitHighlightedProperties"/> <Column Name="ContentClass"/> <Column Name="IsDocument"/>   
 <Column Name="PictureThumbnailURL"/> <Column Name="PopularSocialTags"/> <Column Name="PictureWidth"/>   
 <Column Name="PictureHeight"/> <Column Name="DatePictureTaken"/> <Column Name="ServerRedirectedURL"/>   
 <Column Name="ItemID"/> <Column Name="Host"/> <Column Name="Permalink"/> </Columns>  

Step 3: Customizing the XSL.

The other part we will be customizing is the XSL that controls how the XML search results are rendered.  I am including my XSL code blocks below, but first I will explain what they do.

The first block is added to the results body and is placed just below the result metadata.  This block of code calls a series of custom XSL templates.  Each template renders one of the links.  I have named the templates in a way that it should be self-evident which link they render.  I pass to these templates the parameters needed to render the link (notice, our custom managed properties are referenced, as well as a few default managed properties).  To implement this code, add the following lines to your XSL (using the XSL Editor) below the div tag with the "srch-Metadata2" class:

 <div class=”srch-Metadata2″>  
   <xsl:call-template name=”ViewProperties”>  
    <xsl:with-param name=”Url” select=”url” />  
    <xsl:with-param name=”itemID” select=”itemid” />  
    <xsl:with-param name=”isDocument” select=”isdocument” />  
    <xsl:with-param name=”host” select=”host” />  
   </xsl:call-template>  
   
   <xsl:call-template name=”EditDocument”>  
    <xsl:with-param name=”Url” select=”url” />  
    <xsl:with-param name=”contentclass” select=”contentclass” />  
   </xsl:call-template>  
   
   
   <xsl:call-template name=”ShowFolder”>  
    <xsl:with-param name=”Url” select=”sitename” />  
    <xsl:with-param name=”itemID” select=”itemid” />  
   </xsl:call-template>  
   
   <xsl:call-template name=”ShowPermalink”>  
    <xsl:with-param name=”Permalink” select=”permalink” />  
    <xsl:with-param name=”contentclass” select=”contentclass” />  
   </xsl:call-template>  
   
   <xsl:call-template name=”EmailLink”>  
   <xsl:with-param name=”Permalink” select=”permalink” />  
   <xsl:with-param name=”Url” select=”urlEncoded” />  
   </xsl:call-template>  
 </div>  

The second block of code is the templates.  The most complicated of these templates is the ViewProperties template.  I leaned heavily on the work of other people to build this template and I suggest you visit their sites linked above.  The sites above did have some bugs in their template and so I have made modifications to compensate for those bugs (namely the template did not generate the correct link for a document library with folders).  The rest of the templates are much more straight forward.  To add these templates to the XSL, just before the line that says "<!-- XSL transformation starts here -->" place the following code:

 <xsl:template name="ViewProperties">  
  <xsl:param name="Url" />  
  <xsl:param name="itemID" />  
  <xsl:param name="host" />  
  <xsl:param name="isDocument" />  
  <xsl:if test="string-length($itemID) > 0">  
     <xsl:variable name="UrlLcase" select="translate($Url, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz')"/>  
     <xsl:variable name="hostLcase" select="translate($host, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz')"/>  
     <xsl:choose>  
    <xsl:when test="$isDocument = 'True'">  
        <xsl:variable name="docLib" select="substring-before(substring-after(substring-after($UrlLcase,$hostLcase), '/'), '/')" />  
        <xsl:variable name="viewPropUrl" select="concat($host, '/', $docLib, '/Forms/DispForm.aspx?id=', $itemID)" />  
        <a href="{$viewPropUrl}">View Properties</a>  
        <xsl:text disable-output-escaping="yes">&amp;nbsp;|&amp;nbsp;</xsl:text>  
    </xsl:when>  
    <xsl:when test="$UrlLcase = $hostLcase">  
    </xsl:when>  
    <xsl:otherwise>     
        <xsl:variable name="listLoc" select="substring-before(substring-after(substring-after($UrlLcase,concat($hostLcase,'/lists')), '/'), '/')" />  
        <xsl:variable name="viewPropUrl" select="concat($host, '/lists/', $listLoc, '/DispForm.aspx?id=', $itemID)" />  
        <a href="{$viewPropUrl}">View Properties</a>  
        <xsl:text disable-output-escaping="yes">&amp;nbsp;|&amp;nbsp;</xsl:text>  
    </xsl:otherwise>  
    </xsl:choose>  
  </xsl:if>  
 </xsl:template>  
   
 <xsl:template name="EditDocument">  
   
    <xsl:param name="Url" />  
    <xsl:param name="contentclass" />  
    <xsl:if test="$contentclass='STS_ListItem_DocumentLibrary'">  
       <a href="{$Url}" onclick="return DispEx(this,event,'TRUE','FALSE','FALSE','SharePoint.OpenDocuments.3','0','SharePoint.OpenDocuments','','','','1','0','0','0x7fffffffffffffff')">Edit Document</a>  
        <xsl:text disable-output-escaping="yes">&amp;nbsp;|&amp;nbsp;</xsl:text>  
    </xsl:if>  
 </xsl:template>  
   
 <xsl:template name="ShowFolder">  
    <xsl:param name="Url" />  
    <xsl:param name="itemID" />  
    <xsl:if test="string-length($itemID) > 0">  
       <a href="{$Url}">Show Folder</a>  
        <xsl:text disable-output-escaping="yes">&amp;nbsp;|&amp;nbsp;</xsl:text>  
    </xsl:if>  
 </xsl:template>  
   
 <xsl:template name="ShowPermalink">  
    <xsl:param name="Permalink" />  
    <xsl:param name="contentclass" />  
    <xsl:if test="string-length($Permalink) > 0">  
       <a href="{$Permalink}">Permalink</a>  
        <xsl:text disable-output-escaping="yes">&amp;nbsp;|&amp;nbsp;</xsl:text>  
    </xsl:if>  
 </xsl:template>  
   
 <xsl:template name="EmailLink">  
    <xsl:param name="Permalink" />  
    <xsl:param name="Url" />  
    <xsl:choose>  
    <xsl:when test="string-length($Permalink) > 0">  
       <a href="mailto:?body={$Permalink}">Email Link</a>  
    </xsl:when>  
    <xsl:otherwise>  
       <a href="mailto:?body={$Url}">Email Link</a>  
    </xsl:otherwise>  
    </xsl:choose>  
 </xsl:template>  

Last Thoughts: 

Once you have made the changes in the XSL Editor, apply the changes and save the page.  You should now see results that have an extra line of links like this:



The templates will confirm that the different links apply to each result.  I have found a few minor bugs (for example on mysites the View Properties link still sometimes does not work).  The one thing I would like to add is a source parameter that gets passed in the View Properties link.  This would ensure that if the user clicks close that SharePoint would return to the results page.  If I get it working I will post the tweak.

Tuesday, April 24, 2012

Hiding the First Top-Link Tab for a Publishing SharePoint Site

After enabling the SharePoint Server Publishing Infrastructure feature on a SharePoint site the first tab in the top-link bar automatically changes to the site title:

Here is an example of a site before the publishing infrastructure has been enabled:

This is what the site looks like after enabling the feature (I also changed the site title to further illustrate the problem):

This can be a very undesirable result when the site title is long or just undesirable as the first tab's label.  With a few masterpage changes this behavior can be remedied.  To accomplish this follow these steps:

Open the site in SharePoint Designer and go to the Master Pages left navigation. 
Make a copy of the "v4.master" and rename it to something like "modified.master":


The first step is to edit your new "modified.master" and add the following line to the Registers at the top of the master page:

<%@ Register 
 Tagprefix="PublishingNavigation" 
 Namespace="Microsoft.SharePoint.Publishing.Navigation" 
 Assembly="Microsoft.SharePoint.Publishing, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" 
%>


Further down in the master page you will find a block of code that looks something like this:

The part of the code that we are going to focus on is the SharePoint:DelegateControl.  A good article that describes the purpose of the DelegateControl can be found here: http://msdn.microsoft.com/en-us/library/ms432695.aspx.  We are going to replace this delegate control with a control that we can customize a little bit more.  Highlight the SharePoint:DelegateControl (like shown below) and delete it:

We are going to replace the deleted text with the following:

<PublishingNavigation:PortalSiteMapDataSource 
ID="topSiteMap" 
Runat="server" 
SiteMapProvider="CombinedNavSiteMapProvider" 
StartFromCurrentNode="true" 
StartingNodeOffset="0" 
ShowStartingNode="false" 
TrimNonCurrentTypes="Heading" 
/>

This creates a new TopNav datasource.  This data source will still consume the out of the box data navigation, but will not show the first tab (created by the publishing feature).

After making this modification the top link bar the top link now looks like this:

For more information on this topic check out these other blogs:

http://blogs.msdn.com/b/ecm/archive/2007/02/10/moss-navigation-deep-dive-part-1.aspx


Wednesday, April 11, 2012

How To: Enable Embedded HTML in a Calculated Column

We had implemented the calculated column to HTML JavaScript that can be found on the Path to SharePoint website (http://blog.pathtosharepoint.com/2008/09/01/using-calculated-columns-to-write-html/).   This was a great solution at the time, and I really appreciate the work they did over at Path to SharePoint to create this method.

This solution had worked well, but in order to make it work across the entire site the JavaScript had been embedded in the Master Page on each site that used a calculated column to display HTML.  We were using the calculated column to create a status indicator on projects, and the column was named the same thing on each site.  Luckily, Microsoft has provided a better way for this scenario.

Microsoft has a great article on using XSLT style sheets to customize the rendering of list views (http://msdn.microsoft.com/en-us/library/ff606773.aspx).  By creating an XSLT style sheet for specific column you can change how that column is rendered.  In the use-case above the column could be customized by creating a new fldtypes_*.xsl file.  A fldtypes.xsl file customizes the way that SharePoint renders a specific column type.  It can also be used to customize how SharePoint renders a column of a specific name.

Let's look at an example:
I created a file called fldtypes_ScenicSP.xsl and placed it in C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS\XSL on my web front end.  Inside this file I placed the following style sheet:


<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:template match="FieldRef[@Name='Status_x0020_Indicator']" mode="Text_body">
    <xsl:param name="thisNode" select="."/>
    <xsl:value-of select="$thisNode/@*[name()=current()/@Name]" disable-output-escaping ="yes" />
  </xsl:template>
</xsl:stylesheet>

In the style sheet I have a match condition of FieldRef[@Name='Status_x0020_Indicator'].  This indicates that I want this style sheet to apply to all fields that are named "Status Indicator".  You need to be careful not to pick a field name that is going to be commonly used for a lot of different tasks.  This basically makes that field name reserved as a special field that will be treated by SharePoint differently than any other field.  The behavior I changed is to add the following: disable-output-escaping ="yes".  This tells SharePoint that when it renders that field to render the HTML instead of displaying the tags.  The select clause in the value-of is explained in much more detail on the Microsoft article posted above (which I highly encourage you to read so that you understand this principle).

Once I save my file I then need to recycle the application pool on any web application where I intend on using this new style sheet (I suggest doing an IISReset so that all application pools are cycled).  This same file will need to be copied to each web front end and the application pool will need to be cycled on each as well.  Once this has been done you can now see the fruits of your labor.

First, create a task list (I called mine myTasks):

Second, I added a column called Indicator.  I created it as a choice column with three choices (red, yellow, or green). I also created a record

Third, I created a calculated column that generates an image URL based on the Indicator color.

Finally, I added one more calculated column named Status Indicator that creates an image tag.  Status Indicator was the column above for which we changed the rendering behavior.

The finished product can be seen below:


This was a very simple example and can be replicated using an out of the box KPI list, but I will post some more useful examples in a second article on this topic. Please make sure you read the Microsoft article above.  They do a great example of making negative values turn red on a currency column.