Inconvenient Content Query Web Part slots and CQWP’s extensibility
SharePoint 2010 Content Query Web Parts ships with the Slots capability which simplifies working with custom Item Styles. Unfortunately it turns out that Slots may be broken when working with extended CQWPs. So what is the problem exactly and, what’s more important, how can you solve it?
Slots – and it’s not about Vegas
Slots are one of the great improvements of the Content Query Web Part in SharePoint 2010. Using slots you can decouple your custom Item Styles (presentation layer) from the underlying data. What’s even more important is that when using slots you don’t need to know the internal names of fields and map them in the hidden properties of the Content Query Web Part. Slots allow you to use the display names what gives you a really convenient way of working with custom Item Styles.
Extending Content Query Web Part with custom functionality
The standard functionality of Content Query Web Part provided with SharePoint 2010 should be sufficient for most scenarios. Once in a while however you might come across some requirement that cannot be achieved using the standard functionality. And while you could create a whole new Web Part, it might be easier to just extend the CQWP with the missing functionality.
One of scenarios for extending the Content Query Web Part capabilities is to provide additional functionality that can be used in XSLT. To do that all you have to do is to inherit from the ContentByQueryWebPart class and override the ModifyXsltArgumentList method. In there you can start extending the standard functionality with custom parameters and XSLT functions.
Unfortunately, it turns out, that extending the Content Query Web Part with new XSLT capabilities comes with a price.
CQWP slots vs. custom XSLT extensions
Imagine the following Item Style template:
<xsl:template name="MyTemplate" match="Row[@Style='MyTemplate']" mode="itemstyle"> <xsl:variable name="isMember" select="contains(@Members, mv:GetUserLoginName())"/> <li> <xsl:choose> <xsl:when test="$isMember"> <a href="[email protected]}"><xsl:value-of select="@SiteTitle"/></a> </xsl:when> <xsl:otherwise> <xsl:value-of select="@SiteTitle"/> </xsl:otherwise> </xsl:choose> </li> </xsl:template>
As you can see there are two slots defined (Members and SiteTitle). In between a custom XSLT function called GetUserLoginName ** is called. When you open CQWP’s properties however you will see only one slot:
Congratulations: you just broke CQWP slots.
The reason why CQWP slots and custom XSLT extensions don’t go well along is how the Content Query Web Parts generates a list of all available slots in the linked Item Style XSL file.
While building the list of all available slots, the Content Query Web Parts creates a new instance of the CQWP, binds dummy data to it and performs an XSL transformation. As you can imagine, as soon as the transformation will stumble upon your extension it will throw an exception and CQWP will stop retrieving slots for your template.
Depending on where in your Item Style template you make a call to your custom functions, you will either see all of your slots, just as if everything worked correctly, you might see only some of them (those located before the call to the custom function) or none of them.
So much for flexibility…
CQWP slots and custom XSLT extensions: better together
It turns our that it is possible to combine custom XSLT extensions with slots after all and all you need for it is a little trick. The basic idea is to make the CQWP parse all of your slots before the first call to any of your custom extensions where the exception occurs.
What doesn’t work
The most obvious solution to this issue, that you might think of, might be to define a variable for every slot, like:
<xsl:variable name="MySlot" select="@MySlot"/>
Unfortunately it doesn’t work. Because available slots are retrieved using an XSL transformation rather than string matching, the value of the variable is not instantiated until it’s being referred to for the first time. And if the call to your custom extension occurs before the first call to the variable, your slots will still not be picked up by the CQWP.
What does work
What you really need, to work around this issue, is a void template. A template that retrieves values of all slots and yet doesn’t output any of them.
<xsl:template name="void"> <xsl:param name="input"/> <xsl:value-of select="''"/> </xsl:template>
With that all you have to do is to modify your Item Style template so that it initializes all slots before doing anything else:
<xsl:template name="MyTemplate" match="Row[@Style='MyTemplate']" mode="itemstyle"> <xsl:call-template name="void"> <xsl:with-param name="input"> <xsl:value-of select="@Slot1"/> <xsl:value-of select="@Slot2"/> <!-- other slots --> </xsl:with-param> </xsl:call-template> <!-- the rest of the template --> </xsl:template>
With this in place you can benefit of all your custom extensions and yet keep the CQWP fully functional.
Trying to find out if the Content Query Web Part will load all slots might be a tedious process especially if you’re working with Content Approval. To help you be productive I created a command line tool that allows you to check which slots will the CQWP pick up for your Item Style template.
It’s usage is simple and if you want to know everything about it just type MaventionWhatsMySlots and hit enter and all available commands will be shown to you.
The great thing about using this tool is that all you have to do is to save your changes in the XSLT files and run the tool. No more you have to worry about publishing them, clearing the cache and reloading the page.
Download: Mavention What’s My Slots (19KB, ZIP)