Create a Customized Content Query Web Part (CQWP) in SharePoint 2007/2010 with results Tabbed, Grouped, and Gridded

A Customized CQWP is a powerful way to aggregate data from multiple SharePoint sites.
A Customized CQWP is a powerful way to aggregate data from multiple SharePoint sites. | Source

Updates

05/08/2013 - 'User' issue in SharePoint 2007 Solved

Based on the 05/07/13 hunch, my tests today show that it works as described when "Name (with presence)" is selected in the 'Show Field' of the Site Column

05/07/2013 - I think I figured out why 'User' didn't work:

In the site column there is an option for "Show Field". Even though the field datatype was Person or Group, I had only selected "Name", not "Name (with presence)". I discovered this when trying to display the data in a different webpart, and the hyperlinked word didn't show.

I haven't tested this, but will do so when I get time.

05/02/2013 - Finished making a prototype for SharePoint 2010. Here are some new facts:

1. The <CommonViewFields> does NOT need to be populated in order to show data. My property is completely blank, but I can access ALL data from the list. (Don't know if this is a good thing or not, but it's the way it is.)

2. The "User" datatype problem that I had in SharePoint 2007 is not a problem in 2010!!!!!!!

3. Easy Tabs work in 2010, too!

4. I had to make a specific Web Part Page in 2010, as the home.aspx does not allow for hidden CEWPs. Not sure why. (Based on the fact that I had to create a new web part page, I also had to enable the Left NavBar using my own instructions: SharePoint 2010: Add/Show the Quick Launch NavBar on a Web Part Page)

05/01/2013 - Today I finished a proof of concept in SharePoint 2010. For the most part, I had to recreate everything from scratch, using all the same techniques specified below. There is some new code in 2010 that I made sure not to touch as I implemented the custom code. The takeaway there is that you can't copy and paste from 2007 to 2010, you must implement specific lines of code.

Prologue

I had hoped to never have to learn this much about a Content Query Web Part (CQWP), but the organization decided to stop using a well-known aggregation platform, also known as CorasWorks. So, in an attempt to remediate all of the pages that depend on the CorasWorks grid, I stepped into an already-in-progress project that required the use of a CQWP. Personally, I think I would have preferred to scrap it all and start over with a Data View Web Part (DVWP), but after spending 40 hours constructing this thing, I might as well tell you about it.

First thing I need to do is to thank everyone in the "Credits" section at the bottom of this article. Without all of those people posting their golden nuggets, then you wouldn't be reading this. They paved the muddy trail, and made it such that my boots stayed clean. If you're just starting, my guess would be that you've encountered these great people in your web searches, so please take the time to read/watch them, and give them their due.

Regarding the topic, I doubt that you'll grab it all the first time. If you're like me, you'll need to read an article a couple of times (including this one) as it may not make sense the first time you read it. Reading and re-reading it numerous times will happen. I know I did.

Thanks for reading. Feel free to post questions / comments as I'll try hard to respond.

Select a Content Type

Using a custom content type is an easy way to filter for data to display in the CQWP
Using a custom content type is an easy way to filter for data to display in the CQWP | Source

First things first.

Learning the basics of the CQWP is the first step, and understanding how you can aggregate data, will make this exercise easier for you. You see, the way that a CQWP can compile data from across multiple sites is by instructing it to look for a certain Content Type. So, if you use a generic Content Type, like Task, then you'll end up pulling EVERY task list throughout your scope. This will probably not be desired, so to overcome this, you can make a Custom Content Type. Once created, you can make lists throughout your scope, and when you instruct the CQWP to look for it, then you'll only get your custom list content.

Creating a Custom Content Type

For this, I went to the Site Collection's Site Content Type Gallery (/_layouts/mngctype.aspx) and created a new Content Type. I used all new Site Columns, ensuring that no spaces or other special characters were in the field names. Spaces will eventually result in having to use some dreaded character string, like "_x0020x_", which just makes your job harder. So, don't use spaces or #, %, $, etc.

Next, I created a custom list based on the "Custom List" template. Edit the setting of the list, and added my new Content Type to the list, such that I had all of the fields needed for display in the CQWP. Once I finalized that model list, I saved that list as a new List Template, for use throughout the Site Collection. For my proof of concept, I did make a couple of sites and put my new list in it, but you can also just make a bunch of lists in a single site, and they'll get picked up by the CQWP as well. (Just makes it easier when your time crunched.)

Custom CQWP

With the infrastructure ready to go, make a "Base" CQWP in preparation to customize it. (Many of the other articles cover this step-by-step, so I've shortened it to save the bandwidth.)

  1. Create a Web Part Page, add a CQWP
  2. Make all the selections needed to get data to show from your disparate list locations. (The big trick at this point is to select the new Custom Content Type. See pic.)
  3. Export your new CQWP for customizing.
  4. Open in SharePoint Designer or other editor that will help with XML based files. (I did it all in SPD.)

To add your custom fields for output through your custom CQWP, you must update the ViewCommonFields property. It is blank when you first export it, like this:

<property name="CommonViewFields" type="string" />

Add you add fields like this example:

<property name="CommonViewFields" type="string">FirstName,Text;Department,Choice;Employee,User;HireDate,DateTime</property>

Here are a couple of things that I wasted time researching:

  1. The type="string" means nothing, so ignore it. Don't touch it, you can only break it.
  2. Each Fieldname,DataType; pairing has a comma between them, not a dot. Ensure you don't fat finger it.
  3. Use a semi-colon if you want another pairing after it. You can have a semicolon before the closing property tag, and it won't cause an error.

*TIME / FRUSTRATION SAVER ALERT*

Add each field only one at a time! Save your webpart, import it into the Gallery, then add an instance to your page and see if it works. Debugging these files is a complete PITA because there is no way to test it prior to using it. If you put all of the fields, and one of them is bogus, then you'll have to do this technique anyway.

Also, adding the fields to this property does NOT make them render in your custom webpart. It only makes them available for output. Until you update the ItemStyle.xsl, they will NOT show.

I also must warn you that at the time of writing this, I was unable to get a field type of "User" to work. When I use it, my webpart bombs when I try to add it to the page. You can follow my thread here: http://social.technet.microsoft.com/Forums/en-US/sharepointcustomizationlegacy/thread/4ba437b4-2638-4082-8a58-325369990b7b

You'll see in my code, later, that I have it as "GPM,Text" but it doesn't return anything, and for now, I'm hardcoding a value for that field in my ItemStyle.xsl for output. If you know how to fix it, please chime in.

*/ALERT*

It's worth noting that if you need to rename columns, then look for the DataColumnRenames property. I did not use it, but the other bloggers have covered this in detail.

My entire .webpart file is below, which is probably overkill, but enjoy.

Custom Templates added to the ItemStyle.xsl get displayed in the Web Parts Style section.
Custom Templates added to the ItemStyle.xsl get displayed in the Web Parts Style section. | Source

Customize the ItemStyle.xsl

As stated previously, just updating the .webpart file to include your fields does NOT allow them to display, it only sets up the infrastructure. So, the first thing to do to ItemStyle.xsl is to make a view that displays ALL of the data that is available to you.

ShowXML Template

So, look at the example code below and locate the Template called "ShowXML". Copy and paste that into the bottom of your ItemStyle.xsl file. (This does customize or unghost your file, but gotta do what you gotta do.) Publish it as a major version, just know that you're affecting your entire Site Collection, so if you botch this, your phone might ring... like immediately. (No pressure, because at worst, you can select "Reset to site definition, and all will be well again.)

In the case it works, you will see something like this:

  1. __begincolumn True
  2. __begingroup True
  3. _Level 1
  4. _x007B_1d22ea11_x002D_1e32_x002D_424e_x002D_89ab_x002D_9fedbadb6ce1_x007D_ 1
  5. _x007B_3ac91263_x002D_7b12_x002D_4cb2_x002D_8786_x002D_014d81e930e0_x007D_ HR Online
  6. _x007B_a19442d8_x002D_6b31_x002D_4381_x002D_8f4f_x002D_c9a2ccfd786b_x007D_ 100.000000000000
  7. _x007B_c8191403_x002D_09d0_x002D_4bf7_x002D_a489_x002D_907ee8d2bbbb_x007D_ 1 - In Progress
  8. Author Clark, Steven B
  9. Comments
  10. Created 2013-04-25 10:27:53
  11. Description
  12. Editor Clark, Steven B
  13. FileRef homesite/office/ProjA/Lists/Project D/1_.000
  14. GPM
  15. GroupStyle DefaultHeader
  16. ID 1
  17. ImageUrl
  18. ImageUrlAltText
  19. LinkUrl http://myservername/Office/ProjA/Lists/Project D/1_.000
  20. ListId {5C57575F-9999-9999-9C66-56623CB294D4}
  21. Modified 2013-04-25 10:28:24
  22. PMODescription
  23. PMOPrimaryPhase Configuration
  24. PMOPrimaryPhaseEnd
  25. PMOPrimaryPhaseStart
  26. PMOProjectFullEnd
  27. PMOProjectFullStart
  28. PMOProjectStatusCategory 2. Yellow
  29. PMOSecondaryPhaseEnd
  30. PMOSecondaryPhaseStart
  31. PubDate Thu, 25 Apr 2013 14:28:24 GMT
  32. PublishingRollupImage
  33. Style ShowXML
  34. Title Project D
  35. WebId {276782A2-4D89-9999-AAAA-5270C8479E1A}

(Some data has been altered to protect the innocent)

The output is sorted in alphabetical order, so your field names will be interspersed with the core data, like Author, ImageURL and Style.

So, what this is telling you are the fields that are available to be displayed. If you don't see it here, you can't output it later. (Again, notice that my GPM field is blank, and I still need help with it. Somebody please help me.)

In the hockey stick learning curve that this article presents, you have now reached the end of the puck-striking end, and you are about to scale upwards toward the gripping end. Again, I would advise that you understand the topics covered in the Credits section, which will reduce the dramatic curve.

Use the existing templates, and guidance from the other articles to create a simple output, which will do things like:

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


Output the content to a table... GROUPED!

In the credits below, Paul Galvin's article describes how to output information in table form. He taps into when to start the table, and more importantly, when to end it. This works with relative ease, and with some formatting, you can make it as pretty as you need. (This is why all of my code has id tags with it. I was working with a great stylesheet guy that told me what to put and where, I just followed suit.)

Unfortunately, I need my data to be grouped, and then also output in table form. So, after spending time just getting the table created, the next thing was to focus on producing it for each Group.

If you look in my ContentQueryMain.xsl code, you will find the following:

<xsl:variable name="GridPMOtableEnd">
<xsl:if test="@Style='GridPMO'">
<![CDATA[ </table></div> ]]>
</xsl:if>
</xsl:variable>

<div id="footer">
<xsl:value-of select="$GridPMOtableEnd" disable-output-escaping="yes" />

What this does is creates a variable called GridPMOTableEnd. If the current style being applied is my "GridPMO", as defined in my ItemStyle.xsl, then it adds the HTML code to end the table. Then, I use a regular select to get the value from the variable, but also use the disable-output-escaping such that it doesn't convert "<" to &lt; and the like.

Also in the ContentQueryMain, you'll see that I followed Paul's lead and passed a parameter for the StartNewGroup (which is already part of the code.). I'm going to admit that don't understand fully how this works, but I appreciate the gift horse. Look through the code and you'll see StartNewGroup and StartNewGrp. (Enjoy!)

In ItemStyle.xsl, I then make a variable for the tableStart, and call the variable just like I did for the tableEnd (in ContentQueryMain.xsl)

<xsl:if test="$StartNewGrp = 'True'">
<xsl:value-of select="$tableStart" disable-output-escaping="yes"/>
</xsl:if>

Formatting Dates in XSLT 1.0

Just want to take a moment to discuss formatting dates. This is readily found, so I won't pretend to have invented it. It's just useful reference in case you're wondering why.

In my ItemStyle.xsl you'll see:

<xsl:value-of select="ddwrt:FormatDate(string(@PMOProjectFullEnd), 1033, 1)"

This is what converted my dates from YYYY-MM-DD to how they look now. The 1033 is American English, and the 1 is the desired format.

It is important to know that the following code must be added to the top of the file in order to call the function:

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

[Edit 03/04/16]

(If the link above this line has StrikeThrough applied, do not assume that it is a broken link, nor incorrect. Since this is a reference to a code library, I don't know that HubPages knows how to deal with it. Here it is broken down:

schemas dot Microsoft dot com / webparts / v2 / dataview / runtime

[/Edit]

Displaying the Status Icons (instead of text)

In my table, the user selects a choice value

1 - Active
2 - Pending
3 - Completed
4 - Undetermined

In a library at the Site Collection level, there are 4 icons (stolen from the KPI's) titled:

1.png
2.png
3.png
4.png

So, in the code (below), just strip off the character and tack on the .png extension.

<xsl:variable name="StatusCatIconURL">
<xsl:value-of select="concat('/Assets1/images/kpi/',substring-before(@PMOProjectStatusCategory,'.'),'.png')" />
</xsl:variable>

Creating Tabs

If you look at the top picture, you can see that there are multiple tabs, each with different status, and has you can imagine, a different CQWP on each tab.

The magic here is the use of Easy Tabs. Implement their code, then make an instance of your CQWP, with the proper filters, on the web part page. Easy Tabs are implemented as code in a Content Editor Web Part (we reference a file in a library, vice posting the code with the CEWP), and everything above that CEWP gets made into a tab. It's very slick.

http://www.pathtosharepoint.com/pages/easyTabs.aspx

Conclusion

With that, Brave Coder, I wish you luck and good health with your Custom CQWP. Be patient, make small code changes, and deploy, such that debugging and/or rolling back are not so tedious and painful.

All of my code is below for full review, and I hope it prevents a few wasted hours on your part, as there was on mine.

Thanks for reading!

Steve Clark
Twin-Soft Corporation
Easy Bins Dumpster Rentals
Independent Consultant

ItemStyle.xsl Custom Templates

<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:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt"
  xmlns:ddwrt="#" id="broken_link_36432304"  style="color:#333; text-decoration:line-through"  oncontextmenu="return showBrokenLink(36432304, false);" onclick="return showBrokenLink(36432304, false)"
  >
    
    <xsl:template name="GridPMO" match="Row[@Style='GridPMO']" mode="itemstyle">
    	<xsl:param name="CurPos" />
		<xsl:param name="Last" />
		<xsl:param name="StartNewGrp" />		
		
	    <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="SafeLinkUrl" select="substring-before(@LinkUrl,'Lists')" />

        <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="tableEnd">
	      <xsl:if test="$CurPos = $Last">
	        <![CDATA[ </table></div> ]]>
	      </xsl:if>
	    </xsl:variable>
	    
	    <xsl:variable name="StatusCatIconURL">
	    	<xsl:value-of select="concat('/Assets1/images/kpi/',substring-before(@PMOProjectStatusCategory,'.'),'.png')" />
	    </xsl:variable>	    

   		<xsl:variable name="tableStart">
   			   			
				<![CDATA[ 
					<div id="GridPMOTable">
					<table cellpadding="0" cellspacing="0"></th></tr>
				 		<tr id="GridPMOTableHeader">
							<th><b>Project Name</b></th>
							<th><b>Phase</b></th>					
							<th><b>Full Start</b></th>
							<th><b>Full End</b></th>
							<th><b>Phase1 Start</b></th>
							<th><b>Phase1 End</b></th>
							<th><b>Phase2 Start</b></th>
							<th><b>Phase2 End</b></th>
							<th><b>GPM</b></th>
							<th><b>Status</b></th>
						</tr>
				]]>
   			
   		</xsl:variable> 

		<xsl:if test="$StartNewGrp = 'True'">
			<xsl:value-of select="$tableStart" disable-output-escaping="yes"/>		
		</xsl:if>
		<tr>
		    <td id="GridPMOTableTDProjectName"><xsl:call-template name="OuterTemplate.CallPresenceStatusIconTemplate"/>     
		        <a href="{$SafeLinkUrl}" target="_blank" title="{@LinkToolTip}">
		         <xsl:value-of select="$DisplayTitle"/>
		        </a>
		   </td>
        	<td id="GridPMOTableTDPhase"><xsl:value-of select="@PMOPrimaryPhase" /></td>
        	<td id="GridPMOTableTDFullStart"><xsl:value-of select="ddwrt:FormatDate(string(@PMOProjectFullStart), 1033, 1)" /></td>
			<td id="GridPMOTableTDFullEnd">  <xsl:value-of select="ddwrt:FormatDate(string(@PMOProjectFullEnd), 1033, 1)" /></td> 
			<td id="GridPMOTableTDPhase1Start"><xsl:value-of select="ddwrt:FormatDate(string(@PMOPrimaryPhaseStart), 1033, 1)" /></td> 
			<td id="GridPMOTableTDPhase1End"><xsl:value-of select="ddwrt:FormatDate(string(@PMOPrimaryPhaseEnd), 1033, 1)" /></td> 
			<td id="GridPMOTableTDPhase2Start"><xsl:value-of select="ddwrt:FormatDate(string(@PMOSecondaryPhaseStart), 1033, 1)" /></td> 
			<td id="GridPMOTableTDPhase2End"><xsl:value-of select="ddwrt:FormatDate(string(@PMOSecondaryPhaseEnd), 1033, 1)" /></td> 
			<td id="GridPMOTableTDGPM"><xsl:value-of select="concat('Clark, Steven B',@GPM)" /></td> 
			<td id="GridPMOTableTDStatus"><img alt="{@PMOProjectStatusCategory}" src="{$StatusCatIconURL}" height="16" width="16" /></td> 		   
        </tr>
                
    </xsl:template>
    
    <xsl:template name="ShowXML" match="Row[@Style='ShowXML']" mode="itemstyle">
		<xsl:variable name="SafeLinkUrl">
			<xsl:call-template name="OuterTemplate.GetSafeLink">
				<xsl:with-param name="UrlColumnName" select="'LinkUrl'"/>
			</xsl:call-template>
		</xsl:variable>
		<b>Item: 
			<i><a href="{@LinkUrl}"><xsl:value-of select="@Title" /></a></i>:
		</b>
		<ol>
			<xsl:for-each select="@*">
				<xsl:sort select="name()"/>
				<li>
					<xsl:value-of select="name()" />
					<xsl:text disable-output-escaping="yes"> </xsl:text>
					<i><xsl:value-of select="." /></i>
				</li>
			</xsl:for-each>
		</ol>
		<br />
	</xsl:template>

</xsl:stylesheet>

ContentQueryMain.xsl

<xsl:stylesheet
    version="1.0"
    exclude-result-prefixes="x xsl cmswrt cbq" 
    xmlns:x="http://www.w3.org/2001/XMLSchema"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:cmswrt="http://schemas.microsoft.com/WebPart/v3/Publishing/runtime"
    xmlns:cbq="urn:schemas-microsoft-com:ContentByQueryWebPart">
    <xsl:output method="html" indent="no" />
    <xsl:param name="cbq_isgrouping" />
    <xsl:param name="cbq_columnwidth" />
    <xsl:param name="Group" />
    <xsl:param name="GroupType" />
    <xsl:param name="cbq_iseditmode" />
    <xsl:param name="cbq_viewemptytext" />
    <xsl:param name="SiteId" />
    <xsl:param name="WebUrl" />
    <xsl:param name="PageId" />
    <xsl:param name="WebPartId" />
    <xsl:param name="FeedPageUrl" />
    <xsl:param name="FeedEnabled" />
    <xsl:param name="SiteUrl" />
    <xsl:param name="BlankTitle" />
    <xsl:param name="BlankGroup" />
    <xsl:param name="UseCopyUtil" />
    <xsl:param name="DataColumnTypes" />
    <xsl:param name="ClientId" />
    <xsl:template match="/">
        <xsl:call-template name="OuterTemplate" />
    </xsl:template>
    <xsl:template name="OuterTemplate">
        <xsl:variable name="Rows" select="/dsQueryResponse/Rows/Row" />
        <xsl:variable name="RowCount" select="count($Rows)" />
        <xsl:variable name="IsEmpty" select="$RowCount = 0" />
            <table id="cbqwp" cellspacing="0" cellpadding="0" class="cbq-layout-main">
                <tr>
                    <xsl:choose>
                        <xsl:when test="$IsEmpty">
                             <xsl:call-template name="OuterTemplate.Empty" >
                                 <xsl:with-param name="EditMode" select="$cbq_iseditmode" />
                             </xsl:call-template>
                        </xsl:when>
                        <xsl:otherwise>
                             <xsl:call-template name="OuterTemplate.Body">
                                 <xsl:with-param name="Rows" select="$Rows" />
                                 <xsl:with-param name="FirstRow" select="1" />
                                 <xsl:with-param name="LastRow" select="$RowCount" />
                            </xsl:call-template>
                        </xsl:otherwise>
                    </xsl:choose>
                </tr>
            </table>
            <xsl:if test="$FeedEnabled = 'True'">
                <div class="cqfeed">
                    <xsl:variable name="FeedUrl1" select="concat($SiteUrl,$FeedPageUrl,'xsl=1&amp;web=',$WebUrl,'&amp;page=',$PageId,'&amp;wp=',$WebPartId)" />
                    <a href="{cmswrt:RegisterFeedUrl( $FeedUrl1, 'application/rss+xml')}"><img src="\_layouts\images\rss.gif" border="0" alt="{cmswrt:GetPublishingResource('CbqRssAlt')}"/></a>
                </div>
            </xsl:if>
    </xsl:template>
    <xsl:template name="OuterTemplate.Empty">
        <xsl:param name="EditMode" />
        <td>
            <xsl:if test="$EditMode = 'True'">
                <div class="wp-content description">
                    <xsl:value-of disable-output-escaping="yes" select="$cbq_viewemptytext" />
                </div>
            </xsl:if>
        </td>
    </xsl:template>
    <xsl:template name="OuterTemplate.Body">
        <xsl:param name="Rows" />
        <xsl:param name="FirstRow" />
        <xsl:param name="LastRow" />
        <xsl:variable name="BeginColumn1" select="string('&lt;td id=&quot;column&quot; width=&quot;')" />
        <xsl:variable name="BeginColumn2" select="string('%&quot; valign=&quot;top&quot;&gt;')" />
        <xsl:variable name="BeginColumn" select="concat($BeginColumn1, $cbq_columnwidth, $BeginColumn2)" />
        <xsl:variable name="EndColumn" select="string('&lt;/td &gt;')" />
        <xsl:for-each select="$Rows">
            <xsl:variable name="CurPosition" select="position()" />
            <xsl:if test="($CurPosition &gt;= $FirstRow and $CurPosition &lt;= $LastRow)">
                <xsl:variable name="StartNewGroup" select="@__begingroup = 'True'" />
                <xsl:variable name="StartNewColumn" select="@__begincolumn = 'True'" />
                <xsl:choose>
                    <xsl:when test="$cbq_isgrouping != 'True'">
                        <xsl:if test="$CurPosition = $FirstRow">
                            <xsl:value-of disable-output-escaping="yes" select="$BeginColumn" />
                        </xsl:if>
                    </xsl:when>
                    <xsl:when test="$StartNewGroup and $StartNewColumn">
                        <xsl:choose>
                            <xsl:when test="$CurPosition = $FirstRow">
                                <xsl:value-of disable-output-escaping="yes" select="$BeginColumn" />
                                <xsl:call-template name="OuterTemplate.CallHeaderTemplate"/>
                            </xsl:when>
                            <xsl:otherwise>
                                <xsl:call-template name="OuterTemplate.CallFooterTemplate"/>
                                <xsl:value-of disable-output-escaping="yes" select="concat($EndColumn, $BeginColumn)" />
                                <xsl:call-template name="OuterTemplate.CallHeaderTemplate"/>
                            </xsl:otherwise>
                        </xsl:choose>
                    </xsl:when>
                    <xsl:when test="$StartNewGroup">
                        <xsl:call-template name="OuterTemplate.CallFooterTemplate"/>
                        <xsl:call-template name="OuterTemplate.CallHeaderTemplate"/>
                    </xsl:when>
                    <xsl:when test="$StartNewColumn">
                        <xsl:choose>
                            <xsl:when test="$CurPosition = $FirstRow">
                                <xsl:value-of disable-output-escaping="yes" select="$BeginColumn" />
                            </xsl:when>
                            <xsl:otherwise>
                                <xsl:value-of disable-output-escaping="yes" select="concat($EndColumn, $BeginColumn)" />
                            </xsl:otherwise>
                        </xsl:choose>
                    </xsl:when>
                    <xsl:otherwise>
                    </xsl:otherwise>
                </xsl:choose>
                <xsl:call-template name="OuterTemplate.CallItemTemplate">
                    <xsl:with-param name="CurPosition" select="$CurPosition" />
                    <xsl:with-param name="LastRow" select="$LastRow" />                    
					<xsl:with-param name="StartNewGroup" select="$StartNewGroup" />
                </xsl:call-template>
                <xsl:if test="$CurPosition = $LastRow">
                    <xsl:call-template name="OuterTemplate.CallFooterTemplate"/>
                    <xsl:value-of disable-output-escaping="yes" select="$EndColumn" />
                </xsl:if>
            </xsl:if>
        </xsl:for-each>
    </xsl:template>
    <xsl:template name="OuterTemplate.CallHeaderTemplate">
        <xsl:apply-templates select="." mode="header">
        </xsl:apply-templates>
    </xsl:template>
    
    <xsl:template name="OuterTemplate.CallItemTemplate">
    	<xsl:param name="CurPosition" />
    	<xsl:param name="LastRow" />
    	<xsl:param name="StartNewGroup" />
    	     	
        <xsl:choose>
            <xsl:when test="@Style='NewsRollUpItem'">
                <xsl:apply-templates select="." mode="itemstyle">
                   <xsl:with-param name="EditMode" select="$cbq_iseditmode" />
                </xsl:apply-templates>
            </xsl:when>
            <xsl:when test="@Style='NewsBigItem'">
                <xsl:apply-templates select="." mode="itemstyle">
                   <xsl:with-param name="CurPos" select="$CurPosition" />
                </xsl:apply-templates>
            </xsl:when>
            <xsl:when test="@Style='NewsCategoryItem'">
                <xsl:apply-templates select="." mode="itemstyle">
                   <xsl:with-param name="CurPos" select="$CurPosition" />
                </xsl:apply-templates>
            </xsl:when>
            
            <xsl:when test="@Style='GridPMO'">
		        <xsl:apply-templates select="." mode="itemstyle">
		          <xsl:with-param name="CurPos" select="$CurPosition" />
		          <xsl:with-param name="Last"   select="$LastRow" />
   		          <xsl:with-param name="StartNewGrp" select="$StartNewGroup" />
		        </xsl:apply-templates>
		    </xsl:when>
      
            <xsl:otherwise>
                <xsl:apply-templates select="." mode="itemstyle">
                </xsl:apply-templates>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>
    <xsl:template name="OuterTemplate.CallFooterTemplate">
	
		<xsl:variable name="GridPMOtableEnd">
			<xsl:if test="@Style='GridPMO'">
		 		<![CDATA[ </table></div> ]]>
		   	</xsl:if>
		</xsl:variable>

		<div id="footer">
    		<xsl:value-of select="$GridPMOtableEnd" disable-output-escaping="yes" />
        </div>
    </xsl:template>
    <xsl:template name="OuterTemplate.GetSafeLink">
        <xsl:param name="UrlColumnName"/>
        <xsl:if test="$UseCopyUtil = 'True'">
            <xsl:value-of select="concat('/_layouts/CopyUtil.aspx?Use=id&amp;Action=dispform&amp;ItemId=',@ID,'&amp;ListId=',@ListId,'&amp;WebId=',@WebId,'&amp;SiteId=',$SiteId)"/>
        </xsl:if>
        <xsl:if test="$UseCopyUtil != 'True'">
            <xsl:call-template name="OuterTemplate.GetSafeStaticUrl">
                <xsl:with-param name="UrlColumnName" select="$UrlColumnName"/>
            </xsl:call-template>
        </xsl:if>
    </xsl:template>
    <xsl:template name="OuterTemplate.GetTitle">
        <xsl:param name="Title"/>
        <xsl:param name="UrlColumnName"/>
        <xsl:if test="string-length($Title) != 0">
            <xsl:value-of select="$Title"/>
        </xsl:if>
        <xsl:if test="string-length($Title) = 0">
            <xsl:if test="$UseCopyUtil = 'True'">
                <xsl:value-of select="$BlankTitle" />
            </xsl:if>
            <xsl:if test="$UseCopyUtil != 'True'">
                <xsl:call-template name="OuterTemplate.GetPageNameFromUrl">
                    <xsl:with-param name="UrlColumnName" select="$UrlColumnName"/>
                </xsl:call-template>
            </xsl:if>
        </xsl:if>
    </xsl:template>
    <xsl:template name="OuterTemplate.FormatColumnIntoUrl">
        <xsl:param name="UrlColumnName"/>
        <xsl:variable name="Value" select="@*[name()=$UrlColumnName]"/>
        <xsl:if test="contains($DataColumnTypes,concat(';',$UrlColumnName,',URL;'))">
            <xsl:call-template name="OuterTemplate.FormatValueIntoUrl">
                <xsl:with-param name="Value" select="$Value"/>
            </xsl:call-template>
        </xsl:if>
        <xsl:if test="not(contains($DataColumnTypes,concat(';',$UrlColumnName,',URL;')))">
            <xsl:value-of select="$Value"/>
        </xsl:if>
    </xsl:template>
    <xsl:template name="OuterTemplate.FormatValueIntoUrl">
        <xsl:param name="Value"/>
        <xsl:if test="not(contains($Value,', '))">
            <xsl:value-of select="$Value"/>
        </xsl:if>
        <xsl:if test="contains($Value,', ')">
            <xsl:call-template name="OuterTemplate.Replace">
                <xsl:with-param name="Value" select="substring-before($Value,', ')"/>
                <xsl:with-param name="Search" select="',,'"/>
                <xsl:with-param name="Replace" select="','"/>
            </xsl:call-template>
        </xsl:if>
    </xsl:template>
    <xsl:template name="OuterTemplate.Replace">
        <xsl:param name="Value"/>
        <xsl:param name="Search"/>
        <xsl:param name="Replace"/>
        <xsl:if test="contains($Value,$Search)">
            <xsl:value-of select="concat(substring-before($Value,$Search),$Replace)"/>
            <xsl:call-template name="OuterTemplate.Replace">
                <xsl:with-param name="Value" select="substring-after($Value,$Search)"/>
                <xsl:with-param name="Search" select="$Search"/>
                <xsl:with-param name="Replace" select="$Replace"/>
            </xsl:call-template>
        </xsl:if>
        <xsl:if test="not(contains($Value,$Search))">
            <xsl:value-of select="$Value"/>
        </xsl:if>
    </xsl:template>
    <xsl:template name="OuterTemplate.GetSafeStaticUrl">
        <xsl:param name="UrlColumnName"/>
        <xsl:variable name="Url">
            <xsl:call-template name="OuterTemplate.FormatColumnIntoUrl">
                <xsl:with-param name="UrlColumnName" select="$UrlColumnName"/>
            </xsl:call-template>
        </xsl:variable>
        <xsl:value-of select="cmswrt:EnsureIsAllowedProtocol($Url)"/>
    </xsl:template>
    <xsl:template name="OuterTemplate.GetColumnDataForUnescapedOutput">
        <xsl:param name="Name"/>
        <xsl:param name="MustBeOfType"/>
        <xsl:if test="contains($DataColumnTypes,concat(';',$Name,',',$MustBeOfType,';'))">
            <xsl:value-of select="@*[name()=$Name]"/>
        </xsl:if>
    </xsl:template>
    <xsl:template name="OuterTemplate.GetPageNameFromUrl">
        <xsl:param name="UrlColumnName"/>
        <xsl:variable name="Url">
            <xsl:call-template name="OuterTemplate.FormatColumnIntoUrl">
                <xsl:with-param name="UrlColumnName" select="$UrlColumnName"/>
            </xsl:call-template>
        </xsl:variable>
        <xsl:call-template name="OuterTemplate.GetPageNameFromUrlRecursive">
            <xsl:with-param name="Url" select="$Url"/>
        </xsl:call-template>
    </xsl:template>
    <xsl:template name="OuterTemplate.GetPageNameFromUrlRecursive">
        <xsl:param name="Url"/>
        <xsl:choose>
            <xsl:when test="contains($Url,'/') and substring($Url,string-length($Url)) != '/'">
                <xsl:call-template name="OuterTemplate.GetPageNameFromUrlRecursive">
                    <xsl:with-param name="Url" select="substring-after($Url,'/')"/>
                </xsl:call-template>
            </xsl:when>
            <xsl:otherwise>
                <xsl:value-of select="$Url"/>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>
    <xsl:template name="OuterTemplate.GetGroupName">
        <xsl:param name="GroupName"/>
        <xsl:param name="GroupType"/>
        <xsl:choose>
            <xsl:when test="string-length(normalize-space($GroupName)) = 0">
                <xsl:value-of select="$BlankGroup"/>
            </xsl:when>
            <xsl:otherwise>
                <xsl:choose>
                    <xsl:when test="$GroupType='URL'">
                        <xsl:variable name="Url">
                            <xsl:call-template name="OuterTemplate.FormatValueIntoUrl">
                                <xsl:with-param name="Value" select="$GroupName"/>
                            </xsl:call-template>
                        </xsl:variable>
                        <xsl:call-template name="OuterTemplate.GetPageNameFromUrlRecursive">
                            <xsl:with-param name="Url" select="$Url"/>
                        </xsl:call-template>
                    </xsl:when>
                    <xsl:otherwise>
                        <xsl:value-of select="$GroupName" />
                    </xsl:otherwise>
                </xsl:choose>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>
    <xsl:template name="OuterTemplate.CallPresenceStatusIconTemplate">
        <xsl:if test="string-length(@SipAddress) != 0">
          <span class="presence-status-icon"><img src="/_layouts/images/imnhdr.gif" onload="IMNRC('{@SipAddress}')" ShowOfflinePawn="1" alt="" id="{concat('MWP_pawn_',$ClientId,'_',@ID,'type=sip')}"/></span>
        </xsl:if>
    </xsl:template>
  </xsl:stylesheet>

Web Part XML

<webParts>
  <webPart xmlns="http://schemas.microsoft.com/WebPart/v3">
    <metaData>
      <type name="Microsoft.SharePoint.Publishing.WebControls.ContentByQueryWebPart, Microsoft.SharePoint.Publishing, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" />
      <importErrorMessage>Cannot import this Web Part.</importErrorMessage>
    </metaData>
    <data>
      <properties>
        <property name="PageSize" type="int">-1</property>
        <property name="SortBy" type="string">{a19442d8-6b31-4381-8f4f-c9a2ccfd786b}</property>
        <property name="ChromeState" type="chromestate">Normal</property>
        <property name="AllowZoneChange" type="bool">True</property>
        <property name="ContentTypeBeginsWithId" type="string" />
        <property name="AllowMinimize" type="bool">False</property>
        <property name="Height" type="string" />
        <property name="FilterField2" type="string" />
        <property name="SystemViewFields" type="string" />
        <property name="ParameterBindings" type="string" null="true" />
        <property name="FeedTitle" type="string" />
        <property name="FilterValue2" type="string" />
        <property name="ContentTypeName" type="string">PMOProjectDetails</property>
        <property name="TitleUrl" type="string" />
        <property name="AllowConnect" type="bool">False</property>
        <property name="DisplayColumns" type="int">1</property>
        <property name="XslLink" type="string" null="true" />
        <property name="ExportMode" type="exportmode">All</property>
        <property name="GroupBy" type="string">{3ac91263-7b12-4cb2-8786-014d81e930e0}</property>
        <property name="DataSourcesString" type="string" />
        <property name="WebUrl" type="string">/Office/EX/ESD/CSB/PortalTeam/POC</property>
        <property name="AllowHide" type="bool">False</property>
        <property name="AdditionalGroupAndSortFields" type="string" null="true" />
        <property name="FeedDescription" type="string" />
        <property name="WebsOverride" type="string" />
        <property name="CommonViewFields" type="string">PMOProjectPillar,Choice;PMOPrimaryPhase,Choice;PMODescription,Text;PMOProjectFullStart,DateTime;PMOProjectFullEnd,DateTime;PMOPrimaryPhaseStart,DateTime;PMOPrimaryPhaseEnd,DateTime;PMOSecondaryPhaseStart,DateTime;PMOSecondaryPhaseEnd,DateTime;GPM,Text;PMOProjectStatusCategory,Choice</property>
        <property name="DisplayName" type="string" />
        <property name="ChromeType" type="chrometype">TitleOnly</property>
        <property name="AdditionalFilterFields" type="string" null="true" />
        <property name="NoDefaultStyle" type="string" null="true" />
        <property name="FilterValue3" type="string" />
        <property name="BaseType" type="string" />
        <property name="FireInitialRow" type="bool">True</property>
        <property name="SortByDirection" type="Microsoft.SharePoint.Publishing.WebControls.ContentByQueryWebPart+SortDirection, Microsoft.SharePoint.Publishing, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c">Desc</property>
        <property name="DataFields" type="string" />
        <property name="GroupStyle" type="string">DefaultHeader</property>
        <property name="DataColumnRenames" type="string" />
        <property name="HeaderXslLink" type="string" />
        <property name="AllowEdit" type="bool">False</property>
        <property name="MissingAssembly" type="string">Cannot import this Web Part.</property>
        <property name="HelpUrl" type="string" />
        <property name="ViewFieldsOverride" type="string" />
        <property name="FilterField1" type="string" />
        <property name="UseCache" type="bool">True</property>
        <property name="ShowUntargetedItems" type="bool">False</property>
        <property name="HelpMode" type="helpmode">Modeless</property>
        <property name="DataSourceID" type="string" />
        <property name="ListName" type="string" />
        <property name="FilterType1" type="string" />
        <property name="Default" type="string" />
        <property name="FilterType2" type="string" />
        <property name="FilterOperator1" type="Microsoft.SharePoint.Publishing.WebControls.ContentByQueryWebPart+FilterFieldQueryOperator, Microsoft.SharePoint.Publishing, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c">Eq</property>
        <property name="ViewFlag" type="string" />
        <property name="FilterOperator3" type="Microsoft.SharePoint.Publishing.WebControls.ContentByQueryWebPart+FilterFieldQueryOperator, Microsoft.SharePoint.Publishing, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c">Eq</property>
        <property name="FilterOperator2" type="Microsoft.SharePoint.Publishing.WebControls.ContentByQueryWebPart+FilterFieldQueryOperator, Microsoft.SharePoint.Publishing, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c">Eq</property>
        <property name="GroupByFieldType" type="string">Choice</property>
        <property name="UseCopyUtil" type="bool">True</property>
        <property name="Description" type="string">PMO Projects Roll-Up (06)</property>
        <property name="Filter1ChainingOperator" type="Microsoft.SharePoint.Publishing.WebControls.ContentByQueryWebPart+FilterChainingOperator, Microsoft.SharePoint.Publishing, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c">Or</property>
        <property name="ItemLimit" type="int">-1</property>
        <property name="SortByFieldType" type="string">Number</property>
        <property name="ListGuid" type="string" />
        <property name="ViewContentTypeId" type="string" />
        <property name="ItemXslLink" type="string" />
        <property name="FeedEnabled" type="bool">False</property>
        <property name="MainXslLink" type="string" />
        <property name="TitleIconImageUrl" type="string" />
        <property name="Filter2ChainingOperator" type="Microsoft.SharePoint.Publishing.WebControls.ContentByQueryWebPart+FilterChainingOperator, Microsoft.SharePoint.Publishing, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c">Or</property>
        <property name="FilterValue1" type="string" />
        <property name="ListsOverride" type="string" />
        <property name="FilterType3" type="string" />
        <property name="Direction" type="direction">NotSet</property>
        <property name="QueryOverride" type="string" />
        <property name="ServerTemplate" type="string">100</property>
        <property name="UseSQLDataSourcePaging" type="bool">True</property>
        <property name="Width" type="string" />
        <property name="FilterByAudience" type="bool">False</property>
        <property name="GroupByDirection" type="Microsoft.SharePoint.Publishing.WebControls.ContentByQueryWebPart+SortDirection, Microsoft.SharePoint.Publishing, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c">Desc</property>
        <property name="SampleData" type="string">&lt;dsQueryResponse&gt;
                    &lt;Rows&gt;
                        &lt;Row Title="Item 1" LinkUrl="http://Item1" Group="Group Header" __begincolumn="True" __begingroup="True" /&gt;
                        &lt;Row Title="Item 2" LinkUrl="http://Item2" __begincolumn="False" __begingroup="False" /&gt;
                        &lt;Row Title="Item 3" LinkUrl="http://Item3" __begincolumn="False" __begingroup="False" /&gt;
                    &lt;/Rows&gt;
                    &lt;/dsQueryResponse&gt;</property>
        <property name="Xsl" type="string">&lt;xsl:stylesheet xmlns:x="http://www.w3.org/2001/XMLSchema" version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:cmswrt="http://schemas.microsoft.com/WebPart/v3/Publishing/runtime" exclude-result-prefixes="xsl cmswrt x" &gt; &lt;xsl:import href="/Style Library/XSL Style Sheets/Header.xsl" /&gt; &lt;xsl:import href="/Style Library/XSL Style Sheets/ItemStyle.xsl" /&gt; &lt;xsl:import href="/Style Library/XSL Style Sheets/ContentQueryMain.xsl" /&gt; &lt;/xsl:stylesheet&gt;</property>
        <property name="CacheXslStorage" type="bool">True</property>
        <property name="Hidden" type="bool">False</property>
        <property name="Title" type="string">PMO Projects Roll-up (06)</property>
        <property name="ShowWithSampleData" type="bool">False</property>
        <property name="CacheXslTimeOut" type="int">86400</property>
        <property name="ItemStyle" type="string">GridPMO</property>
        <property name="AllowClose" type="bool">False</property>
        <property name="FilterField3" type="string" />
        <property name="CatalogIconImageUrl" type="string" />
      </properties>
    </data>
  </webPart>
</webParts>

Credits

If you're just starting to learn how to create your own custom CQWP, then here's all the sources that I found to be helpful. They're not ordered any particular way, but thanks to all of these folks for paving the muddy path for me. I couldn't have done it without them.

CQWP XSL customization
http://sharepointnadeem.blogspot.com/2011/09/cqwp-xsl-customizations.html

Steven van de Craen:
http://blog.pathtosharepoint.com/2009/06/08/the-content-query-web-part/

George Perantatos http://blogs.msdn.com/b/ecm/archive/2006/10/25/configuring-and-customizing-the-content-query-web-part.aspx

Heather Solomon http://www.heathersolomon.com/blog/articles/customitemstyle.aspx

Matt Koon - Custom Header and Footer - (use only when width is predictable or small number of columns) http://zooma.io/articles/sharepoint-user-interface/sharepoint-content-query-web-part-custom-header-and-footer

Table Creation within XSL http://paulgalvinsoldblog.wordpress.com/2007/12/09/display-content-query-web-part-results-in-a-grid-table/

Having fun with XSLT in SharePoint, 2007-style
http://jesse.kimofkims.com/kb/336

Grouping in CQWP http://www.sharepointbriefing.com/spcode/article.php/3874226/Content-Query-Web-Part-SharePoints-Swiss-Army-Knife.htm

Vids:

SharePoint: Custom Content Query Web Parts with Properties
http://www.youtube.com/watch?v=r2uMQ2XRTMk

SharePoint: Displaying Custom Fields in Content Query Web Parts http://www.youtube.com/watch?v=88b_R-H05Io

More by this Author


Comments 2 comments

jason1979 5 months ago

Steven,

Bravo on posting this article.

Can you share the Site column name used in the SPS List?

I` trying to develop a dashboard from multiple lists and testing your solution will enlighten my knowledge.

awaiting response


jason1979 5 months ago

Steven,

I found the Site column names:

Project Name

Phase

Full Start

Full End

Phase1 Start

Phase1 End

Phase2 Start

Phase2 End

GPM

Status

Please confirm, will this solutions works in SharePoint 2013?

    Sign in or sign up and post using a HubPages Network account.

    0 of 8192 characters used
    Post Comment

    No HTML is allowed in comments, but URLs will be hyperlinked. Comments are not for promoting your articles or other sites.


    Click to Rate This Article
    working