Apr 6

In this post, I'll describe how we can create a custom style for a content query web part.

A reason why we may need to do something like this is because sometimes the OOTB content query web part does not display exactly what we need when querying articles for example. I wanted to the web part to display the date, title, and some of the body text (and a read more link). Unfortunately, this is something that needs to be customized since it's not available OOTB.

Firstly, you need to open ItemStyle.xsl in SharePoint Designer. Check the file out, so you can roll back if you make any mistakes in it.

Scroll to the bottom of the file and add the following just before the </xsl:stylesheet> tag:

     <xsl:template name="WeiStyle" match="Row[@Style='WeiStyle']" mode="itemstyle">
        <xsl:variable name="SafeLinkUrl">
            <xsl:call-template name="OuterTemplate.GetSafeLink">
                <xsl:with-param name="UrlColumnName" select="'LinkUrl'"/>
            </xsl:call-template>
        </xsl:variable>
        <xsl:variable name="SafeImageUrl">
            <xsl:call-template name="OuterTemplate.GetSafeStaticUrl">
                <xsl:with-param name="UrlColumnName" select="'ImageUrl'"/>
            </xsl:call-template>
        </xsl:variable>
        <xsl:variable name="DisplayTitle">
            <xsl:call-template name="OuterTemplate.GetTitle">
                <xsl:with-param name="Title" select="@Title"/>
                <xsl:with-param name="UrlColumnName" select="'LinkUrl'"/>
            </xsl:call-template>
        </xsl:variable>
        <xsl:variable name="LinkTarget">
            <xsl:if test="@OpenInNewWindow = 'True'" >_blank</xsl:if>
        </xsl:variable>
        <xsl:variable name="Created">
            <xsl:value-of select="ddwrt:FormatDateTime(string(@Created) ,1033 ,'dd-MM-yyyy')" />
        </xsl:variable>
        <div id="linkitem" class="item">
            <xsl:if test="string-length($SafeImageUrl) != 0">
                <div class="image-area-left">
                    <a href="{$SafeLinkUrl}" target="{$LinkTarget}">
                        <img class="image" src="{$SafeImageUrl}" alt="{@ImageUrlAltText}" />
                    </a>
                </div>
            </xsl:if>
            <div class="link-item">
                <xsl:call-template name="OuterTemplate.CallPresenceStatusIconTemplate"/>    
                <a href="{$SafeLinkUrl}" target="{$LinkTarget}" title="{@LinkToolTip}">
                  <xsl:value-of select="$Created"/> - <xsl:value-of select="$DisplayTitle"/>
                </a>
                <div class="description">
                  <xsl:value-of select="substring(@PublishingPageContent, 0, 200)" disable-output-escaping="yes"/>
                  <a href="{$SafeLinkUrl}" target="{$LinkTarget}" title="Read more">...</a>
                </div>
            </div>
        </div>
    </xsl:template>

Note that in the code above that you pasted, there is a xsl variable that you set that formats the date in any way you like. To make the date formatting work, you need to add the following at the top of the xsl (namespace attribute):

  xmlns:ddwrt="http://schemas.microsoft.com/WebParts/v2/DataView/runtime"

Now save the document and refresh the page where the CQWP resides. You'll see that everything works except there is no body (yet). To make the summarised body appear, you need to export the CQWP and edit the .webpart file it in Notepad. This needs to be done because you need to add the scope to the web part.

Look for the property name CommonViewFields and change it as follows:

<property name="CommonViewFields" type="string">ExternalUrl,URL;PublishingPageImage,Image;PublishingPageContent,Note;</property>

Save the .webpart file and then go back to where the CQWP resides, delete the web part that's there and re-add a new web part. Click Advanced Web Part gallery and options, then click the "Browse" drop down and select Import. Look for the .webpart file and import that web part. You should now be able to see the web part with the date, title, content (to 200 chars) and then a ... to read more.

But that's not all. While I was doing this, I found that the first 200 chars that the web part displays actually includes any HTML code that's in there. And it just so happened (while I was testing), that if the 200th character happens to be within the HTML tag itself, the "..." read more link wouldn't work. In addition, there is a big ugly gap between the title and the summary body (because of the <p> tag in the body). So I had to figure out how to strip all HTML tags from the summary.

Go back to the ItemStyle.xsl file and add the following at the end just before the </xsl:stylesheet> tag:

<xsl:template name="RemoveHtmlTags">
    <xsl:param name="html"/>
    <xsl:choose>
        <xsl:when test="contains($html, '&lt;')">
            <xsl:value-of select="substring-before($html, '&lt;')"/>
            <!-- Recurse through HTML -->
            <xsl:call-template name="removeHtmlTags">
                <xsl:with-param name="html" select="substring-after($html, '&gt;')"/>
            </xsl:call-template>
        </xsl:when>
        <xsl:otherwise>
            <xsl:value-of select="$html"/>
        </xsl:otherwise>
    </xsl:choose>
</xsl:template>

This is the template that will remove all html code (when called).

Now, let's re-visit the original code you entered into the ItemStyle above. We need to add a new variable that calls this RemoveHtmlTags template in order to remove the HTML tags in the summary.

    <xsl:template name="DetailedStyle" match="Row[@Style='DetailedStyle']" mode="itemstyle">
        <xsl:variable name="SafeLinkUrl">
            <xsl:call-template name="OuterTemplate.GetSafeLink">
                <xsl:with-param name="UrlColumnName" select="'LinkUrl'"/>
            </xsl:call-template>
        </xsl:variable>
        <xsl:variable name="SafeImageUrl">
            <xsl:call-template name="OuterTemplate.GetSafeStaticUrl">
                <xsl:with-param name="UrlColumnName" select="'ImageUrl'"/>
            </xsl:call-template>
        </xsl:variable>
        <xsl:variable name="DisplayTitle">
            <xsl:call-template name="OuterTemplate.GetTitle">
                <xsl:with-param name="Title" select="@Title"/>
                <xsl:with-param name="UrlColumnName" select="'LinkUrl'"/>
            </xsl:call-template>
        </xsl:variable>
        <xsl:variable name="LinkTarget">
            <xsl:if test="@OpenInNewWindow = 'True'" >_blank</xsl:if>
        </xsl:variable>
        <xsl:variable name="Created">
            <xsl:value-of select="ddwrt:FormatDateTime(string(@Created) ,1033 ,'dd-MM-yyyy')" />
        </xsl:variable>
        <xsl:variable name="PureTextBody">
            <xsl:call-template name="RemoveHtmlTags">
                <xsl:with-param name="html" select="@PublishingPageContent" />
            </xsl:call-template>
        </xsl:variable>

        <div id="linkitem" class="item">
            <xsl:if test="string-length($SafeImageUrl) != 0">
                <div class="image-area-left">
                    <a href="{$SafeLinkUrl}" target="{$LinkTarget}">
                        <img class="image" src="{$SafeImageUrl}" alt="{@ImageUrlAltText}" />
                    </a>
                </div>
            </xsl:if>
            <div class="link-item">
                <xsl:call-template name="OuterTemplate.CallPresenceStatusIconTemplate"/>    
                <a href="{$SafeLinkUrl}" target="{$LinkTarget}" title="{@LinkToolTip}">
                  <xsl:value-of select="$Created"/> - <xsl:value-of select="$DisplayTitle"/>
                </a>
                <div class="description">
                  <xsl:value-of select="substring($PureTextBody, 0, 200)" disable-output-escaping="yes"/>
                  <a href="{$SafeLinkUrl}" target="{$LinkTarget}" title="Read more">...</a>
                </div>
            </div>
        </div>
    </xsl:template>

I have highlighted the difference in <bold> so you can see what was done to strip out the HTML.

Mar 24

This one took me a while to figure out so I'd better describe what I did so I don't forget it in a hurry.

You might think that the easiest way to do this is to modify the Person.aspx file, but that is not a good idea as it will void warranty and is not best practice. So I went about trying to figure out how this can be done without coding anything. After many hours, I realised that it would not be possible.

We need to write Features (available in MOSS 2007/WSS 3 and possibly in later versions). Before you go, oh no... it's going to be tough... well, that's what I thought too, until I started working on it. I won't lie, there is a quite a bit to do, so just bear with me as I try to explain it so that when I look back in a year or so, I'll still know what to do.

Step 1: Create your custom master page

Copy the default.master, which can be found in the TEMPLATEGLOBAL folder as seen in the screenshot below.

Masterpage Location

Create a folder somewhere and call it CustomMySite. Within this folder, create one called CustomMySiteStaplee.

Place the copy of the default.master into the CustomMySiteStaplee folder. Customize this master page in any way you like and save it as wei.master (or whatever name you choose). In my case, there was no need to make any changes to the master page since everything is going to be exactly where it is. You would only customize the master page if you want to add or remove some functionality. If you want to change the look and feel, you just need to edit the styles (which I will go through a little later).

Side note: Even if you are not going to edit your master page as in my case, you should make a copy of it anyway in case you decide to in the future.

Now, we're off to code...

Using Visual Studio 2005 or 2008

Start a new project, class library. Add a reference to Windows SharePoint Services (Microsoft.SharePoint.dll) and then just copy and paste the following code:

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Administration;
using System.Diagnostics;

namespace CustomMySite
{
    class CustomMySite : SPFeatureReceiver
    {
        public override void FeatureInstalled(SPFeatureReceiverProperties properties)
        {
            //throw new Exception("The method or operation is not implemented.");
        }

        public override void FeatureUninstalling(SPFeatureReceiverProperties properties)
        {
            //throw new Exception("The method or operation is not implemented.");
        }

        public override void FeatureActivated(SPFeatureReceiverProperties properties)
        {
            SPSecurity.RunWithElevatedPrivileges(delegate()
{
    EventLog eventlog = new EventLog();
    eventlog.Source = "MySiteFeatureStaple";
    eventlog.WriteEntry("Starting Activation of the MySite Master Page Switcher Feature");
    try
    {
        SPSite mysite = properties.Feature.Parent as SPSite;
        SPWeb myweb = mysite.RootWeb;
        if (myweb.WebTemplate == "SPSPERS")
        {
            using (myweb)
            {
                myweb.Description = "MySite :: " + DateTime.Now.ToShortDateString();
                if (myweb.MasterUrl.Contains("default.master"))
                {
                    myweb.MasterUrl = myweb.MasterUrl.Replace("default.master", "wei.master");
                    myweb.ApplyTheme("WeiCustomTheme");
                }
                myweb.Update();
                eventlog.WriteEntry("Found site using 'SPSPERS' template & updated it with the custom master page");
            }
        }
        if (myweb.WebTemplate == "SPSMSITEHOST")
        {
            using (myweb)
            {
                myweb.Description = "MySite :: " + DateTime.Now.ToShortDateString();
                if (myweb.MasterUrl.Contains("default.master"))
                {
                    myweb.MasterUrl = myweb.MasterUrl.Replace("default.master", "wei.master");
                    myweb.ApplyTheme("WeiCustomTheme");
                }

                myweb.Update();
                eventlog.WriteEntry("Found site using 'SPSMSITEHOST' template & updated it with the custom master page");
            }
        }
    }
    catch (Exception e)
    {
        eventlog.WriteEntry(String.Format("Error activating MySite master page switcher feature {0} : ", e.Message));
    }
});
        }

        public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
        {
            SPSecurity.RunWithElevatedPrivileges(delegate()
{
    EventLog eventlog = new EventLog();
    eventlog.Source = "MySiteFeatureStaple";
    eventlog.WriteEntry("Starting De-activation of the MySite Master Page Switcher Feature");
    try
    {
        SPSite mysite = properties.Feature.Parent as SPSite;
        SPWeb myweb = mysite.RootWeb;
        if (myweb.WebTemplate == "SPSPERS")
        {
            using (myweb)
            {
                if (myweb.MasterUrl.Contains("wei.master"))
                {
                    myweb.MasterUrl = myweb.MasterUrl.Replace("wei.master", "default.master");
                    myweb.ApplyTheme("none");
                }
                myweb.Update();
                eventlog.WriteEntry("Removed custom master page from site using 'SPSPERS' template");
            }
        }

        if (myweb.WebTemplate == "SPSMSITEHOST")
        {
            using (myweb)
            {
                if (myweb.MasterUrl.Contains("wei.master"))
                {
                    myweb.MasterUrl = myweb.MasterUrl.Replace("wei.master", "default.master");
                    myweb.ApplyTheme("none");
                }
                myweb.Update();
                eventlog.WriteEntry("Removed custom master page from site using 'SPSMSITEHOST' template");
            }
        }
    }
    catch (Exception e)
    {
        eventlog.WriteEntry(String.Format("Error de-activating MySite master page switcher feature {0} : ", e.Message));
    }
});
        }
    }
}

Note where I set the theme in the code above myweb.ApplyTheme("WeiCustomTheme"); - this means that I chose to use my own custom theme for all the MySites that are created. This is important for styling purposes. You need to create a custom theme to reflect your own mysite if you want the right branding (logos, colours etc to appear on your MySite). Instructions to create your own custom theme can be found here. As a result, you will need to change that ApplyTheme value to your own custom theme.

Build the project (Ctrl+Shift+B) to get the DLL (don't forget to sign the assembly as I sometimes do). Drag and drop the DLL into the GAC (I find this is the fastest way to install DLLs into the GAC). FYI: GAC is located at c:windowsassembly.

Now the fun bit... creating the feature.

Creating the staplee feature.xml and the element.xml files

Open Notepad and copy the following code into it:

<?xml version="1.0" encoding="utf-8"?>
<Feature xmlns="http://schemas.microsoft.com/sharepoint/"
         Id="5E1DFC64-ECB7-4920-B55C-F8CE92FBD77D"
         Title="Custom MySite Feature Staplee"
         ReceiverAssembly="CustomMySite, Version=1.0.0.0, Culture=neutral, PublicKeyToken=48aa5af62c615cd0"
         ReceiverClass="CustomMySite.CustomMySite"
         Scope="Site"
         Version="1.0.0.0"
         DefaultResourceFile="core">
  <ElementManifests>
    <ElementManifest Location="CustomMySiteElement.xml" />
    <ElementFile Location="wei.master" />
  </ElementManifests>
</Feature>

Make sure you change the value for PublicKeyToken to your own PublicKeyToken value. You can get this value by navigating to the GAC folder (C:windowsassembly) and looking for the DLL that you dragged in there. The public key token appears next to the DLL Name. If you like, you can change the Guid, but it shouldn't cause a problem if you use the same one I did as this Guid identifies this feature and it is immensely unlikely that you are currently using this Guid to identify a different feature.

Save this file as feature.xml into the previously created CustomMySiteStaplee folder along with the wei.master file.

Open Notepad again and copy the following code into it:

<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <Module Name="CustomMySitePage" Path="" RootWebOnly="true" Url="_catalogs/masterpage">
    <File Url="wei.master" Type="GhostableInLibrary" />
  </Module>
</Elements>

Save this file as CustomMySiteElement into the CustomMySiteStaplee folder with the wei.master and element.xml files.

Creating the stapler feature.xml and the element.xml files

This bit needs to be done so that the public mysite pages also has the right branding applied to it (which is the main purpose of branding mysites in the first place). Create a new folder in the CustomMySite folder alongside the previously created CustomMySiteStaplee folder. Name this folder CustomMySiteStapler.

Open Notepad again and copy the following code into it:

<Elements xmlns="http://schemas.microsoft.com/sharepoint/" >
 <FeatureSiteTemplateAssociation Id="5E1DFC64-ECB7-4920-B55C-F8CE92FBD77D" TemplateName="SPSPERS#0"/>
 <FeatureSiteTemplateAssociation Id="5E1DFC64-ECB7-4920-B55C-F8CE92FBD77D" TemplateName="SPSMSITEHOST#0"/>
</Elements>

Save this file as elements.xml into the newly created CustomMySiteStapler folder. Note the Guids... look familiar? These Guids need to be identical to the ones in the Staplee feature.xml so the feature knows what to staple and where (SPSPERS and SPSMSITEHOST).

Open Notepad yet again (this is the last time) and copy the following code into it:

<Feature  
 Id="4457E66E-6FCD-4352-AD4D-B870600B4696"
  Title="My Site Creation Feature Stapler"
  Scope="Farm"
  xmlns="http://schemas.microsoft.com/sharepoint/" >
 <ElementManifests>
  <ElementManifest Location="elements.xml" />
 </ElementManifests>
</Feature>

Save this file as feature.xml into the newly created CustomMySiteStapler folder.

Now, you're done... you have all the files you need to make MySite branding work.

Deploying MySite branding

Before we deploy, let's check that we have the right files and folders in the CustomMySite folder.

We have:

  1. CustomMySiteStaplee (which contains CustomMySiteElement.xml, feature.xml and wei.master)
  2. CustomMySiteStapler (which contains elements.xml and feature.xml)
  3. WeiCustomTheme (which contains the custom branding)

Copy the CustomMySiteStaplee folder to:
C:Program FilesCommon FilesMicrosoft Sharedweb server extensions12TEMPLATEFEATURESCustomMySiteStaplee

Copy the CustomMySiteStapler folder to:
C:Program FilesCommon FilesMicrosoft Sharedweb server extensions12TEMPLATEFEATURESCustomMySiteStapler

Copy the WeiCustomTheme folder to:
C:Program FilesCommon FilesMicrosoft Sharedweb server extensions12TEMPLATETHEMESWeiCustomTheme

If you haven't done so already, you need to add the theme template definition to SPTHEMES.XML, which is the file that determines which themes are available as options on the Site Theme page. This XML file is located in the C:Program FilesCommon FilesMicrosoft SharedWeb Server Extensions12TEMPLATELAYOUTS1033 directory
 <Templates>
  <TemplateID>WeiCustomTheme</TemplateID>
  <DisplayName>WeiCustomTheme</DisplayName>
  <Description>Wei's Custom MySite Theme.</Description>
  <Thumbnail>images/WeisCustomThemeThumb.gif</Thumbnail>
  <Preview>images/WeisCustomThemePreview.gif</Preview>
 </Templates>

The Thumbnail and Preview images are just image files that are used as previews when browsing available themes.

Run the following commands:

stsadm –o installfeature –name CustomMysiteStaplee
stsadm –o activatefeature –name CustomMysiteStaplee -url http://your_intranet/mysite
stsadm -o installfeature -name CustomMysiteStapler
iisreset

From here, every new MySite created will by default contain the custom branding you have created.

Updating existing MySite sites with the new branding

I think this only needs to be done once. Go to a previously created public MySite page as Administrator. Click Site Actions, then Site Settings. Click on Site theme and choose WeiCustomTheme from the list. Once you select this, all the other existing MySites will be updated to this custom theme.

If you did make changes to the master page, then you need to go into SharePoint Designer, open the MySite sites, navigate to _catalogs/masterpage, right click on the wei.master and select Set as Default Master Page.

Updating to new Custom Styles after deployment

You will notice that if you try to change the style after deploying the MySite branding, nothing appears to happen. What you need to do is firstly, uninstall the feature, change the styles and then reinstall the feature.

stsadm -o uninstallfeature -name CustomMysiteStapler
stsadm –o deactivatefeature –name CustomMysiteStaplee -url http://your_intranet/mysite
stsadm –o uninstallfeature –name CustomMysiteStaplee
iisreset

Make changes to the stylesheet here... then run:

stsadm –o installfeature –name CustomMysiteStaplee
stsadm –o activatefeature –name CustomMysiteStaplee -url http://your_intranet/mysite
stsadm -o installfeature -name CustomMysiteStapler
iisreset

I'm not 100% sure if we need to do the iisreset, so I'll let you experiment and see.

Once you have updated the styles and navigate back to the MySite, it will look as if the styles were not updated. I had to change the theme to something else, then change it back for it to pick up the changes. Oh well... as long as it works :)