Wrapping the contents of a Content Query Web Part in additional markup
Content Query Web Part, Customization, SharePoint 2010, Tips & Tricks
When building content aggregations using the Content Query Web Part (CQWP) there are often situations when you need to wrap the results of the aggregation in some additional markup. Find out how to wrap the contents of a CQWP in additional markup.
Building content aggregations with the CQWP
You can use the Content Query Web Part to build all kinds of content aggregations: from simple list-like overviews to dynamic carousels. The CQWP supports all of that because it allows you to fully control the output HTML using XSLT. When building the markup of an overview you not only have to consider the markup of items but also the markup surrounding the overview and this has to do with how HTML and CSS work.
It’s a wrap
By wrapping some portion of a page into a container, such as a div, you can isolate that fragment of the page from other elements. Such isolation offers you great benefits not only from the layout perspective but also from the semantic, and therefore accessibility and search engine optimization, point of view.
Another great benefit of wrapping contents into a container element has to do with the cascading properties of CSS. Instead of specifically marking up every single item of a content aggregation as an item of specific type, you can instead mark the whole overview as a specific type of overview and leverage the cascading capabilities of CSS to format the items as desired. Consider the following markup:
<div class="overview-item">
<p><a href="page-1.html">Page 1</a></p>
<p>Page 1 description</p>
</div>
<div class="overview-item">
<p><a href="page-2.html">Page 2</a></p>
<p>Page 2 description</p>
</div>
<div class="overview-item">
<p><a href="page-3.html">Page 3</a></p>
<p>Page 3 description</p>
</div>
Did you notice the overview-item class that is applied to every div surrounding each item? How about if we could change the markup above as follows:
<div class="overview">
<div>
<p><a href="page-1.html">Page 1</a></p>
<p>Page 1 description</p>
</div>
<div>
<p><a href="page-2.html">Page 2</a></p>
<p>Page 2 description</p>
</div>
<div>
<p><a href="page-3.html">Page 3</a></p>
<p>Page 3 description</p>
</div>
</div>
Isn’t the markup cleaner? By adding the wrapping div and applying a CSS class there we can now remove the CSS class from every item. This not only lowers the page size but also simplifies the maintenance of the page: should we ever need to change anything about this overview, all we need to do is to change the CSS class on the wrapper div and that’s all!
Wrapping the contents of a Content Query Web Part in additional markup
Before we start modifying the CQWP’s XSL files, let’s take a moment to examine how the CQWP works and how the XSL files are used.
Anatomy of CQWP XSL files
The Content Query Web Part uses three XSL files: ContentQueryMain.xsl, Header.xsl and ItemStyle.xsl. The contents of the ContentQueryMain.xsl file are applied once to every aggregation. The Header.xsl file contains XSL templates that are applied once for every group and finally, the ItemStyle.xsl file contains XSL templates that are applied once for every item returned from a content aggregation. Below is the hierarchy in which XSL templates are applied when building the markup in CQWP:
- / (ContentQueryMain.xsl)
- OuterTemplate (ContentQueryMain.xsl)
- OuterTemplate.Body (ContentQueryMain.xsl)
- OuterTemplate.CallHeaderTemplate / OuterTemplate.CallFooterTemplate / OuterTemplate.CallItemTemplate (ContentQueryMain.xsl)
- Specific ItemStyle template (ItemStyle.xsl)
XSL templates 1-3 are executed once for every content aggregation. Templates mentioned under number 4 are wrapped in an for-each loop iterating through all content aggregation items. The OuterTemplate.CallHeaderTemplate and OuterTemplate.CallFooterTemplate XSL templates are applied once for every group and the OuterTemplate.CallItemTemplate template is applied once for every item. Finally the XSL processing moves from the ContentQueryMain.xsl file to ItemStyle.xsl and applies the right Item Style template as configured in the CQWP.
With that there are two ways in which we can wrap the contents of a content aggregation in some additional markup: we can do this from within the ContentQueryMain.xsl file, but we can as well add the surrounding markup to the contents of our Item Style.
Approach 1: Wrapping the contents of a content aggregation from within ContentQueryMain.xsl
The idea behind this approach is fairly easy: in order to wrap the results of a content aggregation in some additional markup we have to apply it before item-level styles are applied, which would be before the templates mentioned under number 4. Given the fact that the OuterTemplate.Body template contains only the for-reach loop iterating trough content aggregation items, it would be the best to have our markup added in the OuterTemplate template.
Because we want our additional markup to be ItemStyle-specific we first have to retrieve the name of the ItemStyle used by the CQWP. For this add the following code snippet in the ContentQueryMain.xsl file in line 43, just after the IsEmpty variable definition:
<xsl:variable name="ItemStyle" select="$Rows[1]/@Style" />
Next we are ready to start adding the wrapping markup. Add the following code snippet in line 57 just before calling the OuterTemplate.Body template:
<xsl:choose>
<xsl:when test="$ItemStyle = 'Default'">
<xsl:text disable-output-escaping="yes"><![CDATA[<div class="overview">]]></xsl:text>
</xsl:when>
</xsl:choose>
And then add the following code snippet in line 67 right after the closing tag of the call to the OuterTemplate.Body template:
<xsl:choose>
<xsl:when test="$ItemStyle = 'Default'">
<xsl:text disable-output-escaping="yes"><![CDATA[</div>]]></xsl:text>
</xsl:when>
</xsl:choose>
Using the xsl:choose statement we start the logic of conditional application of the additional markup to our overviews. Although we could as well use the xsl:if comparison, as we have only one comparison at this moment, the odds are high that you might want to add surrounding markup to multiple ItemStyles.
Next we compare the name of the currently used Item Style and if it matches we start adding the surrounding markup. Because HTML in XSLT is considered XML it has to be well formatted to prevent errors. Since we are wrapping the contents of the OuterTemplate.Body template we have to split our HTML in the before and the after piece and in order to prevent XML formatting errors, we have to escape it using the xsl:text element with the disable-output-escaping attribute set to yes. Finally, because our HTML is invalid, it also has to be wrapped in a <![CDATA[]]> block. Once we have the before markup done, we have to do the same to add the closing markup.
Although this approach works and is quite easy to implement, it has one serious flaw. While the markup surrounding the content aggregation is Item Style-specific, we have added it to the ContentQueryMain.xsl file. With that we have created a tight coupling between the Item Styles defined in the ItemStyle.xsl file and the additional logic, which is required by the Item Style but which is present in the ContentQueryMain.xsl file. Should we ever want to reuse the specific Item Style, we would have to keep in mind to copy the surrounding markup from the ContentQueryMain.xsl file. The big advantage of choosing for this approach is the fact that it deals properly with the additional markup that the CQWP applies to every item which I described in more detail in my previous article.
Approach 2: Wrapping the contents of a content aggregation using ItemStyle.xsl and counters
Did you know that when applying XSL templates, the Content Query Web Part counts the position of which row it is processing at the moment? Using that counter we could add surrounding markup to our ItemStyles!
Important: By default the Content Query Web Part adds some HTML markup around every item from within the ContentQueryMain.xsl file. This approach will work only if you suppress this markup using the approach I presented in my previous article.
Although the Content Query Web Part stores the information about the position of the content aggregation item being processed at the time, it doesn’t make it available within Item Styles by default. We can change this behavior by applying the following modifications in the ContentQueryMain.xsl file.
Add the following code snippet in line 130 just after passing the CurPosition as parameter:
<xsl:with-param name="LastRow" select="$LastRow" />
Next add the following code snippet in line 149 right after defining the CurPosition parameter:
<xsl:param name="LastRow" />
Finally add the following code snippet in line 169 right after the call to apply Item Style templates:
<xsl:with-param name="CurPos" select="$CurPosition" /> <xsl:with-param name="LastRow" select="$LastRow" />
With that we are ready to leverage counters in our Item Styles to add surrounding markup. In this example we will extend the TitleOnly Item Style defined in the ItemStyle.xsl file.
First we need to extend the definition of our Item Style so that it receives counter information from ContentQueryMain.xsl. For this add the following code snippet in line 114 right under the definition of the TitleOnly XSL template:
<xsl:param name="CurPos" /> <xsl:param name="LastRow" />
With that we can start adding the surrounding markup. Add the following code snippet in line 127 right after defining the last variable:
<xsl:if test="$CurPos = 1">
<xsl:text disable-output-escaping="yes"><![CDATA[<div class="overview">]]></xsl:text>
</xsl:if>
And then add the following code snippet in line 146 right before closing the XSL template:
<xsl:if test="$CurPos = $LastRow">
<xsl:text disable-output-escaping="yes"><![CDATA[</div>]]></xsl:text>
</xsl:if>
As you can see adding the surrounding HTML markup is very similar to how we did it in the previous approach. The only difference is in the fact that in this approach we compare the number of the row to determine whether we should output the surrounding markup or not.
The great benefit of using this approach is the fact that all of the markup that belongs to that Item Style is defined within that Item Style itself. Should you ever want to reuse that Item Style, all you need to do, is to ensure, that the CQWP passes the item counter information to the Item Styles.
Building self-contained Item Styles
Wrapping the contents of a Content Query Web Part is not the only scenario when you would want to apply techniques presented in this article. Consider for example a banner carousel that, next to the proper HTML markup, requires some additional JavaScript and CSS. Should you define those in the Master Page and have them served to everyone on every page no matter if the banner carousel is on that page or not? Why not instead leverage either of the two approaches presented above and have the references to all required assets be added to the page only when necessary?
Summary
When building content aggregations using the Content Query Web Part there are often situations when you need to wrap the results of the aggregation in some additional markup. The CQWP offers you multiple approaches to do this, and depending on what you are trying to achieve, you should make an educated choice about which approach fits your scenario the best.




June 26th, 2012 at 11:07 am
Very useful indeed for instance when you are working with JQuery and JQueryUI stuff. Often you don't want to have extra DIVs added by SharePoint or have a UL list as the item wrapper but DIVs instead. In those cases you need to change the main XSL and item XSL as described in your two excellent posts.
One suggestion from my side: often I don't want to edit the ContentByQueryMain.xsl or the item style xsl. What I do is create copies of those files, strip all the styles that I don't need and add my own outer style and item styles.
Next you need to add a content by query web part to the page, export the webpart definition file, edit it with for instance notepad and replace XSL links with relative paths to your XSL files, import the web part definition again on the page and there you go.
Most of the times I just do this once so that I have a preconfigured webpart that is using custom xsl files.
July 2nd, 2012 at 8:36 am
That's a good tip indeed. If you're using the Mavention Content Query Web Part you don't even have to export the CQWP to change the XSL links as they are available as Web Part Properties in the Editor Pane.
August 17th, 2012 at 12:39 pm
Hi Waldek,
I have tried to create the tabular itemstyle, but the LastRow variable is not being read.. I have attached the code, and cannot seem to find any reason as to why this would not work.
<![CDATA[YearTitleNumber]]>
<![CDATA[Last Row]]>
–
August 17th, 2012 at 12:40 pm
OOps… forgot that the code can't be pasted in here.. :/
November 17th, 2012 at 3:13 am
Hi
I have created a content query webpart that fetches data from a list, list has 1 single line text field(field name is 'address' and 1 yes/no boolean field(field name is 'show') in itemstyle.xslt i can access address field by @address but for @show it always return blank string, even i cannot see my 'show' field in webpart properties presentation section, do i need to do some extra for yes/no columns? If i change the 'show' column type to single line of text from yes/no it works
November 19th, 2012 at 7:26 am
Have you mapped the Show column to your Show slot in the Web Part properties?
November 21st, 2012 at 6:47 am
Hi
Thank you for your reply. yes its working now
I thought if column names are same we don't need to map.
I have another issue, i am using approach 2, i have added "div" start and end tag and a separate div tag (id="test") that will be rendered for each item, following is my markup
<![CDATA[]]>
<![CDATA[]]>
i get following output,
Hotmail.comgooglehome
this output completely ignoring my div start and end tags and adding some ul and li tags. i don't want these ul and li tags.
I have also tried to render a dropdown and that works very well.
abc
November 21st, 2012 at 7:50 am
Hi
Your another post related to unwanted ul and li tags worked for me. thanks.
November 30th, 2012 at 9:11 pm
Hi Waldek,
Thank you so much for your contribution. I'm trying to display newsletter article previews, about 3 to a page (they contain a heading,image and description) I incorporated your code and another persons jquery code for a image slider. I can scroll up and down, by one article at a time (div) using prev and next links but I want to page up and down, 3 at a time. Any ideas? Thanks.
December 2nd, 2012 at 9:19 pm
It would depend a little on the implementation of your slider. Do you have a CQWP with paging or are you loading all results and just showing them in bulks of 3?