Jan 4, 2012

Building a Custom ContentQueryWebPart

Since I have just gone through building my first custom CQWP for a publishing site collection, step by step, thought I'd share some notes on it. It's basically really not too complicated, but it does have a few quirks to go with it.

ContentQueryWebPart uses two main xsl-files:
  • the ItemStyles.xsl for item templates
  • the contentquerymain.xsl for the outer templates
Most often what you would be interested in customizing, is the ItemStyles.xsl which is located in the Site Collection Style Library, in the XSL Style Sheets folder. This provides the templates for items in the query result, enabling you to e.g. add fields to show, alter the order of fields and add additional html and style classes to item templates. This, of course, requires knowledge of xsl, how deep depends on what you are trying to do.

The first thought might be "ok, I'll open the ItemStyles.xsl to SharePointDesigner and edit it". I say no, you don't. You open the ItemStyles.xsl to SharePoint Designer and copy all its content and paste it to an xsl file in VisualStudio. But wait! We're not that far along yet, so let's back up a bit.

Yes, the correct way to do this is to create a VisualStudio 2010 project and package it as a wsp solution package. So start by creating an Empty SharePoint (2010) Project (remember to check that your Framework is set to 3.5). 

Type a site to use as test site and select the solution type (either one is ok, sandbox or farm). Add a module (Add > New item) in the project:

Delete the Sample.txt file and add a new item to the module:

Note, the item added is xslt-file (from Data category), remember to change the file type to xsl, e.g. CustomItemStyles.xsl.

Open the Elements.xml file of the module and edit the module information by adding the Style Library as the Url for the module and XSL Style Sheets folder as the Url of the item:

Now it is time to locate the original ItemStyles.xsl and open it for editing in SharePointDesigner. There is no need to check it out since there is no need to edit it, simply copy the contents of the file and paste them to the xsl file just created in VisualStudio. VisualStudio will notify (as an error) that the named template OuterTemplate-etc. does not exist, but don't mind this. The OuterTemplates are defined in the contentquerymain.xsl, which will be available for the custom CQWP without needing to copy it to the project.

Now what we ought to do, is select one of the existing templates as the basis for the custom one - this saves a whole lot of work. So if you're not sure which is a good starting place, insert a CQWP on your site and try out the different OOB templates. Then locate the template you want to modify, copy the complete template tag and paste it on the xsl sheet as a new template tag. Modify it as you like - you can move the divs around and create your own (remember to assign a class for your div!), but be mindful to maintain the schema and general structure of the template!

Now, the item template being done, it is time to add the webpart in the project. The OOB CQWP does not know how to use the custom xsl and there is no way to implement it, so we need to create our custom CQWP as well. Add a SharePoint webpart item in the project:

You don't need the webpart's .cs-file, so you can delete that. Then open the browser again, add a new CQWP on a page and export the webpart without setting any properties. Once saved on your disc, open the webpart file and copy and paste its contents to the webpart file in VisualStudio, replacing the default content created by VS.

Change the title of your webpart:

Then locate the properties ItemXslLink and ItemStyle and set them to point to the custom xsl:

Yes, that's right: no slash between ~sitecollection and Style Library.

Furthermore, if you want to make things neat, you might want to change the category in which the webpart is found on the site. The deafult is Custom, but you can change it by opening the Elements.xml file of the webpart and typing your own group name:

Ok, now we're almost done. The last thing to deal with is the features. Adding the module created a feature and adding the webpart created another one. There is no need for two features, so you can delete the other one. The clue is to delete the right one. Double click on each feature to see its properties. The first one only contains the styles module, the second one shows both modules (although you need to add the styles module to the feature items). Delete the first one, and add the styles module to the other one. 

Also, you might want to rename the feature. One thing is to rename the feature in the feature properties and the other to rename the feature file in the solution explorer (right-click the feature > Rename). You might want to do both.

The final thing to do, is add a feature event receiver. Why? Because for some reason at least a publishing site collection otherwise refuses to let the custom CQWP use the custom xsl file, informing that it is not trusted. So this feature receiver corrects this problem by specifically assigning the sitecollection url for the webpart.

Right click your feature and add an Event Receiver to it. Open the event receiver for editing, uncomment the FeatureActivated method and add the following code to the method, changing the wp.File.Name value to your own webpart name.

(Thanks for the feature receiver goes to our coder, couldn't have come up with it myself!)

Note that you need to add the using statements for System.Linq and System.Text.

You're solution tree should now look like something this:

Now you are ready to package, deploy and test the webpart! 

Tip: If you need to do this more than once, which usually is the case, the easiest way to make it happen is to copy-paste the original xsl and webpart files to your disc drive (or such). Then you don't need to always do the same opening and copying all over again.


Half said...

Moi Sanna,

why bloggers make that wonderful piece of code that you need a picture, so that we cannot copy and paste it?? :D
Thanks for the lesson on replacing the site collection Url, hopefully it will solve my problem.

SM said...

Hei Half,

I'm very sorry about that; with Blogger it is an unfortunate necessity for the editor otherwise intrprets it as a runnable code snippet and does not show it, or strips it to an unrecognizable blunder. I tried cdata and different stuff, but had to resign to using pictures.

Good luck with solving your problem!

CarlyK said...

This is an excellent post and very helpful! The only problem I'm running into is getting the CustomItemStyles.xsl into the Style Library. When viewing Style Library through the UI, it does not appear to be there. However, if I use SharePoint Designer and click All Files, I can then see it. Any ideas?

SM said...

Hi CarlyK,
You can try deleting the file in SharePoint Designer and then deploying it again with a different name. I don't exactly know why this sort of thing sometimes happens, but this might help... Hope you get it to work!

Anonymous said...

Very nice and helpful post. I wasted 2 days on setting this up...

Unknown said...

Hi Sanna,

I followed your guide and it worked like a charm. So as a favour for your work, I can tell you that the reason the xsl-file shows in the list when using SharePoint Designer but does not show up in the list of your SharePoint site is that you didn't specify Type="GhostableInLibrary" for your xsl-file. If type is NOT specified the file is considered not ghosted. You want to make the reference to the orginal file visible and also accessible from a list/library so you therefore should have GhostableInLibrary

For more information about Ghostable, GhostableInLibrary and "Unghost", I found this blog to be useful:

SM said...

Hi Hans,

Thank you for pointing that out! A bit embarrassing to have overlooked it like this, but sometimes it happens...


Sonali said...

Hi nice post, I am struggling a lot to apply custom xsl in cqwp, I found your blog very helpful but got many errors for Outertemplate.
The named template 'OuterTemplate.GetSafeStaticUrl' does not exist.8 Announcemnt_CQWP

Any help would be appreciated...