Tuesday, May 8, 2012

Hyperlinking to an InfoPath form using the XsltListView web part

Recently I was working on a "no-code" SharePoint / InfoPath solution where we had a requirement to create a number of SharePoint views where the link to open the InfoPath form instance wasn't based on the original Name column (which was set when the form was submitted using a combination of username and current date/time), but was instead based on the ID column (assigned by SharePoint). I considered two options:
  1. Utilise a workflow to rename the InfoPath form when it's submitted to the form library, so that its name is based on the ID column, for instance "Form-[ID].xml", where [ID] is the value of the ID column. The views could then remain using the Name column, but the actual value of the Name would be something more useful.
  2. Modify each of the views so that the Name column wasn't displayed, but the ID column was displayed and hyperlinked to the InfoPath form instance.
I evaluated each option, and eventually selected the first option because (for other reasons) we needed the form instance renamed. Implementing this option had its own challenges (workflow context metadata seems to take a while to be updated after you've renamed an item within the workflow) - but isn't the subject of this post. Instead I thought I'd share how I achieved the second option - for my own reference (in case I ever need to do something similar again) and in case it helps anyone else.

To start with I thought I could just remove the Name column and add the ID column to the view, then use SharePoint Designer to add the hyperlink using its "Common Tasks" menu. I opened the view in SharePoint Designer's "Split" view, hovered over the ID column in the "Design" pane until the "Common xsl:value-of Tasks" menu was displayed, and selected the option to "Show Link To Item". Saved the view, clicked the link and... Alas, the browser attempted to download the InfoPath XML file, not open it inside SharePoint. It seems as though the "Show Link To Item" option works OK with standard SharePoint lists, not so much when it's a form library and you want to open the InfoPath form using the SharePoint InfoPath form server. So back to the drawing board.

Next up I noticed that in addition to the standard "Name (linked to item with edit menu)" column there is also a "Name (linked to item)" column that doesn't generate the edit menu, but does include a hyperlink to (correctly) open the InfoPath form. I thought maybe I could copy the XSL stylesheet that is generated for the "Name (linked to item)" column and apply it to the ID column. Here's what I came up with:

<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" xmlns:o="urn:schemas-microsoft-com:office:office">
  <xsl:include href="/_layouts/xsl/main.xsl"/>
  <xsl:include href="/_layouts/xsl/internal.xsl"/>

  <xsl:param name="AllRows" select="/dsQueryResponse/Rows/Row[$EntityName = '' or (position() >= $FirstRow and position() <= $LastRow)]"/>
  <xsl:param name="dvt_apos">'</xsl:param>

  <xsl:template name="FieldRef_body.ID" ddwrt:dvt_mode="body" match="FieldRef[@Name='ID']" mode="body" ddwrt:ghost="" xmlns:ddwrt2="urn:frontpage:internal">

    <xsl:param name="thisNode" select="."/>
    <a href="
{$thisNode/@FileRef}" onclick="return DispEx(this,event,'TRUE','FALSE','{$thisNode/@File_x0020_Type.url}','{$thisNode/@File_x0020_Type.progid}','{$XmlDefinition/List/@DefaultItemOpen}','{$thisNode/@HTML_x0020_File_x0020_Type.File_x0020_Type.mapcon}','{$thisNode/@HTML_x0020_File_x0020_Type}','{$thisNode/@serverurl.progid}','{$thisNode/@CheckoutUser.id}','{$Userid}','{$XmlDefinition/List/@ForceCheckout}','{$thisNode/@IsCheckedoutToLocal}','{$thisNode/@PermMask}')" onfocus="OnLink(this)" onmousedown="return VerifyHref(this,event,'{$XmlDefinition/List/@DefaultItemOpen}','{$thisNode/@HTML_x0020_File_x0020_Type.File_x0020_Type.mapcon}','{$thisNode/@serverurl.progid}')"><xsl:value-of select="$thisNode/@*[name()=current()/@Name]" /></a>

  </xsl:template>
</xsl:stylesheet>


The key element seemed to be applying the four attribute values highlighted above (href, onclick, onfocus, onmousedown). Again, I saved the view, clicked the link and... success! The InfoPath form opened correctly!

Great, I thought. Now just a bit of tidy-up, I removed the "Name (linked to item)" column from the view (leaving my nicely hyperlinked ID column) and I was done. Except that the link didn't work any more. Huh?

Long and annoying story short, it seems as though in order for the XSL template above to work, you need to include one of the "linked" columns in the view (such as "Name (linked to item with edit menu)" or "Name (linked to item)"). But... that kind of defeated the purpose!

Eventually however I managed to come upon a solution where the column is included in the view, but an XSL template is applied to the column to hide it. Hence, the complete XSL template becomes:

<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" xmlns:o="urn:schemas-microsoft-com:office:office">
  <xsl:include href="/_layouts/xsl/main.xsl"/>
  <xsl:include href="/_layouts/xsl/internal.xsl"/>

  <xsl:param name="AllRows" select="/dsQueryResponse/Rows/Row[$EntityName = '' or (position() >= $FirstRow and position() <= $LastRow)]"/>
  <xsl:param name="dvt_apos">'</xsl:param>

  <xsl:template name="FieldRef_body.ID" ddwrt:dvt_mode="body" match="FieldRef[@Name='ID']" mode="body" ddwrt:ghost="" xmlns:ddwrt2="urn:frontpage:internal">

    <xsl:param name="thisNode" select="."/>
    <a href="
{$thisNode/@FileRef}" onclick="return DispEx(this,event,'TRUE','FALSE','{$thisNode/@File_x0020_Type.url}','{$thisNode/@File_x0020_Type.progid}','{$XmlDefinition/List/@DefaultItemOpen}','{$thisNode/@HTML_x0020_File_x0020_Type.File_x0020_Type.mapcon}','{$thisNode/@HTML_x0020_File_x0020_Type}','{$thisNode/@serverurl.progid}','{$thisNode/@CheckoutUser.id}','{$Userid}','{$XmlDefinition/List/@ForceCheckout}','{$thisNode/@IsCheckedoutToLocal}','{$thisNode/@PermMask}')" onfocus="OnLink(this)" onmousedown="return VerifyHref(this,event,'{$XmlDefinition/List/@DefaultItemOpen}','{$thisNode/@HTML_x0020_File_x0020_Type.File_x0020_Type.mapcon}','{$thisNode/@serverurl.progid}')"><xsl:value-of select="$thisNode/@*[name()=current()/@Name]" /></a>

  </xsl:template>

  <xsl:template name="FieldRef_header.LinkFilenameNoMenu" ddwrt:dvt_mode="header" match="FieldRef[@Name='LinkFilenameNoMenu']" mode="header" ddwrt:ghost="" xmlns:ddwrt2="urn:frontpage:internal">
   
  </xsl:template>

  <xsl:template name="FieldRef_printTableCell_EcbAllowed.LinkFilenameNoMenu" match="FieldRef[@Name='LinkFilenameNoMenu']" mode="printTableCellEcbAllowed" ddwrt:dvt_mode="body" ddwrt:ghost="" xmlns:ddwrt2="urn:frontpage:internal">
    <xsl:param name="thisNode" select="."/>
    <xsl:param name="class" />
   
  </xsl:template>

</xsl:stylesheet>


The key here are the two empty templates for the FieldRef[@Name='LinkFilenameNoMenu'] match, which hide the "Name (linked to item)" column.

The final piece was making this stylesheet reusable across a number of views, rather than having to copy/paste it into each view. This is where the <XslLink> element of the XsltListView web part comes in. You simply set the value of this element to the URL of your XSLT file. In my case, I loaded the XSLT file into my Site Assets library, and then included a relative URL to the file: the important thing to note when using a relative URL is that the URL seems to be relative to the location of the view or page the XsltListView web part is in - ie, it's NOT site relative... so you may need a URL like "../SiteAssets/MyTemplate.xsl" if for instance your view or page is located in eg a "SitePages" library.

Anyway, thanks for listening, hope this helps someone!

1 comment:

  1. Good stuff, but you went about the LinkFilename the hard way. You included the data in your normal ViewFields element, which added it as a column in the template which then had to be customized to blank it out. Instead, include a reference with the ViewData element(http://msdn.microsoft.com/en-us/library/aa543114(v=office.14).aspx), like so (replace square brackets with angular):

    [ViewData]
    [FieldRef Name="LinkFilename"/]
    [/ViewData]

    ReplyDelete