Thursday, December 30, 2010

Feature stapling in sandbox solutions

In a recent posting of mine about the WebProvisioned event receiver in SharePoint 2010, I made a comparison between the latter and the feature stapling functionality which has been available since SharePoint 2007. In that posting I had the wrong assumption that feature stapling only works at farm level, but after a prompting comment I had to correct this. After another check of the SharePoint SDK, I saw that feature stapling can actually be scoped at three levels: farm, web application and site collection – which provides not only extra flexibility but also better isolation when necessary. When I understood that one can create a site collection scoped “feature stapling” feature, I asked myself whether it’s possible to provision such a feature in a sandbox solution (that’s the highest possible feature scope for a sandbox solution). I created a small POC project in a couple of minutes, deployed it and it turned out that feature stapling does work in sandbox solutions too.

The setup in my POC project was very simple – it contained two features – one site collection scoped (Scope="Site") and one site scoped (Scope="Web"). The latter was a dummy feature, containing no feature elements – its purpose was to be “stapled” by the stapling feature. The stapling feature was the site collection scoped one (naturally) – it contained one feature element file with two “FeatureSiteTemplateAssociation” elements – both were targeting the standard “blank site” site definition (STS#1), the first one specifying the dummy site scoped feature in the same sandbox solution and the second one – the standard “WikiPageHomePage” feature (this is the standard feature that creates the wiki page library in the standard “team site” and sets its default page to be the “home” page in the wiki page library). I used two feature template associations, because I wanted to make sure that both sandbox and farm solution features get stapled by a “feature stapling” feature in a sandbox solution – which turned out to be exactly so.

And let’s see what the possible benefits from the feature stapling (site collection scoped) in a sandbox solution may be compared to the “normal” feature stapling in farm solutions – I can see these two positive/advantageous points:

  • the first one stems from a general feature of the sandbox solutions – sandbox solutions can be installed and activated by site collection administrators (no need for farm administrator’s rights) and you don’t “pollute” the 14 hive with extra features.
  • the second one is better isolation and manageability – if you have the feature stapling features in a farm solution with the “Hidden” attribute set to FALSE, so that site collection administrators can see them and manage them (activate/deactivate) in the standard site settings pages, the stapling features will appear in the settings pages of all site collections in the SharePoint farm. If you on the other hand decide to have the stapling features with the “Hidden” attribute set to TRUE, then the site collection administrators won’t be able to manage them using the standard SharePoint UI. And on the other hand with a stapling feature in a sandbox solution you can safely leave the feature to be visible, since this visibility will be limited only to the site collection in which you have the sandbox solution activated. And even if the stapling feature is hidden, the site collection administrator will still be able to deactivate the containing sandbox solution and thus deactivate the stapling feature itself.

And now let me make another comparison of the feature stapling (site collection scoped), this time with the WebProvisioned event receiver (which was introduced in SharePoint 2010 – check this posting of mine for more details about the WebProvisioned event receiver) – here are some of the differences between the two:

  • With feature stapling you basically activate extra features to sites based on standard and custom site definitions and you configure the whole thing with XML in a feature element file, while with the WebProvisioned event receiver you write custom code which gets executed after the new site is created. In the WebProvisioned event receiver you can also activate features but you have to do that with code using the SharePoint object model.
  • When you use feature stapling you can use feature properties for the features that you staple. If you want to activate features in the WebProvisioned event receiver with code, you cannot provide feature properties – there is no public method in the SharePoint object model that allows that.
  • The lowest possible scope for feature stapling is the site collection level – meaning that all sub-sites in the site collection with the targeted site definitions will be affected. For the WebProvisioned event receiver you have the site collection scope but also the site scope. With the latter you target only the immediate children of the site to which you attach the WebProvisioned event receiver (this site may be the root site of the site collection but may also be a sub-site).
  • The time of the activation of the stapled features and the time of the execution of the code of the WebProvisioned event receiver in the timeline of the site provisioning differs and this may seriously impact your solution depending on the specifics of your case. Here is the order of activation of the standard elements in the ONET.XML of a site definition/web template:

    1. site collection scoped features from the onet.xml
    2. site collection scoped stapled features
    3. site scoped features from the onet.xml
    4. site scoped stapled features
    5. “List” elements from the onet.xml
    6. “Module” elements from the onet.xml

    In the case of site collection scoped “feature stapling” features we can obviously staple only site scoped (Scope="Web") features, but as you see the time of execution of the site scoped stapled features is before the activation of the “List” and “Module” elements, which means that when your stapled feature gets activated your site is not fully provisioned and you may miss some of the SharePoint lists and some of the files/pages in the site depending on the site definition that is used for the site.  On the other hand the custom code of the WebProvisioned event receiver gets executed after the target site is fully provisioned and you will have all artifacts in the site already created which you can further modify/adjust with the code of the receiver.

Tuesday, November 30, 2010

SharePoint 2007 – associated lookup columns

No, this is not a mistake – we can also have associated lookup columns in SharePoint 2007 [update (Dec 13, 2010): check for two drawbacks in SharePoint 2007 below], though not with the extended UI creation capabilities available in SharePoint 2010. Let’s first have a look at this new functionality in SharePoint 2010 (and define more accurately the terminology – I don’t think that there is a specific term for this lookup field extension, but in this article I will call it as I did in the title of the posting):

image

So, as you can see from the screenshot (and probably tried that many times already yourself), when you create a new lookup column in a list, you have the option to select more than one column from the target lookup list (in SharePoint 2007 we had the option to select just one “show column”). Basically what SharePoint does when you create a lookup field from the UI is to create the “normal” lookup column as before and then create another read-only “associated” lookup column for every additional show field from the check-box list that you have selected. And this functions as follows – when you create a new item, or edit an existing one in the edit form you see only the “normal” lookup column there, but when you change it to point to another item in the lookup list, all “associated” lookup columns change their values so that they correspond to the related columns in the newly selected lookup item. The associated lookup columns are read-only but you can add them to the views of the list, so that you display all additional columns that you need from the lookup item. And since the main lookup and the auxiliary lookup fields are automatically synchronized (SharePoint does that for us), you don’t need any additional code in item event receivers for instance.

Let’s now have a look at the field schema of the “normal” and “associated” lookup columns:

<Field Type="Lookup" DisplayName="Department" Required="FALSE" EnforceUniqueValues="FALSE" List="{ceaf935e-b9c6-48a0-8c23-bcec58a24c91}" ShowField="Title" UnlimitedLengthInDocumentLibrary="FALSE" RelationshipDeleteBehavior="None" ID="{98948dfd-cea5-4d6c-ac47-25bafa5218de}" SourceID="{bca1cd44-3822-49b6-b68c-2ff28ced1726}" StaticName="Department" Name="Department" ColName="int1" RowOrdinal="0" Group="" />

This is the schema of the “normal” or main lookup column – as you see, there is nothing specific in its schema – it is just a regular lookup column. And this is the schema of one of the associated lookup columns that I created for the lookup column above:

<Field Type="Lookup" DisplayName="Department:Code" List="{ceaf935e-b9c6-48a0-8c23-bcec58a24c91}" WebId="4ee36ddf-5b1b-470b-9f9a-fbd970edf5aa" ShowField="Code" FieldRef="98948dfd-cea5-4d6c-ac47-25bafa5218de" ReadOnly="TRUE" UnlimitedLengthInDocumentLibrary="FALSE" ID="{cd2b77b9-6238-4e2e-99ee-826826dc09f2}" SourceID="{bca1cd44-3822-49b6-b68c-2ff28ced1726}" StaticName="Department_x003a_Code" Name="Department_x003a_Code" Version="1" />

So, as you see we have all attributes for a normal lookup column, but one extra attribute as well – “FieldRef”. The value of this attribute is a Guid and this is exactly the ID attribute of the main lookup column. So, obviously the “FieldRef” attribute is the one that defines this association between the main and the associated lookup columns. Notice also the “ReadOnly” attribite in the associated lookup field’s schema which is set to TRUE – this guarantees that the associated lookup column doesn’t appear in the new and edit forms of the SharePoint list (you don’t need it there anyway, since it is synchronized automatically).

This is indeed a very useful new functionality in SharePoint 2010, but you may already ask yourself what SharePoint 2007 has to do in the whole matter – we don’t have this functionality there. Well, it is true that SharePoint 2007 lacks the user interface for creating “associated” lookup columns but this doesn’t mean that we cannot create columns with code specifying the values for all attributes in their schema as we decide. And it turns out that when we create a lookup column in a list and set its “FieldRef” attribute to “point” to another lookup column in the same list (both columns should target one and the same lookup list) – the “associated” lookup functionality actually works – just like in SharePoint 2010 (note here – I tested this on SharePoint 2007 service pack 2). Another detail here is that you shouldn’t forget to set the “ReadOnly” attribute of the field to TRUE – otherwise the “associated” lookup column will appear in the edit form of the list and when you try to save the list item, the page will crush with a nasty error.

As to the question of how you can create a lookup column (an associated lookup column) with code (and set its FieldRef attribute) – probably the easiest way is to use the SPFieldCollection.AddFieldAsXml method, which accepts a string parameter containing the schema XML of the new field. Note that you will have to properly format the schema XML of the associated lookup field providing the correct values to its attributes (the final XML schema should look similar to the one above):

  • in the ID attribute – a unique Guid value
  • in the Name and StaticName attributes – unique (within the SharePoint list) internal name of the field
  • in the List attribute – the ID of the lookup list
  • in the WebId attribute – the ID of the web containing the lookup list (in most cases this is the web of the current list)
  • in the FieldRef attribute – the ID of the main lookup field to which we want to associate the new lookup column
  • in the ShowField attribute – the internal name of the column in the lookup list whose value should be displayed in the new lookup column
  • in the ReadOnly attribute – this should be set to TRUE

And the net result will look something similar to this:

image

In the sample screenshot, the “Department” column is the main lookup column (displaying the “Title” column from the lookup list) and the “Department country” column is the associated lookup column (displaying the custom “Country” text column from the lookup list). Note also that the value displayed in the list view in the column of the associated lookup field is not rendered as a link, unlike the value in the column of the main lookup – actually this is exactly the same behavior as we have in SharePoint 2010. And most importantly again the same as in SharePoint 2010, the values of the associated lookup column or columns get automatically synchronized with the changes of the selected lookup item in the main lookup field.

[update Dec 13, 2010]

Shortly after the publishing of this article there was a comment (by Szymon) that once you create an associated lookup field you cannot delete it – something that I have overlooked. The error that you receive is “One or more field types are not installed properly. Go to the list settings page to delete these fields.” The reason for that is some kind of restriction in SharePoint 2007 (in SharePoint 2010 it works perfectly well) and I have seen the same error when I tried to delete “computed” (not to be confused with “calculated”) fields. After some research the one common thing between the associated lookup and the computed fields is that they both lack the “ColName” attribute, which means that there is no associated database column in the SharePoint “all user data” SQL table. Which also means that these types of columns don’t take space in the content database as one can expect. The bottom line is that you have a field that doesn’t take space in the SharePoint content database, but you still can’t get rid of its schema. As a work around you can set also its “Hidden” attribute so that the field doesn’t appear in the available view fields of the list in the standard SharePoint UI. Still, it is important that you know that once created you cannot delete (at least not using standard SharePoint object model) the associated lookup fields.

The other drawback that you have with associated lookup columns in SharePoint 2007 is that if you use them in list views or as view fields in SPQuery objects you need to always specify the main lookup column as well. If you miss it, you will receive the ugly SharePoint error “Cannot complete this action”.

Wednesday, November 24, 2010

XsltListViewWebPart – how to display columns based on permissions

The standard SharePoint security trimming in the XsltListViewWebPart works on the list item level – the items in the displayed SharePoint list may inherit the permissions from their parent list, but may also have different permissions and based on these the viewing user depending on his rights may see different result sets. In most cases this “horizontal” security trimming will be sufficient for the end users, but there may be cases when a “vertical” type of security trimming is also requested – meaning that certain columns in the list view are displayed only to users with particular rights for the target SharePoint list.

So, I created a small “proof-of-concept” type solution that demonstrates how this can be achieved. The implementation is fairly simple and involves only changing of the XSL used by the web part and modifying its “PropertyBindings” property. The modifications of the rendering XSL are obviously the core part of the implementation but you may wonder what the purpose of using the “PropertyBindings” property of the XsltListViewWebPart is (if you are not familiar with the “PropertyBindings” property and how to use it, you can check out my extensive posting on the subject here). The idea is simple – in the “PropertyBindings” property you can keep configuration information that also becomes available in the XSL of the web part. And the configuration data that is needed in our case is the permissions’ data for the list columns that the web part displays – it is about what rights exactly the user should have for the target list, so that he can see one or another column in the list view. The question here is why put this configuration in the web part’s property bindings and not in the fields’ schema itself for example (the field schema can easily be extended with custom attributes – e.g. xmlns:PermissionMask="0x7FFFFFFFFFFFFFFF"). The main reason for this is that if you use custom attributes in the field schema XML, you cannot then use them in the rendering XSL of the XLV web part, custom attributes simply don’t appear in the view schema XML that is available through the “$XmlDefinition” XSL parameter (check this MSDN article for a sample view schema XML in the XLV’s XSL – the field schema data is in the View/ViewFields/FieldRef elements). Another point here is that even if it were possible to store the field permission data in the field’s schema, this would impact all list views that use the customized XSL (and display the particular column), and with the setting of the “PropertyBindings” property only the current XLV web part will be affected. It is hard to judge whether this is of advantage or disadvantage  and probably depends on the specific case that you may have.

And let me first show you the “PropertyBinding” XML that should be added to the “PropertyBindings” property (I said “added”, because the property normally already contains the XML of other property bindings):

<ParameterBinding Name="ColumnPermissions" DefaultValue="Author|9223372036854775807|Editor|756052856929" />

The “Name” attribute specifies the name of the xsl:param element that will be initialized with the value of the property binding in the web part’s XSL. Its value is in the “DefaultValue” attribute – it is a long string containing values delimited by the ‘|’ character. At odd positions you have field names (internal names actually) and at even positions you see big numbers, which are actually the permission masks that should be applied for the list columns which precede the corresponding number. The permission mask is determined by the standard SharePoint SPBasePermissions enumeration: 9223372036854775807 (hex 7FFFFFFFFFFFFFFF) corresponds to the “Full Control” permission level and 756052856929 (hex B008431061) corresponds to the “Contribute” permission level. This means that the user will see the “Author” (“Created by”) column only if he has “Full Control” rights and the “Editor” (“Modified by”) column only if he has “Contribute” rights for the SharePoint list that is displayed. Note that all fields that are not specified in the property binding will be always visible to all users.

Let’s now move to the custom XSL that should handle the rendering and display only the columns that the current user has rights to see. Before, I show you the complete source of the custom rendering XSL, I want to draw your attention to one important thing – the trick that is used in the XSL to check whether the permission masks for the fields have a match with the effective permissions of the current user for the source SharePoint list. It is very simple actually and uses … a standard SharePoint “ddwrt” XSLT extension method:

<xsl:if test="ddwrt:IfHasRights($checkResult)">

The “IfHasRights” extension method receives an integer parameter for the permission mask and returns true or false depending on whether the current user has those rights for the SharePoint list of the web part. Note that the check is made for the SharePoint list, not the items of the list and not for its parent SharePoint site.

And here is the complete source of the custom XSL (check the extensive comments inside it for more details)

<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">

 

  <!-- import the standard main.xsl, so we have all standard stuff -->

  <xsl:import href="/_layouts/xsl/main.xsl"/>

  <xsl:output method="html" indent="no"/>

 

  <!-- we get here the field permissions configuration from the PropertyBinding with the same name -->

  <xsl:param name="ColumnPermissions" />

  <!-- this is the standard XmlDefinition parameter - the XLV initializes this one with the view schema -->

  <xsl:param name="XmlDefinition" />

 

  <!-- this variable contains the parsed configuration data like <token>Author</token><token>9223372036854775807</token> etc -->

  <xsl:variable name="tokens">

    <xsl:call-template name="Tokenize">

      <xsl:with-param name="string" select="$ColumnPermissions" />

      <xsl:with-param name="delimiter" select="'|'" />

    </xsl:call-template>

  </xsl:variable>

 

  <!-- here we create a copy of the original XmlDefinition removing all View/ViewFields/FieldRef elements for which the user doesn't have rights -->

  <xsl:variable name="XmlDefinition2Raw">

    <xsl:apply-templates mode="transform-schema" select="$XmlDefinition" >

      <xsl:with-param name="tokenSet" select="msxsl:node-set($tokens)" />

    </xsl:apply-templates>

  </xsl:variable>

 

  <!-- the one above is a sequence of tags, in order that it can be used exactly like the standard $XmlDefinition it should be converted to a node set -->

  <xsl:variable name="XmlDefinition2" select="msxsl:node-set($XmlDefinition2Raw)" />

 

  <!-- this one is simply a copy of the template with the same match from the standard vwstyles.xsl (thus we override it), the only difference is that it uses our trimmed $XmlDefinition2 instead of the standard $XmlDefinition -->

  <xsl:template match="/">

    <xsl:choose>

      <xsl:when test="$RenderCTXOnly='True'">

        <xsl:call-template name="CTXGeneration"/>

      </xsl:when>

      <xsl:when test="($ManualRefresh = 'True')">

        <xsl:call-template name="AjaxWrapper" />

      </xsl:when>

      <xsl:otherwise>

        <xsl:apply-templates mode="RootTemplate" select="$XmlDefinition2"/>

      </xsl:otherwise>

    </xsl:choose>

  </xsl:template>

 

  <!-- the same as the template above -->

  <xsl:template name="AjaxWrapper" ddwrt:ghost="always">

    <table width="100%" border="0"  cellpadding="0" cellspacing="0">

      <tr>

        <td valign="top">

          <xsl:apply-templates mode="RootTemplate" select="$XmlDefinition2"/>

        </td>

        <td width="1%" class="ms-vb" valign="top">

          <xsl:variable name="onclick">

            javascript: <xsl:call-template name="GenFireServerEvent">

              <xsl:with-param name="param" select="'cancel'"/>

            </xsl:call-template>

          </xsl:variable>

          <xsl:variable name="alt">

            <xsl:value-of select="$Rows/@resource.wss.ManualRefreshText"/>

          </xsl:variable>

          <a href="javascript:" onclick="{$onclick};return false;">

            <img src="/_layouts/images/staticrefresh.gif" id="ManualRefresh" border="0" alt="{$alt}"/>

          </a>

        </td>

      </tr>

    </table>

  </xsl:template>

 

  <!-- this template creates the copy of the standard $XmlDefinition trimming the View/ViewFields/FieldRef elements for which the user doesn't have rights -->

  <xsl:template mode="transform-schema" match="View" >

    <xsl:param name="tokenSet" />

    <!-- copy the root View element -->

    <xsl:copy>

      <!-- copy the root View element's attributes -->

      <xsl:copy-of select="@*"/>

      <!-- copy the child elements of the root View element -->

      <xsl:for-each select="child::*">

        <xsl:choose>

          <xsl:when test="name() = 'ViewFields'">

            <!-- special handling of the ViewFields element -->

            <ViewFields>

              <!-- iterate the ViewFields/FieldRef elements here -->

              <xsl:for-each select="child::*">

 

                <!-- get the permission mask for the FieldRef element, by the Name attribute -->

                <xsl:variable name="checkResult">

                  <xsl:call-template name="GetValueFromKey">

                    <xsl:with-param name="tokenSet" select="$tokenSet" />

                    <xsl:with-param name="key" select="./@Name" />

                  </xsl:call-template>

                </xsl:variable>

 

                <xsl:choose>

                  <!-- if the permission mask is not empty and the ddwrt:IfHasRights returns true, copy the field -->

                  <xsl:when test="$checkResult != ''">

                    <!-- this is how we check whether the user has sufficient rights for the field (checking the permission mask of the field against the user's permissions for the source list) -->

                    <xsl:if test="ddwrt:IfHasRights($checkResult)">

                      <xsl:copy-of select="."/>

                    </xsl:if>

                  </xsl:when>

                  <xsl:otherwise>

                    <!-- if we don't have the field in the configuration simply copy the FieldRef element -->

                    <xsl:copy-of select="."/>

                  </xsl:otherwise>

                </xsl:choose>

 

              </xsl:for-each>

            </ViewFields>

          </xsl:when>

          <xsl:otherwise>

            <xsl:copy-of select="."/>

          </xsl:otherwise>

        </xsl:choose>

      </xsl:for-each>

    </xsl:copy>

  </xsl:template>

 

  <!-- several helper templates that parse the configuration string and return the permission mask for the field by providing the field's internal name -->

  <xsl:template name="GetValueFromKey">

    <xsl:param name="tokenSet" />

    <xsl:param name="key" />

    <xsl:apply-templates select="$tokenSet/token[text() = $key]" />

  </xsl:template>

 

  <xsl:template name="NextNode" match="token">

    <xsl:value-of select="following-sibling::*"/>

  </xsl:template>

 

  <xsl:template name="Tokenize">

    <xsl:param name="string" />

    <xsl:param name="delimiter" select="' '" />

    <xsl:choose>

      <xsl:when test="$delimiter and contains($string, $delimiter)">

        <token>

          <xsl:value-of select="substring-before($string, $delimiter)" />

        </token>

        <xsl:call-template name="Tokenize">

          <xsl:with-param name="string" select="substring-after($string, $delimiter)" />

          <xsl:with-param name="delimiter" select="$delimiter" />

        </xsl:call-template>

      </xsl:when>

      <xsl:otherwise>

        <token>

          <xsl:value-of select="$string" />

        </token>

        <xsl:text> </xsl:text>

      </xsl:otherwise>

    </xsl:choose>

  </xsl:template>

 

</xsl:stylesheet>

One note about the XSL code – if you have a look at it, you will notice that it replaces (overrides) two of the core XSL templates used in the standard vwstyles.xsl file. It then provides a modified copy of the standard view schema XSL parameter ($XmlDefinition) in these templates. And on the other hand if you check the vwstyles.xls file, you will notice that there are still other XSL templates in it that also use the standard $XmlDefinition parameter (and thus not the modified copy) – these are the templates that handle aggregations and groupings, which means that the customized XSL above won’t be able to handle properly these cases.

And finally a few words on how to use this sample: the first thing is to save the XSL to a XSL file in the TEMPLATE\LAYOUTS or TEMPLATE\LAYOUTS\XSL folder (a subfolder of these two is also possible). Then you need to select your XLV web part (it may be an XLV from a standard list view page or an XLV that you placed on a content page) and change its PropertyBindings and XslLink properties. You can do that with code or using my web part manager utility which provides an easy to use UI for that (you can download it from here). For the “PropertyBindings” property, you should append the XML of the field permissions configuration which should look like the sample above. In the “XslLink” property you should specify the link to the XSL file in which you have saved the custom XSLT above – provided you’ve saved the XSL file as TEMPLATE\LAYOUTS\columns_main.xsl, the value that you should put in the “XslLink” property should be: /_layouts/columns_main.xsl.

Friday, October 22, 2010

XsltListViewWebPart – how to hide the ribbon

A reader of my blog asked me this in a comment to one of the previous postings and I had also wondered about it myself on several occasions. The thing is that sometimes you may not want the ribbon to appear when you select (clicking on the web part basically selects it) the XLV web part or select some of the items that the web part displays. The solution to this one turned out to be pretty simple and straight-forward – it involves creating a small custom XSLT file which does the trick with several lines of javascript (the selection of the web part and the displaying/hiding of the ribbon are all implemented with javascript in standard SharePoint 2010, so it is pretty logical to counter this also with javascript). The replacing of the standard “main.xsl” in the XslLink property of the web part and the setting of one additional property of its hidden SPView are then the only thing left that you need to fulfill the task (I’ll come to that in a moment).

So let me first shortly explain which are the javascript “culprits” that cause the page to display the “list” context specific ribbon when you click on the web part (not on a particular item – that’s a different case) or check the web part’s selection checkbox at the top right corner. If you inspect closely the HTML of the page containing the XLV web part you will be able to find these two HTML elements:

<td id="MSOZoneCell_WebPartWPQ2" valign="top" class="s4-wpcell" onkeyup="WpKeyUp(event)" onmouseup="WpClick(event)">

 

<input type="checkbox" id="SelectionCbxWebPartWPQ2" class="ms-WPHeaderCbxHidden" title="Select or deselect test Web Part" onblur="this.className='ms-WPHeaderCbxHidden'" onfocus="this.className='ms-WPHeaderCbxVisible'" onkeyup="WpCbxKeyHandler(event);" onmouseup="WpCbxSelect(event); return false;" onclick="TrapMenuClick(event); return false;" />

The first one is one of the top container elements of the XLV web part – have a look at its “onmouseup” attribute – it is this javascript bit that triggers the selection of the web part and the appearing of the parent list’s contextual ribbon. The second element is the selection checkbox control itself. Notice the “id” attributes of these two HTML elements – they both have one and the same suffix – “WPQ2” – this is actually something like an web part index which in case you have more than one web parts on the page can be used to identify these two elements for a specific web part.

And let’s go directly to the custom XSLT that hides the ribbon when used in the XLV web part:

<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">

  <!-- import the standard main.xsl so that we have all standard stuff -->

  <xsl:import href="/_layouts/xsl/main.xsl"/>

  <!-- this template was copied from the standard vwstyles.xsl -->

  <xsl:template match="/">

    <!-- only this javascript block was added -->

    <script>

      try

      {

        // remove the click handler of the containing element

        $get('MSOZoneCell_WebPart<xsl:value-of select="$WPQ"/>').onmouseup = function (){};

        // remove the TD element containing the wp selection checkbox in the wp's header

        $get('SelectionCbxWebPart<xsl:value-of select="$WPQ"/>').parentNode.parentNode.style.display = 'none';

      }

      catch (ex) {}

    </script>

    <xsl:choose>

      <xsl:when test="$RenderCTXOnly='True'">

        <xsl:call-template name="CTXGeneration"/>

      </xsl:when>

      <xsl:when test="($ManualRefresh = 'True')">

        <xsl:call-template name="AjaxWrapper" />

      </xsl:when>

      <xsl:otherwise>

        <xsl:apply-templates mode="RootTemplate" select="$XmlDefinition"/>

      </xsl:otherwise>

    </xsl:choose>

  </xsl:template>

</xsl:stylesheet>

As you see, it is pretty concise and doesn’t need much explanation as to what it does and how it does it – I also put several comments inside it, so that you can get a better clue of its workings. Notice that the “WPQ” index that I mentioned above is actually available in an XSL parameter (with the same name) that the XLV web part has initialized for you, so the localizing of the two HTML elements in the javascript becomes a really easy task.

This snippet should be saved as a file in the TEMPLATE\LAYOUTS\XSL folder (I saved it as “main_noribbon.xsl”). After you have the XSL file you will need to set the XLV web part to use it – you have two options here – to set the XslLink property of the web part (the value in this case should be: /_layouts/xsl/main_noribbon.xsl) – you can do that easily using the SharePoint UI. The other option is to set the XslLink property of the associated hidden SPView of the web part (the value in this case is simply the name of the file: main_noribbon.xsl) – the standard UI can’t be used in this case, so you will need some lines of code here (see below).

And … you need one more thing to have it all working. Apart from the ability to select the whole web part, the XLV allows you to select individual item rows which also results in displaying the ribbon. To override this you won’t need extra XSL because the XLV web part provides this as an option out of the box – it is about the so called “view styles” that you can use for list views and hence XLV web parts. The view style can be changed easily from the standard UI – you click the “modify view” button in the ribbon (you have already disabled the ribbon? – no worries – you can do the same from the edit tool-pane of the XLV – it is the “Edit the current view” link there). In the standard “Edit View” page that will open you can scroll down and locate the “Style” section, expand it and then in the “View Styles” list box select the topmost option – “Basic Table”. This view style is pretty much the same as the “Default” one save the ability to select the item rows.

As you saw, you can apply the changes (once you have the XSL file in place) using the standard SharePoint UI alone. And here is how you can do it with code:

private static void XLVHideRibbon(SPWeb web, XsltListViewWebPart xlv)

{

    // if the page is in a library that requires check out, the file containing the XLV should be checked out before calling this method

 

    // get the hidden view of the XLV web part

    SPView hiddenView = web.Lists[new Guid(xlv.ListName)].Views[new Guid(xlv.ViewGuid)];

    // set its XslLink property to reference the custom "no ribbon" XSLT file

    hiddenView.XslLink = "main_noribbon.xsl";

 

    // web.ViewStyles[0] - Basic Table

    hiddenView.ApplyStyle(web.ViewStyles[0]);

    // update the hidden view

    hiddenView.Update();

}

Note that instead of setting the XslLink property of the XLV web part, I set the XslLink property of its hidden SPView, but this basically achieves the same effect.

Monday, October 18, 2010

ListViewWebPart – set the toolbar with code without reflection

[update (Aug 25, 2011): this solution works only for standard SharePoint lists, whose schema definitions (in the schema.xml file) already contain view definitions which do not have the toolbar (usually view definitions with BaseViewID=0) – see details below]

After several postings about the XLV web part in SharePoint 2010, let’s take a step back and have a look at its predecessor – the ListViewWebPart from SharePoint 2007. And more precisely about the problem with the list view toolbar when you add the web part with code. Basically the problem is that the web part gets added with the toolbar containing the “New”, “Actions” and “Settings” buttons which you normally see in the default list view pages like the “AllItems.aspx” one, and which in probably more than 90% of the cases you don’t want to see in the custom page, to which you are adding the web part.

There is already a solution to this problem (i.e. to remove the toolbar after you add the web part), which you can find in at least a dozen SharePoing blogs in internet, but the thing is that the solution in question uses reflection. Which is not a good thing at all. And actually the original solution stopped working after the Infrastructure update for MOSS 2007, so a small extra bit of reflection was to be added to the original code, so that it works after the update.

And let’s see now, how it is possible to add a ListViewWebPart to a page without the commands toolbar. Actually there are two slightly different ways to add a ListViewWebPart to a page with code, let’s see the first one:

        static void AddListView(SPFile file, SPList list, string zoneID)

        {

            SPLimitedWebPartManager mngr = file.GetLimitedWebPartManager(System.Web.UI.WebControls.WebParts.PersonalizationScope.Shared);

            ListViewWebPart lvWebPart = new ListViewWebPart();

 

            lvWebPart.ListName = list.ID.ToString("B").ToUpper();

            // this is either the default view, or some of the other views that you get from the SPList.Views collection by name

            lvWebPart.ViewGuid = list.DefaultView.ID.ToString("B").ToUpper();

 

            mngr.AddWebPart(lvWebPart, zoneID, 1);

        }

Yes, this shouldn’t be something new for you, because you probably use something similar to this code if you have the problem with the commands toolbar.

And here is the second approach:

        static void AddListView2(SPFile file, SPList list, string zoneID)

        {

            SPLimitedWebPartManager mngr = file.GetLimitedWebPartManager(System.Web.UI.WebControls.WebParts.PersonalizationScope.Shared);

            ListViewWebPart lvWebPart = new ListViewWebPart();

 

            lvWebPart.ListName = list.ID.ToString("B").ToUpper();

            string schemaXml = list.GetUncustomizedViewByBaseViewId(0).HtmlSchemaXml;

            // you can modify the schema XML of the view before assigning it to the ListViewWebPart

            // e.g. you can change the ViewFields, the Query element, etc

            lvWebPart.ListViewXml = schemaXml;

 

            mngr.AddWebPart(lvWebPart, zoneID, 1);

        }

As you see, it is very similar to the first approach and the only difference is that instead of setting the ListViewWebPart.ViewGuid property you set the ListViewWebPart.ListViewXml property. And the outcome of this code snippet is that the list view web part gets added to the target page without the annoying toolbar.

So far so good, but let’s have a closer look at the two code snippets, so that you can understand what happens behind the curtains. In the first code example, the ListViewWebPart.ViewGuid property gets set – but this doesn’t imply that the LV web part gets bound somehow to the SPView instance whose ID was provided to the web part’s property. In reality what happens is that a new SPView gets created when the web part is added to the page – this SPView is hidden but you can still find it in the SPList.Views collection of the parent list. What’s more – this SPView is always associated with this LV web part and actually they are more or less two representations of one and the same internal entity (check this older posting of mine for more details). Another detail here is that when this hidden SPView is created its schema XML (containing the settings for the view fields, query, toolbar etc) is copied from the source SPView, whose ID you provided to the ViewGuid property of the web part. And this is why you end up with the commands toolbar in your LV web part – normally the SPList.Views collection contains the views from the standard list view pages of the SharePoint list (like the “All Items” one, etc), all of which do have a commands toolbar.

And now, let’s see what happens in the second code sample – you see that instead of providing a SPView’s ID to the web part, a full view schema XML is retrieved and then provided to the web part’s ListViewXml property – as you see in this case you don’t need to set the ViewGuid property at all. Let’s first see how the view schema XML gets retrieved – you can see the SPList.GetUncustomizedViewByBaseViewId method – this method returns a SPView object but this SPView instance is not a normal view that you can get from the SPList.Views collection. This is sorts of artificial SPView instance whose view schema XML is populated directly from the list’s “schema.xml” file. In the schema.xml of the list’s template definition you have a “Views” element which contains several “View” elements each of which defines the available view schema XML-s for that list. Each “View” element has a unique identifier – that’s its “BaseViewID” attribute – and the integer parameter that you specify in the GetUncustomizedViewByBaseViewId method references the “BaseViewID” attribute. There is one other detail here – some of the “View” elements in the “schema.xml” file of the list definition do provision list views for the list instances created from this list definition (for example the “AllItems.aspx”), but there are also “View” elements that actually don’t provision list view pages:

<View BaseViewID="1" Type="HTML" WebPartZoneID="Main" DisplayName="$Resources:core,objectiv_schema_mwsidcamlidC24;" DefaultView="TRUE" SetupPath="pages\viewpage.aspx" ImageUrl="/_layouts/images/generic.png" Url="AllItems.aspx">

This is the “View” element with BaseViewID=1 of the schema.xml of the standard CustomList – as you see from its attributes it provisions the “AllItems.aspx” view page for the lists based on that list definition.

<View BaseViewID="0" Type="HTML">

And this is the “View” element (it has many child elements but I omitted them because the XML is huge) with BaseViewID=0. As you can see, it contains much less attributes and doesn’t provision any of the list’s view pages. But this doesn’t mean that the view schema XML that it defines is not used, on the contrary – for example, when you use the standard SharePoint UI to add LV web parts to a page, it is the view schema with BaseViewID=0 that’s get copied to the hidden SPView of the web part. And also in the code sample above I specified the view schema with BaseViewID=0 in the GetUncustomizedViewByBaseViewId method for the same reason. If you have a closer look at the “View” elements in the schema.xml you will see the difference in their “Toolbar” elements definition and will see why the “View” with BaseViewID=0 doesn’t add a commands toolbar and the “View” elements with different BaseViewID values normally do.

[update (Aug 25, 2011): the difference between setting the LVP’s ViewGuid and ListViewXml properties is that in the first case, the whole schema of the provided SPView will be copied and used in the newly created LVP, which means that in most cases you will end up with the standard toolbar, because it is already present in most of the non-hidden views (normal view pages) of the list. In the second case although you provide a full view schema, the SharePoint object model will take into account only its BaseViewID property and will otherwise use the original view definition with the same BaseViewID from the list’s schema.xml file. The view definition with BaseViewID=0 for most of the standard SharePoint list templates just happens to come without a toolbar, so this will help you solve the issue with the LVP’s toolbar. Apart from the toolbar, if you want to use a modified view definition when creating a new LVP – this won’t be possible at the moment of the provisioning itself (the only options that you have is to choose between the existing view definitions or the already existing views, as I explained above). But once you have the LVP created it is possible to modify the hidden SPView instance associated with the LVP (check the line of code at the bottom of the posting as to how it is possible to retrieve this hidden SPView from the web part). You can modify the hidden SPView using its properties which expose most of the view’s schema. If you are interested in changing the whole schema you can see this sample code, which unfortunately resorts to reflection, contrary to the posting’s title]

One other advantage of the second approach for adding a LV web part is that after you retrieve the view schema XML you can modify it before assigning it to the ListViewWebPart.ListViewXml property. For example you can change the ViewFields XML node or the Query node and even the Toolbar XML element if you want to display some custom toolbar of yours.

One last thing that I want to mention – it is about the case when you already have you LV web part added to the page and want to change its toolbar (the solution above was intended for the scenario when you add a new LV web part, which is actually the more common case). In this case I would simply delete the existing web part and add a brand new LV web part to the page. The trick here will be that instead of getting the view schema XML with the SPList.GetUncustomizedViewByBaseViewId method, I will use the schema (SPView.HtmlSchemaXml) from the existing web part (actually from its hidden SPView), before I delete it (then the view schema should be modified accordingly). Here is a small code snippet of how your can get the LV web part’s hidden SPView:

SPView hiddenView = list.Views[new Guid(lvWebPart.ViewGuid)];