I’m learning all about SharePoint branding as part of my new job at Synteractive, so this post started out as a reference file and notes on the steps I was taking. It ended up being an insanely long blog draft and I decided to throw it out there to see if more experienced SharePoint superstars can give me some tips on how I could have done this more easily or maybe to help other newbie SharePoint developers.
Some facts and disclaimers:
Here is the challenge that I had:
First, here’s a screenshot of my custom query web part:
If you view the HTML source, there is a TON of HTML code that goes into the above; there are several nested tables and stuff for allowing you to click on the header to edit it. Here is just a small sampling…
<table class="s4-wpTopTable" border="0" cellpadding="0" cellspacing="0" width="100%"><tr><td><table border="0" cellpadding="0" cellspacing="0" width="100%"><tr class="ms-WPHeader"><td align="left" class="ms-wpTdSpace"> </td><td title="Recent News - Displays a dynamic view of content from your site." id="WebPartTitleWPQ2" class="ms-WPHeaderTd"><h3 style="text-align:justify;" class="ms-standardheader ms-WPTitle"><nobr><span>Recent News</span><span id="WebPartCaptionWPQ2"></span></nobr></h3></td>
The actual data part – the list of news items – has HTML code that looks like this:
<ul class="dfwp-column dfwp-list" style="width:100%" ><li class="dfwp-item"><div class="item"><div class="link-item"><a href="#" title="">New SPDevWiki site released!</a><div class="description"></div></div></div></li><li class="dfwp-item"><div class="item"><div class="link-item"><a href="#" title="">SharePoint 2010 SDK released</a><div class="description">SharePoint 2010 SDK released for RTM</div></div></div></li><li class="dfwp-item"><div class="item"><div class="link-item"><a href="#" title="">Announcing Community Kit for SharePoint</a><div class="description">CodePlex projects teams get together to release CKS:Dev!</div></div></div></li></ul>
There are plenty of classes to work with… but if I were to modify the .dfwp-xxx classes, those would affect all of the web parts using that type of display, and in my specific situation, I couldn’t throw the web part into my own custom <div> with an ID or class that would allow me to target only that web part.
But I’m getting ahead of myself. Let me show you what I was shooting for. Here’s my HTML that I would ideally want:
<div class="blogfeed-wrapper"><div class="blogfeed"><h2>Recent News</h2><ul><li><p class="date">Jun 5, 2010</p><p class="image"><img src="images/pic.png" alt="image" width="50" height="50" /></p><h3><a href="#">Article title</a></h3><p class="blurb">Article blurb</p></li><!-- repeat LI for each data item --></ul></div></div>
To accomplish this, I would have to:
I looked around for information about customizing the Content Query Web Part and found this article, “Display Content Query Web Part Results in a Grid/Table” by Paul Galvin which seemed like it might do the trick. He references Heather Solomon’s blog post, “Customizing the Content Query Web Part and Custom Item Styles.” Here are the step-by-steps of what I did:
<xsl:template name="Default" match="*" mode="itemstyle">....</xsl:template><!-- custom item style template for listing out fields --><xsl:template name="TestList" match="Row[@Style='TestList']" mode="itemstyle"><xsl:for-each select="@*"><xsl:value-of select="name()" /><br /></xsl:for-each></xsl:template>
<property name="CommonViewFields" type="string" />
<property name="CommonViewFields" type="string">ArticleStartDate, DateTime</property>
Note: From this MSDN article, “How to Display Custom Fields in a Content by Query Web Part,” there’s an additional step (Step 4) to map the new column to existing/available fields in the web part. This only works if your custom field can replace the Title, Description, LinkURL, or ImageURL web part fields – which could save you a few steps if, say, you wanted to show a different text column in place of the “description” field. In this case, though, since I want to display the article date, I’m going to have to do some actual customization to the XSL files, described below.
<xsl:template name="Default" match="*" mode="itemstyle">....</xsl:template>
<xsl:template name="News" match="Row[@Style='News']" mode="itemstyle">
In other words, replace name=”YourCustomStyleName” and match=”Row[@Style='YourCustomStyleName']” in your <xsl:template> tag.
<li><p class="date">Jun 5, 2010</p><p class="image"><img src="images/pic.png" alt="image" width="50" height="50" /></p><h3><a href="#">Article title</a></h3><p class="blurb">Article text</p></li>
<!-- custom item style template for recent news --><xsl:template name="News" match="Row[@Style='News']" 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><li><!-- Here is where I add in my ArticleStartDate field. It would typically look like<xsl:value-of select="@ArticleStartDate" />but the extra stuff is to format the date. --><p class="date"><xsl:value-of select="ddwrt:FormatDate(@ArticleStartDate,1033,1)" /></p><xsl:if test="string-length($SafeImageUrl) != 0"> <p class="image"> <a href="{$SafeLinkUrl}"> <xsl:if test="$ItemsHaveStreams = 'True'"> <xsl:attribute name="onclick"> <xsl:value-of select="@OnClickForWebRendering"/> </xsl:attribute> </xsl:if> <xsl:if test="$ItemsHaveStreams != 'True' and @OpenInNewWindow = 'True'"> <xsl:attribute name="onclick"> <xsl:value-of disable-output-escaping="yes" select="$OnClickTargetAttribute"/> </xsl:attribute> </xsl:if> <img class="image" src="{$SafeImageUrl}" title="{@ImageUrlAltText}"> <xsl:if test="$ImageWidth != ''"> <xsl:attribute name="width"> <xsl:value-of select="$ImageWidth" /> </xsl:attribute> </xsl:if> <xsl:if test="$ImageHeight != ''"> <xsl:attribute name="height"> <xsl:value-of select="$ImageHeight" /> </xsl:attribute> </xsl:if> </img> </a> </p> </xsl:if><h3><a href="{$SafeLinkUrl}" title="{@LinkToolTip}"><xsl:value-of select="$DisplayTitle"/></a></h3><p class="blurb"><xsl:value-of select="@Description" /></p><!--<xsl:call-template name="OuterTemplate.CallPresenceStatusIconTemplate"/> <a href="{$SafeLinkUrl}" title="{@LinkToolTip}"> <xsl:if test="$ItemsHaveStreams = 'True'"> <xsl:attribute name="onclick"> <xsl:value-of select="@OnClickForWebRendering"/> </xsl:attribute> </xsl:if> <xsl:if test="$ItemsHaveStreams != 'True' and @OpenInNewWindow = 'True'"> <xsl:attribute name="onclick"> <xsl:value-of disable-output-escaping="yes" select="$OnClickTargetAttribute"/> </xsl:attribute> </xsl:if> </a> --></li></xsl:template>
<div class="blogfeed-wrapper"><div class="blogfeed"><h2>Recent News</h2><ul>...</ul></div></div>
At first glance, Paul’s article seems perfect for what I need to do. His method is to edit ContentQueryMain.xsl to pass in parameters that identify the “current position” and “last row” so that ItemStyle.xsl can put in special code at the start of the list (when current position = 0) and the end of the list (current position = last row). Unfortunately, after following his detailed and clear instructions, I found that it didn’t work for me. I’m not sure if it’s because of a difference in SharePoint 2007 and 2010, but basically what was going on was that the .dfwp-xxxx UL and LIs were still being wrapped around my code. So while Paul’s technique “worked” in the sense that the wrapper code showed up before the first item and after the last item, the other stuff was getting in the way and creating havoc with the code:
<ul class="dfwp-column dfwp-list" style="width:100%" ><li class="dfwp-item"><div class="blogfeed-wrapper"><div class="blogfeed"><h2>Recent News</h2><ul><li>...</li></li><li class="dfwp-item"><li>...</li></li><li class="dfwp-item"><li>...</li></ul></div></div></li></ul>
Diving deep into the ContentQueryMain.xsl file, I was able to locate various variable definitions that hold the HTML code for the UL and LI tags. There were some variables near the top of the file and more inside the OuterTemplate.Body template.
<xsl:variable name="BeginList" select="string('<ul class="dfwp-list">')" /><xsl:variable name="EndList" select="string('</ul>')" /><xsl:variable name="BeginListItem" select="string('<li class="dfwp-item">')" /><xsl:variable name="EndListItem" select="string('</li>')" /><xsl:template name="OuterTemplate.Body">...<xsl:variable name="BeginColumn1" select="string('<ul class="dfwp-column dfwp-list" style="width:')" /><xsl:variable name="BeginColumn2" select="string('%" >')" /><xsl:variable name="BeginColumn" select="concat($BeginColumn1, $cbq_columnwidth, $BeginColumn2)" /><xsl:variable name="EndColumn" select="string('</ul>')" />
If you do a quick search in the code for $BeginList, $EndList, etc., you’ll see that those variables are printed out several times in different places. Not being at all an expert, though, it was pretty hard for me to figure out what was going on, so I resorted to an elegant solution to find the places to target. And by “elegant,” I mean “amateurish and hackish.” If you aren’t too embarrassed to follow my example, here’s what you do…
TEST1<xsl:value of disable-output-escaping="yes" select="$BeginColumn" />
You’ll want to repeat that for each time you see an <xsl:value /> tag for $BeginList, $EndList, $BeginListItem, $EndListItem, $BeginColumn, and $EndColumn.
<!-- my custom variables for the news section --><xsl:variable name="newsStart"><![CDATA[<div class="blogfeed-wrapper"><div class="blogfeed"><h2>Recent News</h2><ul>]]></xsl:variable><xsl:variable name="newsEnd"><![CDATA[</ul></div></div>]]></xsl:variable>
<xsl:choose> <xsl:when test="$cbq_isgrouping != 'True'"> <xsl:if test="$CurPosition = $FirstRow">TEST1<xsl:value-of disable-output-escaping="yes" select="$BeginColumn" /> </xsl:if> </xsl:when>
And here’s my edited code:
<xsl:choose> <xsl:when test="$cbq_isgrouping != 'True'"> <xsl:if test="$CurPosition = $FirstRow"> <!-- customized stuff --><xsl:choose> <xsl:when test="@Style='News'"> <xsl:value-of disable-output-escaping="yes" select="$newsStart" /></xsl:when><xsl:otherwise><!-- this was the original code --><xsl:value-of disable-output-escaping="yes" select="$BeginColumn" /></xsl:otherwise> </xsl:choose> <!-- end customized stuff --> </xsl:if> </xsl:when>
<!-- customized stuff --><xsl:choose><xsl:when test="@Style='News'"><xsl:value-of disable-output-escaping="yes" select="$newsEnd" /></xsl:when><xsl:otherwise><!-- this was the original code --><xsl:value-of disable-output-escaping="yes" select="$EndColumn" /></xsl:otherwise></xsl:choose><!-- end customized stuff -->...<xsl:template name="OuterTemplate.CallItemTemplate"> <xsl:param name="CurPosition" /> <xsl:param name="LastRow" /> <!-- customized stuff --><xsl:choose> <xsl:when test="@Style='News'" /><xsl:otherwise><!-- this was the original code --><xsl:value-of disable-output-escaping="yes" select="$BeginListItem" /></xsl:otherwise> </xsl:choose> <!-- end customized stuff -->...<!-- customized stuff --><xsl:choose><xsl:when test="@Style='News'" /><xsl:otherwise><!-- this was the original code --><xsl:value-of disable-output-escaping="yes" select="$EndListItem" /></xsl:otherwise></xsl:choose><!-- end customized stuff -->
<div class="blogfeed-wrapper"><div class="blogfeed"><h2>Recent News</h2><ul><li xmlns:ddwrt="http://schemas.microsoft.com/WebParts/v2/DataView/runtime"><p class="date"></p><h3><a href="..." title="">New SPDevWiki site released!</a></h3><p class="blurb"></p></li><li xmlns:ddwrt="http://schemas.microsoft.com/WebParts/v2/DataView/runtime"><p class="date">8/3/2010</p><p class="image"><a href="..."><img class="image" src=".../image.png" title="" /></a></p><h3><a href="..." title="">SharePoint 2010 SDK released</a></h3><p class="blurb">SharePoint 2010 SDK released for RTM</p></li><li xmlns:ddwrt="http://schemas.microsoft.com/WebParts/v2/DataView/runtime"><p class="date">6/4/2010</p><h3><a href="..." title="">Announcing Community Kit for SharePoint</a></h3><p class="blurb">CodePlex projects teams get together to release CKS:Dev!</p></li></ul></div></div>
Whew! That was really long. Here’s a summary of the steps that I took:
I definitely appreciate any comments that more experienced SharePoint developers have about how to modify the ContentQueryMain.xsl file more elegantly or other ways to accomplish this! If you have questions, please feel free to post them but remember that I’m new to this as well and may not be able to answer them.
References and acknowledgements