Click here to Skip to main content
Click here to Skip to main content

Zen and the art of XSLT rendering fields (SharePoint 2010)

, 17 Aug 2012
Rate this:
Please Sign up or sign in to vote.
Example: HOWTO Move the ECB from the Filename to Title, in a DocLib, preserving all native functionality.

Introduction

SharePoint 2010 has a lot of stuff that wasn't around in MOSS 2007, and in a lot of ways it's leaps and bounds ahead of the previous release. However, some things, which used to be pretty straightforward (although awkward and clunky to get just right,) in MOSS are now cryptic and mysterious in the latest -- probably not the latest for long -- release. They make it easy to develop your own custom field types with custom field classes for rendering fields in Edit/New/Display forms, but... If you're used to the old way, it's considerably more confusing to alter the rendering of a computed field in the List View.

Note: When I say 'computed' I'm not talking about 'calculated' columns that use the "Excel-like" formulas. Those are pretty easy to set up.

Background

I always find it best to explain something complicated with an example.  

Lets say that you have just imported 2000 files from your previous content management/version control system (how about FileNET?) into SharePoint and the file names are arcane gobbledygook and differ wildly from the human readable titles. What are all of the users going to think when they go to get their files and see a lot of xhggsdf12.docx and dfdfkkii.xslx file names? Of course, a first answer might be... "Oh that's easy I'll show the title column right next to it!" Then... you have a potential mess where the file names are still gibberish next to titles. You'd still probably want to show who last modified it, maybe also the modified date, and a thing or two else and if nothing else, the view is a bit on the crowded side. Having the title column be 'the thing of interest' but the menu being stuck on the garbage file name now presents a good bit of User Experience frustration. If you don't believe me, try it. Even hardened SharePoint veteran users (I know, they really don't exist,) will start to moan that they're looking one way and clicking another. It's just not right.

Another thing you might try is renaming the files with some kind of script on the way into SharePoint, or use a script to do so after you get them there. But that's pretty tedious, and lets face it, this isn't an article about renaming files. (Yes, perhaps my example is a little contrived. That's why I call it an example.)

Still another thing you might try is this SharePoint designer 'quick hack' that works nearly perfectly in Lists (non-document libraries,) employing the shiny new 'TitleLink' column. But if you put the menu on the Name (aka File) column it breaks the Open in SomeProgram... functionality. Not only that, but clicking on it doesn't even give you the document. It gives you the properties of the document (like a list item -- get it?) This is even worse than having the title next to the cryptic file names.

By this point, you've probably realized that this isn't going to be simple. Not on the surface, at least.

But fear not! We are developers, which means, we don't have to wait for the next version! We get to MAKE the rules! (Sort of.) We can move the menu/link functionality from the filename column to the title column AND have it behave exactly like the filename column! Menu, click and all! All I'm asking you to do is come watch a short presentation on my timeshare... no.. just kidding.

The answer is to use those things that SharePoint itself uses... XSLT stylesheets!

What-S-What-Who Stylesheets?

X-S-L Transformation Stylesheets. In SharePoint 2010, the rendering of List Views has abandoned the use of Collaborative Application Markup Language (CAML) 'RenderPatterns' in favor of XSLT. (CAML is still around, just not for rendering List Views.) In fact, the ListViewWebPart has been replaced by the XsltListViewWebPart -- you can verify this by mucking around in SharePoint Designer. I don't have time to get into exactly what these two different technologies are, but you can probably learn what you need to know of it by following along here. (Or you can read the tutorial I linked at the beginning of this paragraph. Too lazy to look all the way up there? Here's the link again.)

If you've never seen XSLT before, this is going to look pretty scary! XSLT basically translates XML from one form into another according to the rules of one or more stylesheets. These .xsl files are stylesheets. And they contain one or more (sometimes MANY more) templates, which are the nitty gritty instructions for the transformation. You might also think that XSLT is not exactly a 'language' as much as it is a dialect of XML. (There is speculation that XSLT is indeed Turing complete, so you could argue the point with me over coffee that it's just as much a language as Visual Basic or Beatnik, and before my second cup I'd probably have to agree.) Stylesheets can import other stylesheets. Each stylesheet contains one or more templates that may be invoked by matching a rule with an XPath expression, or by template name. You can call templates with parameters. SharePoint List Views Love parameters. (It's true. I've seen both of their names carved into hearts on the side of trees all over the city.)

How SharePoint itself uses these things

If you open %ProgramFiles%\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS\XSL on your SharePoint server you'll see a bunch of XSL files. The one that renders your List View is called main.xsl.

Let's have a look!

main.xsl:

 <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:import href="http://www.codeproject.com/_layouts/xsl/fldtypes.xsl"/>
  <xsl:import href="http://www.codeproject.com/_layouts/xsl/vwstyles.xsl"/>
  <xsl:output method="html" indent="no"/>
  <xsl:decimal-format NaN=""/>
  <xsl:param name="NavigateForFormsPages" />
  <xsl:param name="MasterVersion" select="3"/>
  <xsl:param name="TabularView"/>
  <xsl:param name="NoAJAX"/>
  <xsl:param name="WPQ"/>
  <xsl:param name="RowLimit" select="5"/>
  <xsl:param name="dvt_sortdir" select="'ascending'"/>
  <xsl:param name="dvt_sortfield" />
  <xsl:param name="WebPartClientID"/>
  <xsl:param name="dvt_filterfields" />
  <xsl:param name="dvt_partguid" />
  <xsl:param name="dvt_firstrow" select="1"/>
  <xsl:param name="dvt_nextpagedata" />
  <xsl:param name="dvt_prevpagedata" />
  <xsl:param name="XmlDefinition" select="."/>
  <xsl:param name="ViewCounter" select="'1'"/>
  <xsl:param name="View" />
  <xsl:param name="ListUrlDir"/>
  <xsl:param name="List" />
  <xsl:param name="Project"/>
  <xsl:param name="WebTitle"/>
  <xsl:param name="ListTitle"/>
 <!-- I DELETED A LOT OF STUFF -->
 </xsl:stylesheet>

As far as XSL stylesheets go, this one is pretty simple. The namespace junk at the beginning is something you can ignore for now. The first two lines of the actual stylesheet body just import other stylesheets. The next couple of lines are simple formatting instructions for the XSLT processor. The rest of the file consists of nothing but parameter declarations which are automatically populated by the XsltListViewWebPart before the transformation is run. That's a lot of parameters!

The most important part of this entire file, besides the very handy parameters you get to use in your stylesheets is the import of the fldtypes.xsl file.

fldtypes.xsl is SharePoint's set of stock templates for rendering all of its own built-in fields. That's right kids, if SharePoint wants to render a field on a List View, it does it the same way you and I do will do it: with XSLT! When you display a page with a List View web part on it, SharePoint executes the CAML query encoded in the view definition to produce some raw XML in the form of a dsQueryResponse element. Then, main.xsl is executed using Microsoft's XSLT processing engine, (MSXLT) against this dsQueryResponse to transform it into the pretty HTML and JavaScript we all know and love. You can see an example of the 'before and after' of this process here.

One of the nice things about this 'everyone does it the same way' thing is that if you want to know how X-feature is rendered, you can search in fldtypes.xsl for a fragment that you want to use and see how big Redmond does it.

So what is it that we're looking for?

I mentioned before that we want the menu (called the Edit Control Block Menu or just "ECB menu",) to appear over the Title column instead of just the filename. So our best bet is to figure out how it's done for the filename.

We need to find out how the filename field does this!

Let's just back up one second, and first think about something. Before we go transforming the output, we need to make sure we have all the input right. Just what is the filename field anyway? How does it tell SharePoint to read the necessary data into the dsQueryResponse? If you dig into the documentation on MSDN, you will eventually find out that the filename text, the part that you and I like to call the filename at least, is a lookup attribute called FileLeafRef. But that's just a single value. It's not a menu! It's not even in a table cell! How does this thing work?

The stuff you see on the page are created using Field definitions. These of course can be created using CAML. What is the Field definition for the filename column that has the menu on it?  What is the Field definition for the filename column that has the menu on it? Poking around in the documentation again, we see that the particular field definition for what is shown as "Name (linked to document with menu)" in the web interface is called LinkFilename.  It's a field of type "Computed." That is a loaded statement. I'll explain.

What exactly are Computed fields?

Computed fields are always 'computed' at render time. They store no value in the database themselves, but can use pieces of information from one or more other fields of the same item. (This is not the same as a Calculated field, which actually does store its value in the database, and is only updated when the item is changed.) They are rendered on the List Views using the XSL stylesheets -- either the built in SharePoint ones found in fldtypes.xsl, or one of the other *.xsl files that appear in the same directory. They include the contents of other fields by "referencing" them in their field definisions. When a CAML view query is executed it determines what fields it will need to load by checking the aggregated list of all of the referenced fields (declared using FieldRef child elements in CAML.) Computed fields can reference either fields that contain actual data in the database and/or other computed fields. This can get rather recursive.

Getting all the right fields

This possible 'inclusion recursion' means that tracking down a particular piece of data that is used in a template can be more complicated than it seems to be at first. You have to find all of the necessary elements and reference all of the necessary fields to make sure they appear in the query response in order for you to transform them as output. Fortunately, we have a guide for this, the Field definitions of the built in fields.

Cracking open SharePoint Manager (a nifty tool, and a MUST for developers,) and looking at the LinkFilename field's field definition, we see that it references only a few fields. 

<Field ID="{5cc6dc79-3710-4374-b433-61cb4a686c12}" Name="LinkFilename" SourceID="http://schemas.microsoft.com/sharepoint/v3" StaticName="LinkFilename" Group="Base Columns" ReadOnly="TRUE" Type="Computed" DisplayName="Name" DisplayNameSrcField="FileLeafRef" Filterable="FALSE" ClassInfo="Menu" AuthoringInfo="(linked to document with edit menu)">
  <FieldRefs>
    <FieldRef ID="{687c7f94-686a-42d3-9b67-2782eac4b4f8}" Name="FileLeafRef" />
    <FieldRef ID="{1344423c-c7f9-4134-88e4-ad842e2d723c}" Name="_EditMenuTableStart2" />
    <FieldRef ID="{2ea78cef-1bf9-4019-960a-02c41636cb47}" Name="_EditMenuTableEnd" />
    <FieldRef ID="{9d30f126-ba48-446b-b8f9-83745f322ebe}" Name="LinkFilenameNoMenu" />
  </FieldRefs>
 ... 

Notice that it does indeed reference that FileLeafRef field, but there are only 3 other fields... something tells me this is one of those aforementioned "recursive" field references. The ones responsible for the guts of the ECB menu are of course _EditMenuTableStart2 and _EditMenuTableEnd. The part that renders the filename as a clickable link that opens the document in the appropriate program with a pretty icon next to it is LinkFilenameNoMenu. We can infer without yet looking at the XSL that it's going to render the LinkFilename sandwiched between an _EditMenuTableStart2 and an _EditMenuTableEnd field. 

Now summing this up, I dug into each one and the resulting list of fields recursively chasing them down so that after some sifting and pasting into notepad, I have this list of field refs: 

      <FieldRef ID="{687c7f94-686a-42d3-9b67-2782eac4b4f8}" Name="FileLeafRef" />
      <FieldRef ID="{56605df6-8fa1-47e4-a04c-5b384d59609f}" Name="FileDirRef" />
      <FieldRef ID="{30bb605f-5bae-48fe-b4e3-1f81d9772af9}" Name="FSObjType" />
      <FieldRef ID="{1d22ea11-1e32-424e-89ab-9fedbadb6ce1}" Name="ID" />
      <FieldRef ID="{105f76ce-724a-4bba-aece-f81f2fce58f5}" Name="ServerUrl" />
      <FieldRef ID="{0c5e0085-eb30-494b-9cdd-ece1d3c649a2}" Name="HTML_x0020_File_x0020_Type" />
      <FieldRef ID="{39360f11-34cf-4356-9945-25c44e68dade}" Name="File_x0020_Type" />
      <FieldRef ID="{9d30f126-ba48-446b-b8f9-83745f322ebe}" Name="LinkFileNameNoMenu" />
      <FieldRef ID="{BA3C27EE-4791-4867-8821-FF99000BAC98}" Name="PermMask" />
      <FieldRef ID="{1df5e554-ec7e-46a6-901d-d85a3881cb18}" Name="Author" />
      <FieldRef ID="{3881510a-4e4a-4ee8-b102-8ee8e2d0dd4b}" Name="CheckedOutUserId" />
      <FieldRef ID="{c63a459d-54ba-4ab7-933a-dcf1c6fadec2}" Name="_SourceUrl" />
      <FieldRef ID="{26d0756c-986a-48a7-af35-bf18ab85ff4a}" Name="_HasCopyDestinations" />
      <FieldRef ID="{6b4e226d-3d88-4a36-808d-a129bf52bccf}" Name="_CopySource" />
      <FieldRef ID="{fdc3b2ed-5bf2-4835-a4bc-b885f3396a61}" Name="_ModerationStatus" />
      <FieldRef ID="{7841bf41-43d0-4434-9f50-a673baef7631}" Name="_UIVersion" />
      <FieldRef ID="{03e45e84-1992-4d42-9116-26f756012634}" Name="ContentTypeId" />  

"AH!" you say... "But we also need Title!" That's very astute of you to notice! Since we want to use the title field's value, we had better make sure it's in the list!

<FieldRef ID="{fa564e0f-0c70-4ab9-b863-0177e6ddd247}" Name="Title"/>
  

So we'll add that to the list. At this point we are ready to create our computed field definitions. We will make two of them, one for just the document title as a link with no menu, and one for the link with the menu.   Fire up Visual Studio 2010... 

Create the Visual Studio Project

Create a New Project selecting SharePoint 2010 -> Empty SharePoint Project 

Make sure on the second screen that you choose "Deploy as a farm solution" because we're going to have to publish a file into the XSL directory in Layouts. (Note: that means you cannot do this in a Sandboxed solution, and as a result, cannot deploy this to SharePoint Online.) 

Now, add a new item to the project, selecting the template for Content Type.  It doesn't matter what you call the content type, nor what its base type is. We're going to delete that part anyway and just add some site field definitions, but this will set up our Feature definition for us. 

Now your solution explorer should look like this:

You can rename the feature by right clicking on Feature1 and choosing "Rename." (Whatever you rename this to will end up being the name of your feature folder in TEMPLATES\Features after deployment.) I picked "DocLinkTitles." 

Then edit it by double clicking on the renamed file where you can change the feature name to something useful like "DocLinkTitles Feature" and say something descriptive in the description. I opted for "Feature containing the ability to move the ECB Menu to the Title column in document libraries."

Now we'll edit the Elements.xml file under the StupidContentType node.  Select everything from <ContentType> to </ContentType> and delete it. In its place, paste the following:

    <Field ID="{F936A51C-9E26-45AA-A3A5-63357913E7E9}" 
         Name="DocTitleOrNameNoMenu" 
         StaticName="DocTitleOrNameNoMenu" 
         DisplayName="Document"
         Type="Computed" 
         ShowInDisplayForm="FALSE" 
         ShowInEditForm="FALSE" 
         ShowInNewForm="FALSE"
         Description="Link to Title or FileName"
         AuthoringInfo=" (linked to document)"
         Overwrite="TRUE">
    <FieldRefs>
      <!-- just so we get all of the required fields in the view query and the xsl has all the goodies we need will work -->
      <FieldRef ID="{fa564e0f-0c70-4ab9-b863-0177e6ddd247}" Name="Title"/>
      <FieldRef ID="{687c7f94-686a-42d3-9b67-2782eac4b4f8}" Name="FileLeafRef" />
      <FieldRef ID="{56605df6-8fa1-47e4-a04c-5b384d59609f}" Name="FileDirRef" />
      <FieldRef ID="{30bb605f-5bae-48fe-b4e3-1f81d9772af9}" Name="FSObjType" />
      <FieldRef ID="{1d22ea11-1e32-424e-89ab-9fedbadb6ce1}" Name="ID" />
      <FieldRef ID="{105f76ce-724a-4bba-aece-f81f2fce58f5}" Name="ServerUrl" />
      <FieldRef ID="{0c5e0085-eb30-494b-9cdd-ece1d3c649a2}" Name="HTML_x0020_File_x0020_Type" />
      <FieldRef ID="{39360f11-34cf-4356-9945-25c44e68dade}" Name="File_x0020_Type" />
      <FieldRef ID="{9d30f126-ba48-446b-b8f9-83745f322ebe}" Name="LinkFileNameNoMenu" />
      <FieldRef ID="{BA3C27EE-4791-4867-8821-FF99000BAC98}" Name="PermMask" />
      <FieldRef ID="{1df5e554-ec7e-46a6-901d-d85a3881cb18}" Name="Author" />
      <FieldRef ID="{3881510a-4e4a-4ee8-b102-8ee8e2d0dd4b}" Name="CheckedOutUserId" />
      <FieldRef ID="{c63a459d-54ba-4ab7-933a-dcf1c6fadec2}" Name="_SourceUrl" />
      <FieldRef ID="{26d0756c-986a-48a7-af35-bf18ab85ff4a}" Name="_HasCopyDestinations" />
      <FieldRef ID="{6b4e226d-3d88-4a36-808d-a129bf52bccf}" Name="_CopySource" />
      <FieldRef ID="{fdc3b2ed-5bf2-4835-a4bc-b885f3396a61}" Name="_ModerationStatus" />
      <FieldRef ID="{7841bf41-43d0-4434-9f50-a673baef7631}" Name="_UIVersion" />
      <FieldRef ID="{03e45e84-1992-4d42-9116-26f756012634}" Name="ContentTypeId" />
    </FieldRefs>
  </Field>
 
 
 
 
  <Field ID="{B5C29F91-5A68-4D11-9F83-F68E10C7136D}"
         Name="DocTitleOrName"
         StaticName="DocTitleOrName"
         DisplayName="Document"
         Type="Computed"
         ShowInDisplayForm="FALSE"
         ShowInEditForm="FALSE"
         ShowInNewForm="FALSE"
         Description="Link to Title or FileName"
         AuthoringInfo=" (linked to document with menu)"
         Overwrite="TRUE">
    <FieldRefs>
      <!-- just so we get all of the required fields in the view query and the xsl has all the goodies we need will work -->
      <FieldRef ID="{fa564e0f-0c70-4ab9-b863-0177e6ddd247}" Name="Title"/>
      <FieldRef ID="{687c7f94-686a-42d3-9b67-2782eac4b4f8}" Name="FileLeafRef" />
      <FieldRef ID="{56605df6-8fa1-47e4-a04c-5b384d59609f}" Name="FileDirRef" />
      <FieldRef ID="{30bb605f-5bae-48fe-b4e3-1f81d9772af9}" Name="FSObjType" />
      <FieldRef ID="{1d22ea11-1e32-424e-89ab-9fedbadb6ce1}" Name="ID" />
      <FieldRef ID="{105f76ce-724a-4bba-aece-f81f2fce58f5}" Name="ServerUrl" />
      <FieldRef ID="{0c5e0085-eb30-494b-9cdd-ece1d3c649a2}" Name="HTML_x0020_File_x0020_Type" />
      <FieldRef ID="{39360f11-34cf-4356-9945-25c44e68dade}" Name="File_x0020_Type" />
      <FieldRef ID="{9d30f126-ba48-446b-b8f9-83745f322ebe}" Name="LinkFileNameNoMenu" />
      <FieldRef ID="{BA3C27EE-4791-4867-8821-FF99000BAC98}" Name="PermMask" />
      <FieldRef ID="{1df5e554-ec7e-46a6-901d-d85a3881cb18}" Name="Author" />
      <FieldRef ID="{3881510a-4e4a-4ee8-b102-8ee8e2d0dd4b}" Name="CheckedOutUserId" />
      <FieldRef ID="{c63a459d-54ba-4ab7-933a-dcf1c6fadec2}" Name="_SourceUrl" />
      <FieldRef ID="{26d0756c-986a-48a7-af35-bf18ab85ff4a}" Name="_HasCopyDestinations" />
      <FieldRef ID="{6b4e226d-3d88-4a36-808d-a129bf52bccf}" Name="_CopySource" />
      <FieldRef ID="{fdc3b2ed-5bf2-4835-a4bc-b885f3396a61}" Name="_ModerationStatus" />
      <FieldRef ID="{7841bf41-43d0-4434-9f50-a673baef7631}" Name="_UIVersion" />
      <FieldRef ID="{03e45e84-1992-4d42-9116-26f756012634}" Name="ContentTypeId" />
    </FieldRefs>
  </Field>
  

Note: If you've seen the 'built in' fields they all come with a bunch of extra stuff at the end of the FieldRefs element called DisplayPattern. We don't mess with those, because it's just for legacy versions of SharePoint. We ONLY use this field definition to specify that we want to include all of the other referenced fields in the query response. Specifying the Type attribute as "Computed" essentially means "OK SharePoint give me this data and XSL will take care of the rest." 

Save it and close it. 

Update note: I capitalized the FALSE values for the ShowInXxxForm attributes. Apparently for some properties this makes a difference and some it doesn't. I guess these are some of those that it does...  

What did we just make?

Now we've just created a SharePoint Farm solution that will be packaged as a WSP archive. This will contain one feature with two site columns defined. We could deploy it right now, but that wouldn't do us any good. Even though the computed columns can be added to lists as they are we still have two problems: We can't see Computed fields in Site Columns (but they are there!) and thus have no way to add them to a list.  Secondly, we have no XSL stylesheet that instructs the rendering of our newly created fields-full-of-awesome.

Lets tackle the stylesheet first. 

SharePoint will load the default fldtypes.xsl directly from within main.xsl, but the XsltListViewWebPart, at render time, will also load any other files named fldtypes*.xsl that exist in the %ProgramFiles%\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\Layouts\XSL folder.  That means we have to create a stylesheet with a set of templates matching our new fields, rendering them, and deploy them in a properly named stylesheet in the correct location. 

We will start by adding a mapped folder to Layouts in our project: 

Just right click on the project node (DocLinkTitles) in the Solution Explorer and select Add -> SharePoint "Layouts" Mapped Folder.  Anything we put in this folder in our project will be deployed to the corresponding directory \14\TEMPLATE\Layouts on the server.

You'll see that Visual Studio has kindly created us a DocLinkTitles folder under Layouts so that our content will be somewhat separated from the built in goodies and that of other people's solutions. That's all well and good but we need to put our stylesheet in the XSL directory, remember? So create a new folder directly under Layouts, NOT under DocLinkTitles.  In the XSL directory, create a blank file named fldtypes_doclinktitles.xsl

Paste the stylesheet contents into it. 

<?xml version="1.0" encoding="utf-8"?>
<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" ddwrt:oob="true">
  <xsl:output method="html" indent="no"/>
 
  <xsl:template match="FieldRef[@Name='DocTitleOrNameNoMenu']" mode="Computed_body" priority="9.0">
    <xsl:param name="thisNode" select="."/>
    <xsl:param name="ShowAccessibleIcon" select="0"/>
    <xsl:param name="folderUrlAdditionalQueryString" select="''"/>
    <xsl:param name="Position" select="1" />
    <xsl:call-template name="TitleOrFileNameBody">
      <xsl:with-param name="thisNode" select="$thisNode" />
      <xsl:with-param name="ShowAccessibleIcon" select="$ShowAccessibleIcon"/>
      <xsl:with-param name="folderUrlAdditionalQueryString" select="$folderUrlAdditionalQueryString"/>
    </xsl:call-template>
  </xsl:template>
 
  <xsl:template match="FieldRef[@Name='DocTitleOrName']" mode="Computed_body" priority="9.0">
    <xsl:param name="thisNode" select="."/>
    <xsl:param name="ShowAccessibleIcon" select="0"/>
    <xsl:param name="folderUrlAdditionalQueryString" select="''"/>
    <xsl:param name="Position" select="1" />
    <xsl:variable name="ID">
      <xsl:call-template name="ResolveId">
        <xsl:with-param name="thisNode" select ="$thisNode"/>
      </xsl:call-template>
    </xsl:variable>
    <xsl:variable name="PermMask">
      <xsl:choose>
        <xsl:when test="$thisNode/@PermMask != ''">
          <xsl:value-of select="$thisNode/@PermMask"/>
        </xsl:when>
        <xsl:otherwise>
          <xsl:value-of select="$ExternalDataListPermissions"/>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:variable>
    <xsl:choose>
      <xsl:when test="$EcbMode or $NoAJAX">
        <xsl:choose>
          <xsl:when test="$MasterVersion=4">
            <!-- Client JS uses 'itx' string to decide whether to create AJAX menu or not -->
            <!-- If AJAX is enabled, then we must include the 'itx' string at the end of the class -->
            <xsl:variable name="ClassName">
              <xsl:choose>
                <xsl:when test="$NoAJAX or $EcbMode">ms-vb</xsl:when>
                <xsl:otherwise>ms-vb itx</xsl:otherwise>
              </xsl:choose>
            </xsl:variable>
            <div class="{$ClassName}" onmouseover="OnItem(this)" CTXName="ctx{$ViewCounter}" id="{$ID}" Field="{@Name}"
              Url="{$thisNode/@FileRef.urlencodeasurl}" DRef="{$thisNode/@FileDirRef}" Perm="{$PermMask}" Type="{$thisNode/@HTML_x0020_File_x0020_Type}"
              Ext="{$thisNode/@File_x0020_Type}"
              OType="{$thisNode/@FSObjType}"
              COUId="{$thisNode/@CheckedOutUserId}" HCD="{$thisNode/@_HasCopyDestinations.value}"
              CSrc="{$thisNode/@_CopySource}" MS="{$thisNode/@_ModerationStatus.}" CType="{$thisNode/@ContentType}"
              CId="{$thisNode/@ContentTypeId}" UIS="{$thisNode/@_UIVersion}" SUrl="{$thisNode/@_SourceUrl}"
              Icon="{$thisNode/@HTML_x0020_File_x0020_Type.File_x0020_Type.mapall}"
              EventType="{$thisNode/@EventType}">
              <xsl:if test="$IsDocLib">
                <xsl:attribute name="sred">
                  <xsl:value-of select="$thisNode/@serverurl.progid"/>
                </xsl:attribute>
                <xsl:attribute name="defaultio">
                  <xsl:value-of select="$XmlDefinition/List/@DefaultItemOpen"/>
                </xsl:attribute>
                <xsl:attribute name="cout">
                  <xsl:value-of select="$thisNode/@IsCheckedoutToLocal"/>
                </xsl:attribute>
              </xsl:if>
              <xsl:call-template name="TitleOrFileNameBody">
                <xsl:with-param name="thisNode" select="$thisNode" />
                <xsl:with-param name="ShowAccessibleIcon" select="$ShowAccessibleIcon"/>
                <xsl:with-param name="folderUrlAdditionalQueryString" select="$folderUrlAdditionalQueryString"/>
              </xsl:call-template>
            </div>
            <!-- render the markup for list item chevron from server side -->
            <div class="s4-ctx" onmouseover="OnChildItem(this.parentNode); return false;">
              <span>&#160;</span>
              <a onfocus="OnChildItem(this.parentNode.parentNode); return false;" onclick="PopMenuFromChevron(event); return false;" href="javascript:;" title="{$open_menu}">
              </a>
              <span>&#160;</span>
            </div>
          </xsl:when>
          <xsl:otherwise>
            <table height="100%" cellspacing="0" class="ms-unselectedtitle" onmouseover="OnItem(this)" CTXName="ctx{$ViewCounter}" id="{$ID}"
             Url="{$thisNode/@FileRef.urlencodeasurl}" DRef="{$thisNode/@FileDirRef}" Perm="{$PermMask}" Type="{$thisNode/@HTML_x0020_File_x0020_Type}"
             Ext="{$thisNode/@File_x0020_Type}"
             OType="{$thisNode/@FSObjType}"
             COUId="{$thisNode/@CheckedOutUserId}" HCD="{$thisNode/@_HasCopyDestinations.value}"
             CSrc="{$thisNode/@_CopySource}" MS="{$thisNode/@_ModerationStatus.}" CType="{$thisNode/@ContentType}"
             CId="{$thisNode/@ContentTypeId}" UIS="{$thisNode/@_UIVersion}" SUrl="{$thisNode/@_SourceUrl}"
             DocIcon="{$thisNode/@HTML_x0020_File_x0020_Type.File_x0020_Type.mapall}"
             EventType="{$thisNode/@EventType}">
              <xsl:if test="$IsDocLib">
                <xsl:attribute name="sred">
                  <xsl:value-of select="$thisNode/@serverurl.progid"/>
                </xsl:attribute>
                <xsl:attribute name="defaultio">
                  <xsl:value-of select="$XmlDefinition/List/@DefaultItemOpen"/>
                </xsl:attribute>
                <xsl:attribute name="cout">
                  <xsl:value-of select="$thisNode/@IsCheckedoutToLocal"/>
                </xsl:attribute>
              </xsl:if>
              <tr>
                <td width="100%" class="ms-vb">
                  <xsl:call-template name="TitleOrFileNameBody">
                    <xsl:with-param name="thisNode" select="$thisNode" />
                    <xsl:with-param name="ShowAccessibleIcon" select="$ShowAccessibleIcon"/>
                    <xsl:with-param name="folderUrlAdditionalQueryString" select="$folderUrlAdditionalQueryString"/>
                  </xsl:call-template>
                </td>
                <td>
                  <img src="http://www.codeproject.com/_layouts/images/blank.gif" width="13" style="visibility:hidden" alt="" />
                </td>
              </tr>
            </table>
          </xsl:otherwise>
        </xsl:choose>
      </xsl:when>
      <xsl:otherwise>
        <xsl:choose>
          <xsl:when test="$MasterVersion=4">
            <div class="ms-vb itx" onmouseover="OnItem(this)" CTXName="ctx{$ViewCounter}" id="{$ID}" Field="{@Name}" Perm="{$PermMask}" EventType="{$thisNode/@EventType}">
              <xsl:call-template name="TitleOrFileNameBody">
                <xsl:with-param name="thisNode" select="$thisNode" />
                <xsl:with-param name="ShowAccessibleIcon" select="$ShowAccessibleIcon"/>
                <xsl:with-param name="folderUrlAdditionalQueryString" select="$folderUrlAdditionalQueryString"/>
              </xsl:call-template>
            </div>
            <!-- render the markup for list item chevron from server side -->
            <div class="s4-ctx" onmouseover="OnChildItem(this.parentNode); return false;">
              <span>&#160;</span>
              <a onfocus="OnChildItem(this.parentNode.parentNode); return false;" onclick="PopMenuFromChevron(event); return false;" href="javascript:;" title="{$open_menu}">
              </a>
              <span>&#160;</span>
            </div>
          </xsl:when>
          <xsl:otherwise>
            <table height="100%" cellspacing="0" class="ms-unselectedtitle itx" onmouseover="OnItem(this)" CTXName="ctx{$ViewCounter}" id="{$ID}" Field="{@Name}" Perm="{$PermMask}" EventType="{$thisNode/@EventType}">
              <tr>
                <td width="100%" class="ms-vb">
                  <xsl:call-template name="TitleOrFileNameBody">
                    <xsl:with-param name="thisNode" select="$thisNode" />
                    <xsl:with-param name="ShowAccessibleIcon" select="$ShowAccessibleIcon"/>
                    <xsl:with-param name="folderUrlAdditionalQueryString" select="$folderUrlAdditionalQueryString"/>
                  </xsl:call-template>
                </td>
                <td>
                  <img src="http://www.codeproject.com/_layouts/images/blank.gif" width="13" style="visibility:hidden" alt="" ddwrt:insideECB=""/>
                </td>
              </tr>
            </table>
          </xsl:otherwise>
        </xsl:choose>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>
 
 
  <xsl:template name="TitleOrFileNameBody">
    <xsl:param name="thisNode" select="."/>
    <xsl:param name="ShowAccessibleIcon" select="0"/>
    <xsl:param name="folderUrlAdditionalQueryString" select="''"/>
    <xsl:choose>
      <!-- if it's a folder, call LinkFilenameNoMenu and let the system do it's normal thing with it-->
      <xsl:when test="$thisNode/@FSObjType='1'">
        <xsl:call-template name="LinkFilenameNoMenu">
          <xsl:with-param name="thisNode" select="$thisNode"/>
          <xsl:with-param name="folderUrlAdditionalQueryString" select="$folderUrlAdditionalQueryString"/>
        </xsl:call-template>
      </xsl:when>
      <xsl:otherwise>
        <!-- warning: this code has optimization in webpart. Change it must change the webpart code too!-->
        <a onfocus="OnLink(this)" href="{$thisNode/@FileRef}" onmousedown="return VerifyHref(this,event,'{$XmlDefinition/List/@DefaultItemOpen}','{$thisNode/@HTML_x0020_File_x0020_Type.File_x0020_Type.mapcon}','{$thisNode/@serverurl.progid}')"
           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}')">
          <xsl:call-template  name="TitleValue">
            <xsl:with-param name="thisNode" select="$thisNode"/>
            <xsl:with-param name="ShowAccessibleIcon" select="$ShowAccessibleIcon"/>
          </xsl:call-template>
        </a>
        <xsl:if test="$thisNode/@Created_x0020_Date.ifnew='1'">
          <xsl:call-template name="NewGif">
            <xsl:with-param name="thisNode" select="$thisNode"/>
          </xsl:call-template>
        </xsl:if>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>
 
 
  <xsl:template name="TitleValue">
    <xsl:param name="thisNode" select="."/>
    <xsl:param name="ShowAccessibleIcon" select="0"/>
    <xsl:variable name="titlevalue" select="$thisNode/@Title"/>
    <xsl:choose>
      <xsl:when test="$titlevalue=''">
        <!-- no title? empty title? k. filename. -->
        <xsl:value-of select="$thisNode/@FileLeafRef.Name" />
      </xsl:when>
      <xsl:otherwise>
        <xsl:choose>
          <xsl:when test="$HasTitleField">
            <!-- if it's also at top level, it's already encoded, decode it.-->
            <xsl:value-of disable-output-escaping="yes" select="$titlevalue" />
          </xsl:when>
          <xsl:otherwise>
            <xsl:choose>
              <xsl:when test="string-length($titlevalue) = 0">
                <!-- no title? empty title? k. filename. -->
                <xsl:value-of select="$thisNode/@FileLeafRef.Name" />
              </xsl:when>
              <xsl:otherwise>
                <xsl:value-of select="$titlevalue" />
              </xsl:otherwise>
            </xsl:choose>
          </xsl:otherwise>
        </xsl:choose>
      </xsl:otherwise>
    </xsl:choose>
    <xsl:choose>
      <xsl:when test="$ShowAccessibleIcon">
        <img src="http://www.codeproject.com/_layouts/images/blank.gif" class="ms-hidden" border="0" width="1" height="1" alt="{$idPresEnabled}" />
      </xsl:when>
      <xsl:otherwise></xsl:otherwise>
    </xsl:choose>
  </xsl:template>
</xsl:stylesheet>
  

Save it. 

What does this stylesheet do?

This stylesheet contains some templates that are roughly based on the one for LinkFilename. I have two 'top level' templates, one that matches DocTitleOrNameNoMenu using the XPath expression "FieldRef[@Name='DocTitleOrNameNoMenu']" and one which matches DocTitleOrName using the XPath expression "FieldRef[@Name='DocTitleOrName']".  The first just calls another template TitleOrFileNameBody, and the second generates the ECB Menu first and THEN calls the same TitleOrFileNameBody template. (Making an ECB Menu sandwich!)

If it finds that the item is a folder, (ie the FSObjType variable is set to '1',) it will just call the built in SharePoint template for "LinkFilename."  Folders don't have Title attributes, so we let the default behavior take over.  

If there is no Title attribute, it defaults back to the file name. No harm no foul. 

Notice that on the attributes of the top level templates I've set two other interesting attributes mode="Computed_body" and priority="9.0".  The priority is not strictly necessary, but this is to guarantee that my template will be applied instead of any other one that matches the same elements, because I'm a greedy and heartless man and I want all of the rendering of my fields for myself.  SharePoint doesn't have any other templates that will match, but someone might deploy one tomorrow just to get me! 9.0 is the highest possible priority, so I'm a lock.

The match and mode attributes are my 'hooks' into SharePoint's built in fldtypes.xsl stylesheet. Deep in its bowels it calls a 'generic' <xsl:apply-templates match="." mode="Computed_body"> on any "Computed" fields that don't already have something else to render them, passing control off to any other templates that have an appropriate match expression and that specify the mode of "Computed_body." Since ours has both of these things, that will cause our template to get the nod and render the content. 

That's great but... 

Now if you deploy this, and you can find some way to add these fields to a list, they'll render, right as rain, but... how do we attach them to a list since they don't show up in Site Columns?

And for my last trick 

I have created a custom Application Page that will allow me to connect these bad boys to lists. 

I just added a New Item -> Application Page under Layouts\DocLinkTitles called FieldAdder.aspx.

 

The contents of the page are simple, a list box containing the document libraries on the site, and a button to add the fields to them.

FieldAdder.aspx (just the "Main" content place holder contents, not the whole file) 

<div class="description">
        Select which lists (by control+clicking) into which you'd like to inject the DocTitleOrName fields.
        You will have to add them to the view.
    </div>
    <asp:ListBox runat="server" ID="lboxDocLibs" SelectionMode="Multiple">
    </asp:ListBox>
    <asp:Label runat="server" Font-Bold="true" EnableViewState="false" ID="message" />
    <br />
    <asp:Button runat="server" OnClick="doButton_click" ID="doButton" Text="Do it!" />
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
    <asp:Button runat="server" OnClick="doneButton_click" ID="doneButton" Text="Done" />
  

FieldAdder.aspx.cs   

        protected override void OnInit(EventArgs e) {
            base.OnInit(e);
 
            // bind the dropdown
            foreach (SPList list in Web.Lists) {
                if (list.BaseTemplate == SPListTemplateType.DocumentLibrary) {
                    lboxDocLibs.Items.Add(new ListItem(list.Title));
                }
            }
        }
 
        protected void Page_Load(object sender, EventArgs e) {
        }
 
        protected void doButton_click(object sender, EventArgs e) {
            try {
                foreach (ListItem item in lboxDocLibs.Items) {
                    if (item.Selected) {
                        DoList(item.Text);
                    }
                }
            }
            catch (Exception ex) {
                SPUtility.TransferToErrorPage("There was an error adding the fields: " + ex.Message);
            }
 
            SPUtility.TransferToSuccessPage(
                    "The fields were successfully added! You will still need to add them to any views in which you want them to appear.");
        }
 
        private void DoList(string text) {
 

            var list = Web.Lists[text];
            if (list != null) {
 
                // only add them if they aren't there already.
                if (!list.Fields.Contains(DocTitleOrNameId)) {
                    var withMenu = Web.AvailableFields[DocTitleOrNameId];
                    list.Fields.Add(withMenu);
                }
                
                if (!list.Fields.Contains(DocTitleOrNameNoMenuId)) {
                    var withoutMenu = Web.AvailableFields[DocTitleOrNameNoMenuId];
                    list.Fields.Add(withoutMenu);
                }
 
            }
        }
 

        private static readonly Guid DocTitleOrNameNoMenuId = new Guid("{F936A51C-9E26-45AA-A3A5-63357913E7E9}");
        private static readonly Guid DocTitleOrNameId = new Guid("{B5C29F91-5A68-4D11-9F83-F68E10C7136D}");
 

So... Now build and deploy the solution. Make sure your deployment process includes an IISRESET so SharePoint will re-scan for new XSL files when it starts up again. 

 Let's go to our newly deployed application page and push these fields into our Shared Documents Library: 

Image depicting the use of the field adding application page. 

Note: If you're using the version in the download, I've added an 'undocumented' Custom Action that puts a link to the FieldAdder page in Site Settings. 

Now, go to the document library and add the fields to the view.

 

I've put the existing Name and Title columns on the page next to these two new ones so you can see that they are pretty much identical in function -- but the text is not the filename! 

Voila! It works! 

 

Feel free to deploy the attached WSP at your own risk.  I offer no warranty of any kind. You can also edit/modify this as you wish. Of course you can create any other custom field rendering you want, but this just walks through the example.  

History 

7/19/2012 - First Round, article created.
8/17/2012 - Fixed the capitalization of FALSE in properties of the field definitions.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

dave.dolan

United States United States
Dave works all day, and stays up all night coding and reading, surfing the intertubes.

Comments and Discussions

 
Questionweird sorting PinmemberSTeRennLT11-Mar-13 4:15 
AnswerRe: weird sorting Pinmemberdave.dolan11-Mar-13 4:23 
GeneralRe: weird sorting PinmemberSTeRennLT11-Mar-13 10:30 
QuestionQ: Can we remove the Title from "All documents" view and keep the Document field still working? Pinmemberterrychen012326-Nov-12 11:02 
AnswerRe: Q: Can we remove the Title from "All documents" view and keep the Document field still working? Pinmemberdave.dolan27-Nov-12 3:18 
GeneralRe: Q: Can we remove the Title from "All documents" view and keep the Document field still working? Pinmemberterrychen01233-Dec-12 4:17 
GeneralRe: Q: Can we remove the Title from "All documents" view and keep the Document field still working? Pinmemberdave.dolan3-Dec-12 11:42 
GeneralRe: Q: Can we remove the Title from "All documents" view and keep the Document field still working? Pinmemberterrychen01234-Dec-12 8:57 
GeneralRe: Q: Can we remove the Title from "All documents" view and keep the Document field still working? Pinmemberdave.dolan4-Dec-12 9:01 
GeneralRe: Q: Can we remove the Title from "All documents" view and keep the Document field still working? Pinmemberdave.dolan4-Dec-12 9:08 
AnswerRe: Q: Can we remove the Title from "All documents" view and keep the Document field still working? Pinmemberdave.dolan4-Dec-12 9:11 
GeneralRe: Q: Can we remove the Title from "All documents" view and keep the Document field still working? Pinmemberterrychen01234-Dec-12 9:30 
GeneralRe: Q: Can we remove the Title from "All documents" view and keep the Document field still working? Pinmemberdave.dolan4-Dec-12 9:32 
GeneralRe: Q: Can we remove the Title from "All documents" view and keep the Document field still working? Pinmemberterrychen01234-Dec-12 9:37 
GeneralRe: Q: Can we remove the Title from "All documents" view and keep the Document field still working? Pinmemberterrychen01234-Dec-12 14:19 
GeneralRe: Q: Can we remove the Title from "All documents" view and keep the Document field still working? Pinmemberterrychen01235-Dec-12 3:57 
GeneralMy vote of 5 PinmemberMarkHazleton24-Nov-12 7:21 
GeneralThe NEW! Icon Pinmemberdave.dolan20-Sep-12 7:58 
GeneralWorkflow error when field deployed Pinmembermsavyo15-Aug-12 23:34 
GeneralRe: Workflow error when field deployed Pinmemberdave.dolan6-Aug-12 0:30 
QuestionComputed columns appear on Edit/Display page PinmemberJörg Spilker26-Jul-12 18:53 
AnswerRe: Computed columns appear on Edit/Display page Pinmemberdave.dolan27-Jul-12 2:07 
GeneralRe: Computed columns appear on Edit/Display page PinmemberJörg Spilker27-Jul-12 12:51 
GeneralRe: Computed columns appear on Edit/Display page Pinmemberdave.dolan28-Jul-12 2:37 
QuestionJust noticed something Pinmemberdave.dolan20-Jul-12 16:45 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.140721.1 | Last Updated 17 Aug 2012
Article Copyright 2012 by dave.dolan
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid