Saturday, May 21, 2011

Customizing Content Query Web Parts

Content Query Web Parts

I have to say the game has changed with content query web parts. When trying to work with them in SharePoint 2007 you ended up having to do a ton of work just so a custom field from a content type. Now it really very simple to go in and start working with content query web parts all inside of SharePoint Designer. In this example I am going to:

  • Create a content query web part that is going to show me the most popular articles that are related to an article that I created.
  • I will do some “conditional formatting” to display a confidentiality warning to the user based on the metadata associated to the article.

Approach

As I alluded creating your own xsl for content query web parts is much easier with SharePoint Designer 2010. To access and modify the style sheets select All Files in the Navigation. Then drill down to Style Library >> XSL Style Sheets. There are three style sheets that come out of the box ContentQuery.xsl, Header.xsl and ItemStyle.xsl. The one you will need to work with the most of the time is ItemStyle.xsl.

clip_image002

Next question you may have is how to create new xsl entries for the content query web part? Well I have found the simplest approach is pretty simple.

  • I open up ItemStyle.xsl.
  • I copy ones of the existing xsl templates. Specifically, I copy a full <xsl:template> and add it to the very bottom.
  • Then you need to modify the template tag and replace XXX values <xsl:template name="XXX" match="Row[@Style='XXX']" mode="itemstyle">. It is important that both values are identical. As well, they need to be unique to the xsl file.

That’s it. I am not going to give a full lesson here on all the cool things you can do once you are in there. I leave that up to you as a creative solution developer.

When doing this write up I refined my thoughts a little bit on how to do content query web parts. Specifically I think it is not best to the ItemStyle.xsl directly. Why? Well when you make a change to the ItemStyle.xsl (as I outlined above) you will get a warning saying “Saving your changes will customize this page so that is no longer based on the site definition. Do you want to continue?”

clip_image003

So you make this change you are customizing (unghosting) the ItemStyle.xsl which may not always be the best thing if you are planning for an Upgrade. YES YOU SHOULD BE THINKING ABOUT THE NEXT VERSION OF SHAREPOINT! Look at the forest; not the tree in front of you. Customized pages had to be dealt with when upgrading from SharePoint 2003 to 2007 as well as SharePoint 2007 to 2010. My response officially is it always “depends” but I try to avoid customizing (unghosting) as much as I can.

Knowing this I believe a best practice will be to create your own content query web parts that point to a custom ItemStyle.xsl file. Then your customizations will be managed separately and will not interfere with upgrades to OOB features. The solution is so easy as well; it is a no brainer. Let me walk through it.

  • First I took the existing ItemStyle.xsl in SharePoint Designer and copy pasted it within the same directory. I named it ItemStyle_BrandingBlog.xsl.
  • Next I went into it and stripped out all of the existing <xsl:template> tags and all the content within them. If those need to be used, I will use the OOB content query web part. Then saved it.
  • Next I just added an out of the box content query web part onto a web part page.
  • I made zero changes to the content query web part and exported the web part configuration. I saved the file locally. I named it Content_Query_Branding_Blog.webpart.
  • Next I opened the Content_Query_Branding_Blog.webpart (which is nothing more than a XML configuration file for the content query web part).
  • I modified the ItemXslLink to point to my new ItemStyle_BrandingBlog.xsl. <property name="ItemXslLink" type="string">/Style Library/XSL Style Sheets/ItemStyle_BrandingBlog.xsl</property>
  • I modified the Title to <property name="Title" type="string">Branding Blog Content Query</property>
  • Next I went to Site Settings >> Web Parts Gallery >> and then upload the Content_Query_Branding_Blog.webpart.

Done.

Now you have created your own XSL style sheet and you have created a content query web part configuration that uses that style sheet. We will later automate this as part of a WSP deployment.

Now let’s jump into the configuration of the two web parts that I need to create for my technology article publishing page.

Related Articles Content Query Web Part

As you may recall I need to support the ability show related technology articles. So I want to query for all articles that are related to the one being viewed.

Step 1 – Create the XSL for Content Query Web Part

The first thing I need to do is create a view of data that will show the following:

  • The title of the article
  • The rating of the article
  • The last modified date
  • The first 300 characters from the article

To accomplish I wrote the following xsl in the ItemStyle_BrandingBlog.xsl that I created in the previous step. I did all of this in SharePoint Designer 2010.

<xsl:stylesheet 
version="1.0"
exclude-result-prefixes="x d xsl msxsl cmswrt"
xmlns:x="http://www.w3.org/2001/XMLSchema"
xmlns:d="http://schemas.microsoft.com/sharepoint/dsp"
xmlns:cmswrt="http://schemas.microsoft.com/WebParts/v3/Publishing/runtime"
xmlns:ddwrt="http://schemas.microsoft.com/WebParts/v2/DataView/runtime"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt">
<xsl:param name="ItemsHaveStreams">
<xsl:value-of select="'False'" />
</xsl:param>
<xsl:variable name="OnClickTargetAttribute" select="string('javascript:this.target=&quot;_blank&quot;')" />
<xsl:variable name="ImageWidth" />
<xsl:variable name="ImageHeight" />
<xsl:template name="TitleDateAndRatings" match="Row[@Style='TitleDateAndRatings']" 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="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>
<div>
<xsl:call-template name="OuterTemplate.CallPresenceStatusIconTemplate"/>
<table>
<tr>
<td width="500px" colspan="3">
<hr />
</td>
</tr>
<tr>
<td width="300px">
<a href="{$SafeLinkUrl}" title="" >
<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>
<xsl:value-of select="$DisplayTitle"/>
</a>
</td>
<td nowrap="nowrap" width="100px">
<xsl:if test="@Ratings &gt;= 4.75">
<span>
<a class="ms-currentRating"><img src="/_layouts/Images/Ratings.png" class="ms-rating_5" alt="Current average rating is 5 stars." /></a>
</span>
</xsl:if>
<xsl:if test="@Ratings &gt;= 4.25 and @Ratings &lt; 4.75">
<span>
<a class="ms-currentRating"><img src="/_layouts/Images/Ratings.png" class="ms-rating_4_5" alt="Current average rating is 4.5 stars." /></a>
</span>
</xsl:if>
<xsl:if test="@Ratings &gt;= 3.75 and @Ratings &lt; 4.25">
<span>
<a class="ms-currentRating"><img src="/_layouts/Images/Ratings.png" class="ms-rating_4" alt="Current average rating is 4 stars." /></a>
</span>
</xsl:if>
<xsl:if test="@Ratings &gt;= 3.25 and @Ratings &lt; 3.75">
<span>
<a class="ms-currentRating"><img src="/_layouts/Images/Ratings.png" class="ms-rating_3_5" alt="Current average rating is 3.5 stars." /></a>
</span>
</xsl:if>
<xsl:if test="@Ratings &gt;= 2.75 and @Ratings &lt; 3.25">
<span>
<a class="ms-currentRating"><img src="/_layouts/Images/Ratings.png" class="ms-rating_3" alt="Current average rating is 3 stars." /></a>
</span>
</xsl:if>
<xsl:if test="@Ratings &gt;= 2.25 and @Ratings &lt; 2.75">
<span>
<a class="ms-currentRating"><img src="/_layouts/Images/Ratings.png" class="ms-rating_2_5" alt="Current average rating is 2.5 stars." /></a>
</span>
</xsl:if>
<xsl:if test="@Ratings &gt;= 1.75 and @Ratings &lt; 2.25">
<span>
<a class="ms-currentRating"><img src="/_layouts/Images/Ratings.png" class="ms-rating_2" alt="Current average rating is 2 stars." /></a>
</span>
</xsl:if>
<xsl:if test="@Ratings &gt;= 1.25 and @Ratings &lt; 1.75">
<span>
<a class="ms-currentRating"><img src="/_layouts/Images/Ratings.png" class="ms-rating_1_5" alt="Current average rating is 1.5 stars." /></a>
</span>
</xsl:if>
<xsl:if test="@Ratings &lt; 1.25">
<span>
<a class="ms-currentRating"><img src="/_layouts/Images/Ratings.png" class="ms-rating_1" alt="Current average rating is 1 star." /></a>
</span>
</xsl:if>
</td>
<td nowrap="nowrap" width="100px">
<xsl:value-of select="ddwrt:FormatDateTime(string(@Date), 1033, 'M/d/yyy h:mm tt')" />
</td>
</tr>
<tr>
<td colspan="3" width="500px">
<xsl:value-of select="substring(@Description, 1, 300)" disable-output-escaping="yes" />...
</td>
</tr>
</table>
</div>
</xsl:template>
</xsl:stylesheet>


Let me discuss several of things I did to accomplishing this:




  • First I created a little HTML table that will control the format of the data. Then I started to add in fields.


  • The link title is printed here - <xsl:value-of select="$DisplayTitle"/> I simply took an example from the out of the box ItemStyle.xsl.


  • Next I needed to print the star ratings. I would have never figured this out until I found this article by accident - blog. This smart person showed how to do it and I just had to make a few minor changes for myself. Basically it looks at the rating value tied to the page rating. Then using styles it will print the appropriate star image.


  • Next I needed to print the last modified date. Here is the code that accomplished that <xsl:value-of select="ddwrt:FormatDateTime(string(@Date), 1033, 'M/d/yyy h:mm tt')" />. One thing to note is I had to add this namespace to the main xsl tag xmlns:ddwrt=http://schemas.microsoft.com/WebParts/v2/DataView/runtime. If you forget to do this, you will get errors.


  • Finally print the description I used the following line of code - <xsl:value-of select="substring(@Description, 1, 300)" disable-output-escaping="yes" />. In it I only print the first 300 characters. Plus I using the disable-output-escaping attributed to remove the embedded HTML in the article.



Step 2 – Test It



I saved my work and the next thing I did was test it to make sure the formatting looked good. To accomplish this I just went to a web part page. Here is a view of it with some test data in there. You can see I am getting the article title, start rating, a nicely formatted date and the article content. Now I just need to configure this into my Page Layout.



clip_image002[5]



Step 3 – Add and Configure the Content Query Web Part to the Page Layout



The last step is to configure this web part into the page layout. When I do this, now every page provisioned using this layout will have this web part pre-configured. What I did was:




  • Go back to my page layout.


  • Added a new row for Related Articles.


  • Open up the split view.


  • Then I will select Insert tab on the ribbon.


  • Then select the web part I deployed in the previous section.



clip_image004



Next I right click on the web part and select Web Part Properties which will open the same web part configuration panel I would use in the SharePoint UI.



clip_image006



I make the following configurations to the content query web part.



First I set up the query, pretty basics stuff. I set it to go against the pages library and only retrieve the Technology Article type.



clip_image007



Next I set up the Filters for the query. I only want to get articles that match on both technology and keywords. I also want to make sure to not get the same article.



For both the Technology and Enterprise Keywords I use [PageFieldValue:XXX] to retrieve metadata values from the article as part of the query. I will also note that if you have values in the URL querystring, you can use [PageQueryString:XXX] to access those values to use in the Filters.



To ensure I do not have the article itself appear in the query, I exclude it by the Title of the article which is a unique value for me.



clip_image008



Next I set it up to sort by star ratings.



Then in the Styles section I select TitleAndDateRatings style I created in ItemStyle_BrandingBlog.xsl.



clip_image009



The last part of the configuration is to set up the fields in the Technology Article to feed into the TitleAndDateRatings style.



clip_image010



I also set the Chrome to None to hide the web part header.



Now that is done I have the below results. I provisioned a Technology Article page, set the Technology and Keywords value. When I saved the article, the content query web part I pre-configured into the page layout will now be visible.



clip_image012



One important note it is not possible to change the configuration of the web part in the provisioned web page (like above). So if I went to edit the web page, I would not have the ability to edit the web part. If you want to support changing the web part configuration after page provisioning you will need to add a web part zone into the page layout and then put the web part into that zone. This can all be done in SharePoint Designer.



Just remember if you put a web part zone into the page layout, this will allow users to put in web parts in an ad-hoc fashion. So there is a real trade-off you have to consider. In my case, I actually used a web part zone at first, I trained users, and they still did not listen and started to add their own web parts. I ended up removing all the web part zones from the page layouts. This becomes pretty important in a real controlled publishing scenario.



Another note you should be aware of is if you make a change to the web part configuration in the page layout those configurations will not be pushed into provisioned pages either. This behavior is different that adding new fields or changing the presentation of the page layout which will change provisioned pages. This behavior makes sense and is expected; though all is not lost. If you really need to make a web part configuration change to all the provisioned pages, you can write some code to iterate through the pages and web parts to make changes. That is beyond the scope of my article but the SharePoint API supports it.



Confidentiality Web Part



The next thing I wanted to accomplish is create some sort of simple solution where I could visually indicate to the user that the data on the article is confidential. The environment I am deploying to does not have Sandbox Solutions enabled so writing a web part was not an option. To accomplish this, I was able to write a content query web part style.



Please note that this solution is not really secure. Page level security can be implemented but still users can accidently copy and paste content and email the information by accident. Web pages cannot be locked down with Information Rights management either. So a user can still copy and paste the information and email. The only real way to ensure this information is not sent out is to create a document library, configure information rights management in that library, load content into that library and then reference a link to the locked down content from the publishing page.



Step 1 – Create the XSL for Content Query Web Part



I followed the same steps and created a template in ItemStyle_BrandingBlog.xsl. The logic of this one is pretty simple. I basically have an If statement that checks to see what level of confidentiality the article has and then show visual indicator.




    <xsl:template name="Confidential" match="Row[@Style='Confidential']" mode="itemstyle">
<div>
<table width="100%" height="50px">
<tr>
<xsl:if test="@Conf = 'Confidential'">
<td style="background-color:red; color:black; text-align:center; font-size:large"><xsl:value-of select="@Conf" /> </td>
</xsl:if>
<xsl:if test="@Conf = 'NDA'">
<td style="background-color:yellow; color:black; text-align:center; font-size:large"><xsl:value-of select="@Conf" /></td>
</xsl:if>
</tr>
</table>
</div>
</xsl:template>


Step 2 – Test It



Then I test it with the web page I already configured.



Step 3 – Add and Configure the Content Query Web Part to the Page Layout



Then just like the last time, I opened the page layout in SharePoint designer and inserted the web part into the top of the page. I right clicked the web part and selected web part properties.



I set it query for the Technology Article type.



clip_image001



For the parameters I set it to only find articles based on the unique title.



clip_image002



For the presentation I set it to use the new Confidential style.



clip_image003[5]



I then provisioned a page, set the confidentiality and I had the following result. As you can see a big red banner is displayed to the user indicating that the content on this page should be created as confidential.



clip_image005



Conclusions



This pretty much concludes my exploration into creating custom content query web parts with SharePoint Designer 2010. I do recognize that if the queries for data become more complex, I recommend creating custom web parts and use LINQ to SharePoint to get out the data and present it. However I really like this solution because I was able to accomplish this without having to write a ton of complex code.



Next



We are going to explore how to redeploy this entire solution using Visual Studio.

4 comments:

Alex C said...

Thanks for posting this article. I've been looking for a way to customize a content query web part without hacking/unghosting the ItemStyle.xsl. This looks promising and I'll try it. Hope it works on SP2010.

Jason Apergis said...

Alex,

Thanks for the feedback.

I had the exact same reaction and that is why I came up with this. It should give you exactly what you are looking for.

Jason

Priyanka said...

Hi,
can you please guide me to do the following requirement, which will allow me to have a background image for my content query webpart?

Jason Apergis said...

Priyanka,

I have not tried what I recall about the content query web part is that configuration is not available. Options I can think of are 1) if the background image is absolutely needed, rebuild what you need as a custom web part 2) put the background image in the master page or page layout and have the content query web part site on top of it.