Tuesday, December 20, 2011

The Ugly, the Bad and the Good (part 4)

Part 4. Revisited

I was still not satisfied with the possibilities I came up with to get the right icon for every filter value in the Refinement panel.
I wanted to be able to add a specific icon to the Refiner values belonging to the Filter category 'Author' for instance.

Therefore I had to promote the ManagedProperty parameter, so its value would be available in the FilterLink template.

Here is the complete XSL that can replace the original one. Add this via the XSL Editor of the Refinement Panel webpart:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:ddwrt="http://schemas.microsoft.com/WebParts/v2/DataView/runtime">
  <xsl:output method="xml" indent="no"/>
  <xsl:param name="RefinementPanelCaption"></xsl:param>
  <xsl:param name="QueryId"></xsl:param>
  <xsl:param name="TagsCategoryId"></xsl:param>
  <xsl:param name="MetadataServiceUrl"></xsl:param>
  <xsl:param name="IsUIRightToLeft"></xsl:param>
  <xsl:param name="ApplyButtonText"></xsl:param>
  <xsl:param name="CancelButtonText"></xsl:param>
  <xsl:param name="CurrentLcid"></xsl:param>
  <xsl:param name="MoreLinkPanelIDSuffix"></xsl:param>
  <xsl:param name="IdPrefix" />
  <xsl:param name="IsDesignMode">True</xsl:param>
  <xsl:param name="RefineByHeading">Refinement</xsl:param>
  <xsl:param name="IsRTL">False</xsl:param>

  <xsl:template match="FilterPanel">
    <xsl:variable name="FilterCategories" select="FilterCategory" />
    <xsl:variable name="CategoryCount" select="count($FilterCategories)" />
    <xsl:if test="($CategoryCount &gt; 0)">
      <xsl:call-template name="FilterCategory">
        <xsl:with-param name="Categories" select="$FilterCategories" />
      </xsl:call-template>
    </xsl:if>
  </xsl:template>

  <xsl:template name="FilterCategory">
    <xsl:param name="Categories" />
    <xsl:if test="$RefinementPanelCaption != ''">
      <div class="ms-searchref-caption">
        <xsl:value-of select="$RefinementPanelCaption" />
      </div>
    </xsl:if>
    <xsl:for-each select="$Categories">
      <xsl:variable name="FilterCategoryId" select="concat(@Id,$IdPrefix)"/> 
      <xsl:variable name="TagsId" select="concat($TagsCategoryId,$IdPrefix)"/> 
      <xsl:variable name="ColumnId" select="substring(@Id,10)"/> 
      <xsl:variable name="FilterCategoryType" select="@Type"/> 
      <xsl:variable name="ManagedProperty" select="@ManagedProperty"/>
      <xsl:variable name="SSPList" select="substring-before(CustomData/AssociateTermSets, '|')"/>
      <xsl:variable name="TermSetList" select="substring-after(CustomData/AssociateTermSets, '|')"/>
      <xsl:variable name="ShowMoreLink" select="translate(@ShowMoreLink,'abcdefghijklmnopqrstuvwxyz','ABCDEFGHIJKLMNOPQRSTUVWXYZ')"/> 
      <xsl:variable name="FreeFormFilterHint" select="@FreeFormFilterHint"/> 
      <xsl:variable name="MoreLinkText" select="@MoreLinkText"/> 
      <xsl:variable name="LessLinkText" select="@LessLinkText"/> 
      <xsl:variable name="ShowCounts" select="@ShowCounts"/> 
      <xsl:variable name="DisplayName" select="@DisplayName"/>   
      <xsl:variable name="EscapedDisplayName" select="CustomData/EscapedDisplayName"/>   
      <xsl:variable name="ShowTaggingControl" select="@ShowTaggingControl"/>   
      <div class="ms-searchref-categoryname">
        <xsl:value-of select="$DisplayName"/>
      </div>
      <ul class="ms-searchref-filters" id="TopFilters_{$FilterCategoryId}">
        <xsl:choose>
          <xsl:when test="$FilterCategoryType = 'Message'">
            <xsl:for-each select="Filters/Filter">
              <xsl:call-template name="FilterMessage">
                <xsl:with-param name="Value" select="Value" />
              </xsl:call-template>
            </xsl:for-each>
          </xsl:when>
          <xsl:otherwise>
            <xsl:call-template name="Filter">
              <xsl:with-param name="Filters" select="Filters/Filter" />
              <xsl:with-param name="ShowCounts" select="$ShowCounts" />
    <!-- HW: Begin -->
    <xsl:with-param name="ManagedProperty" select="$ManagedProperty" />
    <!-- HW: End -->
            </xsl:call-template>
          </xsl:otherwise>
        </xsl:choose>
      </ul>
        <xsl:if test="$ShowMoreLink='TRUE'">
          <xsl:variable name="MoreFilters" select="MoreFilters/Filter" />
          <xsl:choose>
            <xsl:when test="$FilterCategoryId and ($FilterCategoryId != '') and ($FilterCategoryType = 'Microsoft.Office.Server.Search.WebControls.ManagedPropertyFilterGenerator')">
              <xsl:variable name="MoreDivId" select="concat('RefinementMore_', $QueryId, $FilterCategoryId)" />
              <xsl:variable name="LessDivId" select="concat('RefinementLess_', $QueryId, $FilterCategoryId)" />
              <xsl:if test="$MoreFilters != ''">
                <a class="ms-searchref-more" href="javascript:{{}}" onclick="SearchEnsureSOD();ToggleRefMoreLessFilters(this, true);">
                  <div class="ms-searchref-morelink">
                    <xsl:value-of select="$MoreLinkText" />
                    <xsl:text disable-output-escaping="yes">&amp;nbsp;</xsl:text>
                    <img src="/_layouts/images/more_arrow.png" class="ms-searchref-moreicon" />
                  </div>
                </a>
                <ul class="ms-searchref-filters" id="MoreFilters_{$FilterCategoryId}" style="display:none">
                  <xsl:call-template name="Filter">
                    <xsl:with-param name="Filters" select="$MoreFilters" />
                    <xsl:with-param name="ShowCounts" select="$ShowCounts" />
     <!-- HW: Begin -->
     <xsl:with-param name="ManagedProperty" select="$ManagedProperty" />
     <!-- HW: End -->
                  </xsl:call-template>
                </ul>
                <a class="ms-searchref-more" href="javascript:{{}}" onclick="SearchEnsureSOD();ToggleRefMoreLessFilters(this, false);" style="display:none">
                  <div class="ms-searchref-morelink">
                    <xsl:value-of select="$LessLinkText" />
                    <xsl:text disable-output-escaping="yes">&amp;nbsp;</xsl:text>
                    <img src="/_layouts/images/less_arrow.png" class="ms-searchref-moreicon" />
                  </div>
                </a>
              </xsl:if>
            </xsl:when>
            <xsl:when test="$FilterCategoryId and ($FilterCategoryId != '') and ($FilterCategoryType = 'Microsoft.Office.Server.Search.WebControls.TaxonomyFilterGenerator')">
              <xsl:variable name="IsTagsColumn" select="$FilterCategoryId = $TagsId" />
              <xsl:if test="$MoreFilters != ''">
                <a id="{$MoreLinkPanelIDSuffix}_{$FilterCategoryId}" href="javascript:{{}}" class="ms-searchref-more" onclick="SearchEnsureSOD();RenderTaggingControl('{$FilterCategoryId}', {$IsTagsColumn}, '{$SSPList}', '{$TermSetList}', '{$MetadataServiceUrl}', {$CurrentLcid}, '{$MoreLinkPanelIDSuffix}', '{$EscapedDisplayName}', {$ShowTaggingControl}, {$IsUIRightToLeft});">
                  <div class="ms-searchref-morelink">
                    <xsl:value-of select="$MoreLinkText" />
                    <xsl:text disable-output-escaping="yes">&amp;nbsp;</xsl:text>
                    <img src="/_layouts/images/more_arrow.png" class="ms-searchref-moreicon" />
                  </div>
                </a>
                <div style="display:none">
                  <ul class="ms-searchref-filters" id="MoreFilters_{$FilterCategoryId}">
                    <xsl:call-template name="Filter">
                      <xsl:with-param name="Filters" select="MoreFilters/Filter" />
                      <xsl:with-param name="ShowCounts" select="$ShowCounts" />
                    </xsl:call-template>
                  </ul>
                  <div id="TaxonomyMoreControl_{$FilterCategoryId}">
                    <input id="MetadataHiddenInput_{$FilterCategoryId}" type="hidden" />
                    <div id="TaggingControl_{$FilterCategoryId}" class="ms-taxonomy">
                        <img alt="" src="/_layouts/images/blank.gif" height="0" width="0"/>
                    </div>
                    <img style="display:none" src="/_layouts/images/RTE2FIND.gif" class="ms-searchref-taxapply" align="right" alt="{$ApplyButtonText}" onclick="SearchEnsureSOD();var link=GetTaxonomyApplyFilterUrl('{$FilterCategoryId}','{$ColumnId}');if (link!='')window.location=link;" />
                  </div>
                </div>
                <a style="display:none" class="ms-searchref-more" href="javascript:{{}}" onclick="SearchEnsureSOD();ToggleTaxonomyLessFilters(this);">
                  <div class="ms-searchref-morelink">
                    <xsl:value-of select="$LessLinkText" />
                    <xsl:text disable-output-escaping="yes">&amp;nbsp;</xsl:text>
                    <img src="/_layouts/images/less_arrow.png" class="ms-searchref-moreicon" />
                  </div>
                </a>
              </xsl:if>
            </xsl:when>
            <xsl:when test="$FilterCategoryId and ($FilterCategoryId != '') and ($FilterCategoryType = 'Message')">
            </xsl:when>
            <xsl:otherwise>
            </xsl:otherwise>
          </xsl:choose>
        </xsl:if>
      <div class="ms-searchref-catseparator">&#160;</div>
    </xsl:for-each>
  </xsl:template>

  <xsl:template name="Filter">
    <xsl:param name="Filters" />
    <xsl:param name="ShowCounts" />
 <!-- HW: Begin -->
 <xsl:param name="ManagedProperty" />
 <!-- HW: End -->
    <xsl:for-each select="$Filters">
      <xsl:variable name="Selection" select="Selection" />
      <xsl:choose>
        <xsl:when test="($Selection = 'Selected')">
          <xsl:call-template name="FilterLink">
            <xsl:with-param name="Url" select="Url" />
            <xsl:with-param name="UrlTooltip" select="Tooltip" />
            <xsl:with-param name="Value" select="Value" />
            <xsl:with-param name="FilterSelection" select="'ms-searchref-selected ms-searchref-removable'" />
            <xsl:with-param name="ShowCounts" select="$ShowCounts" />
            <xsl:with-param name="Count" select="Count" />
            <xsl:with-param name="Percentage" select="Percentage" />
            <xsl:with-param name="Indentation" select="Indentation" />
   <!-- HW: Begin -->
   <xsl:with-param name="ManagedProperty" select="$ManagedProperty" />
   <!-- HW: End -->
          </xsl:call-template>
        </xsl:when>
        <xsl:when test="($Selection = 'Implied')">
          <xsl:call-template name="FilterLink">
            <xsl:with-param name="Url" select="Url" />
            <xsl:with-param name="UrlTooltip" select="Tooltip" />
            <xsl:with-param name="Value" select="Value" />
            <xsl:with-param name="FilterSelection" select="'ms-searchref-selected'" />
            <xsl:with-param name="ShowCounts" select="$ShowCounts" />
            <xsl:with-param name="Count" select="Count" />
            <xsl:with-param name="Percentage" select="Percentage" />
            <xsl:with-param name="Indentation" select="Indentation" />
   <!-- HW: Begin -->
   <xsl:with-param name="ManagedProperty" select="$ManagedProperty" />
   <!-- HW: End -->
          </xsl:call-template>
        </xsl:when>
        <xsl:otherwise>
          <xsl:call-template name="FilterLink">
            <xsl:with-param name="Url" select="Url" />
            <xsl:with-param name="UrlTooltip" select="Tooltip" />
            <xsl:with-param name="Value" select="Value" />
            <xsl:with-param name="FilterSelection" select="'ms-searchref-unselected'" />
            <xsl:with-param name="ShowCounts" select="$ShowCounts" />
            <xsl:with-param name="Count" select="Count" />
            <xsl:with-param name="Percentage" select="Percentage" />
            <xsl:with-param name="Indentation" select="Indentation" />
   <!-- HW: Begin -->
   <xsl:with-param name="ManagedProperty" select="$ManagedProperty" />
   <!-- HW: End -->
          </xsl:call-template>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:for-each>
  </xsl:template>

  <xsl:template name="FilterLink">
    <xsl:param name="Url"/>
    <xsl:param name="UrlTooltip"/>
    <xsl:param name="Value"/>
    <xsl:param name="FilterSelection"/>
    <xsl:param name="ShowCounts"/>
    <xsl:param name="Count"/>
    <xsl:param name="Percentage"/>
    <xsl:param name="Indentation"/>
 <!-- HW: Begin -->
 <xsl:param name="ManagedProperty" />
 <!-- HW: End -->
    <xsl:variable name="SecureUrl">
      <xsl:call-template name="GetSecureUrl">
        <xsl:with-param name="Url" select="$Url" />
      </xsl:call-template>
    </xsl:variable>
    <li class="ms-searchref-filter {$FilterSelection}">
      <xsl:if test="($Indentation = '1')">
        <span class="ms-searchref-indenticon">&#8627;&#160;</span>
      </xsl:if>
   <!-- HW: Begin -->
      <xsl:choose>
        <xsl:when test="starts-with($Value, 'Any ')">
          <img align="absmiddle" src="/_layouts/images/asterisk.png" border="0" />
        </xsl:when>
        <xsl:when test="$ManagedProperty = 'SiteName'">
          <a href="http://{$Value}" target="_blank" title="Open site in new window"><img align="absmiddle" src="/_layouts/images/SharePointFoundation16.png" border="0" /></a>
        </xsl:when>
        <xsl:when test="ddwrt:MapToIcon('', $Value) != 'icgen.gif'">
          <img align="absmiddle" src="/_layouts/images/{ddwrt:MapToIcon('', $Value)}" border="0" />
        </xsl:when>
        <xsl:when test="$Value = 'Word'">
          <img align="absmiddle" src="/_layouts/images/icdocx.png" border="0" />
        </xsl:when>
        <xsl:when test="$Value = 'Excel'">
          <img align="absmiddle" src="/_layouts/images/icxlsx.png" border="0" />
        </xsl:when>
        <xsl:when test="$Value = 'PowerPoint'">
          <img align="absmiddle" src="/_layouts/images/icpptx.gif" border="0" />
        </xsl:when>
        <xsl:when test="$Value = 'Webpage'">
          <img align="absmiddle" src="/_layouts/images/icaspx.gif" border="0" />
        </xsl:when>
  <xsl:when test="$ManagedProperty = 'Author'">
    <a href="http://sp2010/SearchCenter/Pages/peopleresults.aspx?k={$Value}" target="_blank" title="Search for user profile"><img align="absmiddle" src="/_layouts/images/ACA16.gif" border="0" /></a>
        </xsl:when>
        <xsl:otherwise>
          <img align="absmiddle" src="/_layouts/images/{$Value}.gif" onError="this.src='/_layouts/images/bullet.gif';" border="0" />
        </xsl:otherwise>
      </xsl:choose>
      <xsl:text> </xsl:text>
   <!-- HW: End -->
      <a class="ms-searchref-filterlink" href="{$SecureUrl}" title="{$RefineByHeading}: {$UrlTooltip}">
        <xsl:value-of select="Value"/>
      </a>
      <xsl:choose>
        <xsl:when test="($ShowCounts = 'Count') and ($Count != '')">
          <span class="ms-searchref-count">
            <xsl:if test="$IsRTL = 'True'">&#x200f;</xsl:if> 
            (<xsl:value-of select="Count"/>)
          </span>
        </xsl:when>
        <xsl:when test="($ShowCounts = 'Percentage') and ($Percentage != '')">
          <span class="ms-searchref-count">
            <xsl:if test="$IsRTL = 'True'">&#x200f;</xsl:if> 
            (<xsl:value-of select="format-number($Percentage, '0%')"/>)
          </span>
        </xsl:when>
      </xsl:choose>
    </li>
  </xsl:template>

  <xsl:template name="FilterMessage">
    <xsl:param name="Value"/>
    <xsl:param name="FilterSelection"/>
    <li class="ms-searchref-filtermsg">
      <span class="ms-searchref-filterlink ms-searchref-msg">
        <xsl:value-of select="Value"/>
      </span>
    </li>
  </xsl:template>

  <xsl:template name="GetSecureUrl">
    <xsl:param name="Url"/>
    <xsl:choose>
      <xsl:when test="$IsDesignMode = 'True'">
        <xsl:value-of select="$Url" />
      </xsl:when>
      <xsl:otherwise>
        <xsl:value-of select="ddwrt:EnsureAllowedProtocol($Url)" />
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>

</xsl:stylesheet>

The result is a Refinement Panel with icons, where a hyperlink is added to the icons of the Filter categories 'Site' and 'Author':
- Site: Open site in new window
- Author: hyperlink to a people search results page with a TOP Federated Results webpart, that includes the local people search and an internet people search via the api of linkedin, pipl, etc.
Replace the url in row 253 with the url of your own search center.


Example of custom refiner with custom icons (with same file name as the value).


Wish list:
- Display avatar of the authors: due to the lack of e-mail address and MD5 encoding this is difficult to achieve;
- Show user profile on hoover with javascript/jQuery: be my guest. => There is a solution for this in SP2013/Online made by Elio Struyf: People refinement made easy by adding profile pictures.


I managed to make it possible to select multiple Refiners via the UI, just by extending the XSL of the Refinement Panel and adding a little javascript.

Saturday, December 17, 2011

From Russia with love

Tip:

There are two Russian vendors that offer free SharePoint tools.

RebelSearch Document Preview for SharePoint Search 2010
Install this on your WFE server(s) and you will have a preview option of the search results.
Be aware that it runs under the farm service account, and that is considered a security risk by SharePoint itself.

HarePoint Workflow Scheduler , that allows automatic execution of SharePoint workflows ​by schedule.
HarePoint Workflow Extensions Free Set of Activities, with a lot of String functions (finally available in SPD!).
Together with Virto Workflow Activities Kit that offers a free rollup of activities from Codeplex, these are a very useful extension of the workflow activities in SharePoint Designer.

Wednesday, October 19, 2011

The poor man's interface (use case 1)

How to connect from SAP to SharePoint to show related documents

Use case:
In SAP we have the registration of objects, and we want to link relevant documents or forms to these objects. Instead of the options SAP DMS, Web Dynpro or Adobe forms, we want to make use of SharePoint for this purpose.
So related documents are stored in SharePoint. But how to get to them, how to upload them and link them to the SAP objects?


Solution:
With some modest customization of SAP it is possible to present an option (under the button) to open a browser with a constructed url based on the metadata from SAP. For example: http://sp2010/objectinfo/default.aspx?ObjectID=obj000333
The first part of the url is static:"http://sp2010/objectinfo/default.aspx?ObjectID=", and the ID of the SAP object is added: "obj000333".
This url will connect to the default page of a Document Center site in SharePoint.


What should the user experience be like?
First of all we want to show a selection of documents and pictures based on the ID of the SAP object. This can be done with the help of a Query String (URL) Filter webpart that gets the value of the Query String Parameter: ObjectID. Then we connect to send the filter values to the Documents and Pictures webpart.


We should also be able to add new documents or pictures to the libraries and have the object ID added to them automatically after upload. With the help of javascript we are able to store the Object ID and current user ID as session variables, so the values can be used after upload to modify the properties of the (new) documents and pictures. This is one of the Off-The-Shelf scripts that I have described in an earlier post.

Finally we want to show an input box with the Object ID as default value, and the manual input can be used to change the filter value on the spot.



When the documents are stored in more than one location it's better to fire a search query from SAP.
Add the ObjectID as managed property to your search administration, so it will be indexed and can be used as keyword in the search query.
The url should be like: http://sp2010/SearchCenter/Pages/results.aspx?k=ObjectID%3A%22obj000333%22

Monday, October 17, 2011

Return to the context of the contextual search

The contextual search is integrated in the standard search center of SharePoint 2010. So we have one search experience, that can be modified.
The scope of the contextual search is added to the search query url by the parameters cs (contextual scope: This Site/This List) and u (url of the site/list).


The masterpage of the search center doesn't provide any navigation. And other portals are not part of the navigation within the Home site. But it would be nice to be able to go back to where we started the search, no matter where we came from. This can be done with some simple Html and javascript.
Add a HTML Form Web Part to the left zone of the search results page and paste the following code into the source editor:


<br/>&#160;&#160;&#160;<a href="/"><img class="ms-rtePosition-4" alt="Go Home" src="/_layouts/images/hhome.png" border="0"/></a>&#160;<font color="#0072bc"><a href="/">Go Home</a>&#160; </font><a href="#" onClick="javascript:window.location.replace(GetUrlKeyValue('u'));"><img class="ms-rtePosition-4" alt="Go Back" src="/_layouts/images/back.gif" border="0"/></a>&#160;<a href="#" onClick="javascript:window.location.replace(GetUrlKeyValue('u'));">Go Back</a><br/><br/>

This results in two options: go home, or go back to the context.
When there is no context, you will return to the default page of the search center.


Friday, September 9, 2011

Direct link to a certain version of a document

In an order form I want to show a link to a document that contains the standard specifications of the goods. These specifications will change once in a while so it is important to know on what version of this document the order was based.
I can make use of a standard hyperlink field to add the shortcut to the document, that is the current version at that time. After the order is processed and archived to read-only, I want to be able to click the link to open the right version of the document, even though there are newer versions of it. The url of prior versions of a document is like: site/_vti_history/512/Documents/Specifications.pdf
The version is multiplied with the factor 512, so version 1.0 is 512, 2.0 is 1024, etc. With this knowledge I am able to reconstruct the url of the right version if I save the version number as well. But what if this version is still the current version? The reconstructed url doesn't work, because it should be: site/Documents/Specifications.pdf
Within a HTML <IMG> tag I can make use of the onError event to switch over to an alternative source, like I described in my part about adding icons to the refinement panel in SharePoint 2010. Unfortunately this trick will not work with href to a document. So I searched for an other way, and I found one: Checking if a file(url) exists using Javascript (AJAX). So all I have to do is turn the url into something like: href=javascript:check_file(url). The javascript code will then redirect me to the valid path of the document.

<script language="javascript" type="text/javascript">

http_check = getHTTPObject();

function getHTTPObject(){ 
  //Create a boolean variable to check for a valid Internet Explorer instance.
var xmlhttp = false;
//Check if we are using IE.
 try {
  //If the Javascript version is greater than 5.
  xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
 } catch (e) {
  //If not, then use the older active x object.
  try {
  //If we are using Internet Explorer.
  xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
 } catch (E) {
//Else we must be using a non-IE browser.
xmlhttp = false;
 }
}
//If we are using a non-IE browser, create a javascript instance of the object.
 if (!xmlhttp && typeof XMLHttpRequest != 'undefined') {
 xmlhttp = new XMLHttpRequest();
 }
  return xmlhttp;
}

function check_file(path_of_file) 
{
    http_check.open("HEAD", path_of_file);
    http_check.onreadystatechange = handleHttpResponse_check;
    http_check.send(null);

    function handleHttpResponse_check()
    {
        if(http_check.readyState == 4){
            if (http_check.status == 200) {
                window.open(path_of_file);
            }else if (http_check.status == 404) {
                window.open(other_path_of_file);
            }
        }
    }
}
</script>
With the help of a workflow after submitting the form I want to modify the value of the hyperlink field to include the part: javascript:check_file(url). This doesn't seem to work in the browser.
So I am going to use a calculated column to compose the HTML code, to be rendered by the TextToHtml script from PathtoSharePoint.

Formula:
="<a href='javascript:' onclick="&CHAR(34)&"check_file('http://www.sharepointserver.eu/site/_vti_history/"&VALUE(LEFT(DocVersion;1))*512&"/Documents/Specifications.pdf')"&CHAR(34)&">Standard specifications</a>"

The javascript code with the redirect to the alternative -fixed- url of the current version:
function check_file(path_of_file) 
{
    http_check.open("HEAD", path_of_file);
    http_check.onreadystatechange = handleHttpResponse_check;
    http_check.send(null);

    function handleHttpResponse_check()
    {
        if(http_check.readyState == 4){
            if (http_check.status == 200) {
                window.open(path_of_file);
            }else if (http_check.status == 404) {
                window.open('http://www.sharepointserver.eu/site/Documents/Specifications.pdf');
            }else {
                window.open('http://www.sharepointserver.eu/site/Documents/Specifications.pdf');
            }
        }
    }
}
Somehow the "else if (http_check.status == 404)" doesn't work for the version url, so I use the "if anything else" to redirect to the url of the current version.

Now let's modify the user interface to put all the pieces together:
Add a CEWP to the NewForm.aspx (?toolpaneview=2) of the list and enter the link to the document, so the users can open the current version to read the specs.


Lookup the version number of the document by a workflow and add the value to a hidden (Allow management of content types, set Column Settings to Hidden) text field DocVersion, that is used in the formula of the calculated column.


Add a CEWP to the DispForm.aspx and/or the list view (display the calculated column) and paste the javascripts in the Source Editor. In this way the text in the calculated column is rendered as HTML and the javascript function check_file is executed on click of the link.
In this way there will be a redirect to the valid path of the right version.

Friday, August 26, 2011

The Ugly, the Bad and the Good (Part 3)


Part 3. The Good

Different ways to get the right icon for every filter value in the Refinement panel.

After exploring the possibilities in XSL I came up with this hybrid approach:
- Look for Result Type icons in the docicon.xml file on the server;
- Look for icons with the same name as the filter value in the images folder;
- Several when-statements for specific matches;
- Default icon if there's no match.

Add via the XSL Editor the following rows to the <xsl:template name="FilterLink">:

<xsl:if test="($Indentation = '1')">
  <span class="ms-searchref-indenticon">&#8627;&#160;</span>
</xsl:if>
<!-- Begin -->
  <xsl:choose>
 <!-- for the Any of every category -->
 <xsl:when test="starts-with($Value, 'Any ')">
   <img align="absmiddle" src="/_layouts/images/asterisk.png" border="0" />
 </xsl:when>
 <!-- for Site -->
 <xsl:when test="starts-with($UrlTooltip, 'http://')">
   <img align="absmiddle" src="/_layouts/images/SharePointFoundation16.png" border="0" />
 </xsl:when>
 <!-- for Result Type - will only work for original values, like Pdf, Zip -->
 <xsl:when test="ddwrt:MapToIcon('', $Value) != 'icgen.gif'">
   <img align="absmiddle" src="/_layouts/images/{ddwrt:MapToIcon('', $Value)}" border="0" />
 </xsl:when>
 <!-- for Result Type with custom value -->
 <xsl:when test="$Value = 'Word'">
   <img align="absmiddle" src="/_layouts/images/icdocx.png" border="0" />
 </xsl:when>
 <xsl:when test="$Value = 'Excel'">
   <img align="absmiddle" src="/_layouts/images/icxlsx.png" border="0" />
 </xsl:when>
 <xsl:when test="$Value = 'PowerPoint'">
   <img align="absmiddle" src="/_layouts/images/icpptx.gif" border="0" />
 </xsl:when>
 <xsl:when test="$Value = 'Webpage'">
   <img align="absmiddle" src="/_layouts/images/icaspx.gif" border="0" />
 </xsl:when>
 <!-- find your own added icons with same name as the filter value, or else default icon  -->
 <xsl:otherwise>
   <img align="absmiddle" src="/_layouts/images/{$Value}.gif" onError="this.src='/_layouts/images/bullet.gif';" border="0" />
 </xsl:otherwise>
  </xsl:choose>
  <!-- space -->
  <xsl:text> </xsl:text>
<!-- End -->
<a class="ms-searchref-filterlink" href="{$SecureUrl}" title="{$RefineByHeading}: {$UrlTooltip}">
  <xsl:value-of select="Value"/>
</a>

The result is this pimped up Refinement Panel:


Try it yourself.

Of course it's possible to add separate templates for every category, but that is too much work for now. Maybe later... See Part 4.




The Ugly, the Bad and the Good (Part 2)


Part 2. The Bad

Add your own icons with the same name as the refiner value to the images folder.

Only one extra row of XSL is needed to do the trick: find and show the icon with the filter value in the Refinement panel.

Add via the XSL Editor the following rows to the <xsl:template name="FilterLink">:

<xsl:if test="($Indentation = '1')">
  <span class="ms-searchref-indenticon">&#8627;&#160;</span>
</xsl:if>
<!-- Begin -->
  <!-- find your own added icons with same name as the filter value, or else default icon  -->
  <img align="absmiddle" src="/_layouts/images/{$Value}.gif" onError="this.src='/_layouts/images/bullet.gif';" border="0" />
  <!-- space -->
  <xsl:text> </xsl:text>
<!-- End -->
<a class="ms-searchref-filterlink" href="{$SecureUrl}" title="{$RefineByHeading}: {$UrlTooltip}">
  <xsl:value-of select="Value"/>
</a>

But there must be a smarter way to get the standard icons that are already available. The sequel continues with Part 3.

Wednesday, August 24, 2011

What the hack?!? Not OOTB, but OTS

Adding metadata to uploaded documents automaticly, based on the context from where the Upload action was started.


From a dashboard (webpart page with related lists and library combined) I want to be able to start an upload action to add project documents to the library and want metadata (like project number, document type) added automaticly afterwards.
Well this is certainly not OOTB SharePoint. Normally you would set default values to columns before the upload, or use the Edit in Datasheet view to copy and paste properties to the documents after the upload.

We are going to combine several Off-The-Shelf scripts to get to the solution.
  1. TextToHTML-script from PathToSharePoint to render HTML-code from a calculated column so I can show an icon as upload "button" in the list view with a hyperlink that will call a javascript function with project data as variables.
  2. “sessvars.js” from Thomas Frank, also frequently used by Alexander from SharePoint JavaScripts, that let you use JavaScript session variables without using cookies. In this way I am able to store the context before the upload, and use it afterwards.
  3. SPServices, a jQuery Library for SharePoint Web Services, to use the function $().SPServices.SPUpdateMultipleListItems to update the document properties after the upload.
Download the scripts and add them to a Javascripts library in the site collection.


So I have a list for my projects and I will add a calculated column with HTML-code:
="<a href='javascript:upload(&#39;"&Projectnumber&"&#39;,&#39;Reports&#39;);'><img src='/_layouts/images/upload.gif' border='0' alt='Reports'></a>"

Then add a CEWP at the bottom of the dashboard page and paste the following script in the Source editor:

<script src="/Javascripts/sessvars.js" type="text/javascript"></script>
<script type="text/javascript">
sessvars.$.clearMem();
function upload(projectNumber,docType){
  sessvars.projectNumber = projectNumber;
  sessvars.docType = docType;
  sessvars.userId = _spUserId;
  var targetUrl = "/_layouts/Upload.aspx?List=%7BE69813A0%2D6E90%2D4D26%2D852A%2DBE0952C165CB%7D&RootFolder=%2FDocuments&Source=http%3A%2F%2Fwww%2Esharepointserver%2Eeu%2FPages%2FJavascript%2Easpx&MultipleUpload=1";
  window.location.href = targetUrl;
}
</script>

<script type="text/javascript">
/*
Text to HTML Lite - version 2.1.1
Questions and comments: Christophe@PathToSharePoint.com
*/
function TextToHTML(NodeSet, HTMLregexp) {
var CellContent = "";
var i=0;
while (i < NodeSet.length){
try {
CellContent = NodeSet[i].innerText || NodeSet[i].textContent;
if (HTMLregexp.test(CellContent)) {NodeSet[i].innerHTML = CellContent;}
} 
catch(err){}
i=i+1;
}
}
// Calendar views
var regexpA = new RegExp("\\s*<([a-zA-Z]*)(.|\\s)*/\\1?>\\s*");
TextToHTML(document.getElementsByTagName("a"),regexpA);
// List views
var regexpTD = new RegExp("^\\s*<([a-zA-Z]*)(.|\\s)*/\\1?>\\s*$");
TextToHTML(document.getElementsByTagName("TD"),regexpTD);
</script>

The list view will show an upload button thanks to the TextToHTML script.
The javascript function will add the project number, document type and current user to the session variables, followed by a redirect to the standard upload page. You should replace the targetUrl with your own (click Upload Multiple Documents in the target library and copy the url). The source parameter refers to another page with javascript: /Pages/Javascript.aspx


Next step is to create this web part page, add a CEWP and paste the following script in the Source editor:

<script src="/Javascripts/sessvars.js" type="text/javascript"></script>
<script src="/Javascripts/jquery-1.6.2.min.js" type="text/javascript"></script>
<script src="/Javascripts/jquery.SPServices-0.6.2.min.js" type="text/javascript"></script>
<script type="text/javascript">
var projectNumber = sessvars.projectNumber;
var docType = sessvars.docType;
var userId = sessvars.userId;
if((projectNumber!=undefined)&&(docType!=undefined)&&(userId!=undefined)){
  $().SPServices.SPUpdateMultipleListItems({ 
    listName: "Documents",
    CAMLRowLimit: 0,
    CAMLQuery: "<Query><Where><And><Eq><Fieldref Name='Author' LookupId='TRUE'/><Value Type='Integer'>" + userId + "</Value></Eq><IsNull><Fieldref Name='Projectnumber'/></IsNull></And></Where></Query>",
    valuepairs: [["Projectnumber", projectNumber], ["Documenttype", docType]]
  });
  sessvars.$.clearMem();
  window.location.href = "/Pages/Projects.aspx";
}
</script>

The session variables are read out and the values are used to update the documents that are selected by the CAML query: Created By the current user and no project number.
Finally the memory of the session variables is cleared and there's a redirect back to the dashboard page that will show the update document properties.


Saturday, August 20, 2011

Add Hyperlink to the Image Rotator of PathToSharePoint to search for the picture and open it's dispform

Christophe from PathToSharePoint offers the possibility to generate your own script for an image rotator. You can copy the script and add it to a CEWP on your page.
This is a great service.
But there is only the possibility to add a hyperlink that will download the picture directly, or will open a link that is specified in an extra text column that you should fill out by hand. Instead I want the dispform to be opened with the properties of the picture.

The script uses "/_vti_bin/owssvr.dll?Cmd=Display&XMLDATA=TRUE&List={GUID}" to query the picture library and get a XML output in return. And here is where the restrictions come in: this data doesn't contain the ID of the picture, only the name. And it calls the default view of the picture library that probably will have an item limit, so not all images are rotated.
Therefore I rewrote the script a bit to get it work my way.
First add an All items view to the picture library, and name it xxx or something so users will not be motivated to select it. Get the GUID of this view and add it to the script: "/_vti_bin/owssvr.dll?Cmd=Display&XMLDATA=TRUE&List={GUID}&View={GUID}".
Second I will use the name of the picture to add it to a search query:
var LinkColumn="ows_NameOrTitle";var PictureLink="/SearchCenter/results.aspx?s=PicLib&k="+ListItems[SelectedItem].getAttribute(LinkColumn);
Add a scope to the search administration based on the web address of the picture library.
In this way I will find the picture, but still not have the possibility to open the dispform of the item.
So I will use the trick from SharePoint Search Results: Adding a link to the view properties page of a document to get the ID available in the search results and add a 'View properties' link with XSL.

Quod erat demonstrandum.

The Ugly, the Bad and the Good (Part 1)


Part 1. The Ugly

Let's bring back the icons in the Refinement Panel of the Search results, as with the Faceted Search in MOSS 2007.
Within the Refinement Panel we have the value of the filters available. By extending the XSLT with a lot of when-statements we are able to select the belonging icon from the images folder at the file system of the server.

Add via the XSL Editor the following rows to the <xsl:template name="FilterLink"> between Begin and End comment:

<xsl:if test="($Indentation = '1')">
  <span class="ms-searchref-indenticon">&#8627;&#160;</span>
</xsl:if>
<!-- Begin -->
<xsl:choose>
  <xsl:when test="contains($Value, 'Any')">
    <img align="absmiddle" src="/_layouts/images/asterisk.png" border="0" />
  </xsl:when>
  <xsl:when test="$Value = 'Word'">
    <img align="absmiddle" src="/_layouts/images/icdocx.png" border="0" />
  </xsl:when>
  <xsl:when test="$Value = 'Excel'">
    <img align="absmiddle" src="/_layouts/images/icxlsx.png" border="0" />
  </xsl:when>
  <xsl:when test="$Value = 'Webpage'">
    <img align="absmiddle" src="/_layouts/images/icaspx.gif" border="0" />
  </xsl:when>
  <xsl:when test="$Value = 'Adobe PDF'">
    <img align="absmiddle" src="/_layouts/images/icpdf.gif" border="0" />
  </xsl:when>
  <xsl:otherwise>
    <img align="absmiddle" src="/_layouts/images/bullet.gif" border="0" />
  </xsl:otherwise>
</xsl:choose>
<xsl:text> </xsl:text>
<!-- End -->
<a class="ms-searchref-filterlink" href="{$SecureUrl}" title="{$RefineByHeading}: {$UrlTooltip}">
  <xsl:value-of select="Value"/>
</a>
Of course this is quite an ugly solution due to the fact that you need to add rows for 'every' result type that you might expect.
But it's a quick win and looks very nice. And because of the otherwise-statement, every filter gets at least an icon and no red cross or so.

Here is an example of the result:


In Part 2. we will look at the possibility to use the value of the filter to compose the filename of the icon. In this way we don't need all the when-statements. As you might think, this is the Bad one.

Friday, August 19, 2011

Get back to where you once belonged

My first post will be about getting back the icons where they once belonged:

Next to the Search Refiners in SharePoint 2010,
as with the Faceted Search in MOSS 2007.

Search Refiners (SharePoint 2010)Faceted Search (MOSS 2007)

Thursday, August 18, 2011

At Poleposition

Ready, steady, GO!

At last my contribution to a better SharePoint for the world.

Improve to Approve.