Grouping by a multi-value Managed Metadata Field using XSLT and a DataFormWebPart

First and foremost, I think that there’s a reason why grouping (generally speaking) is usually disabled when the group-by criteria is on a multi-value field.

Performance is probably the key point here: especially when you are dealing with semi-structured data (i.e. SharePoint list items), you need to extract a set of unique values first, on which, in a second pass, you apply your filtering logics.

Also, but this is just my personal opinion, having a result set where a single item may appear more than once (if it “belongs” to more than one group) is far from optimal. I would definitely prefer to rely on refiners, rather than on pre-pupulated item groups.

And finally… where, it’s a little bit tricky J

Here’s what I have done.

Scenario.

A document library with a Managed Metadata field which allows multiple values, named “OU” (which stands for, guess what, Organizational Unit J).

A DataFormWebPart fetching all items from that library.

And, of course, XSLT!

Implementation.

I started implementing a recursive template that parses the value of the taxonomy field and produces an XML tree.

<xsl:template
name=extractOU>

<xsl:param
name=list />

<xsl:variable
name=thisId
select=substring-before($list, ‘;#’) />

<xsl:variable
name=thisName
select=substring-before(substring-after($list, ‘;#’),’;#’) />

<xsl:variable
name=left
select=substring-after(substring-after($list, ‘;#’),’;#’) />

<ou
id={$thisId}
name={normalize-space($thisName)} />

<xsl:if
test=$left>

<xsl:call-template
name=extractOU>

<xsl:with-param
name=list
select=$left />

</xsl:call-template>

</xsl:if>

</xsl:template>

When applied to a field value such as:

1;#HR;#2;#Finance;#3;#Marketing;#

It would eventually produce an XML fragment similar to this one:

<ou
id=1
name=HR />

<ou
id=2
name=Finance />

<ou
id=3
name=Marketing />

 

I applied this template to the overall result set which is returned by the list query, saving the output into a variable:

<xsl:variable
name=items
select=/dsQueryResponse/Rows/Row />

<xsl:variable
name=groupsTree>

<xsl:for-each
select=$items>

<xsl:call-template
name=extractOU>

<xsl:with-param
name=list
select=concat(normalize-space(@OU.), ‘;#’) />

</xsl:call-template>

</xsl:for-each>

</xsl:variable>

This would generate an XML fragment with duplicate elements:

<ou
id=1
name=HR />

<ou
id=2
name=Finance />

<ou
id=3
name=Marketing />

<ou
id=2
name=Finance />

<ou
id=3
name=Marketing />

<ou
id=1
name=HR />

<ou
id=2
name=Finance />

 

In order to extract a set of unique rows (a “distinct” operation) and to iterate over its output, I had to first load the XML tree into a nodeset:

<xsl:variable
name=groups
select=msxsl:node-set($groupsTree)/* />

 

I used the MSXML node-set function, which is not necessarily the best way to achieve this goal, but unfortunately I could not use XSLT extensions (without extending the DataFormWebPart, which was out of scope here) nor I could use XSLT 2.0, which is not supported as well.

On that node set I could finally apply standard XSLT grouping techniques.

In this case I chose the Muenchian method (here you can find an excellent reference).

I defined a key based on the name attribute of my ou element:

<xsl:key
name=organizational-units
match=ou
use=@name />

And I finally used that key to filter the items within the overall set:

<xsl:for-each
select=$groups[count(. | key(‘organizational-units’, @name)[1]) = 1]>

<xsl:sort
select=@name />

<xsl:variable
name=id
select=@id />

<xsl:variable
name=ou
select=@name />

<div
class=certification well>

<h2
class=subtitle text-center>

<xsl:value-of
select=$ou />

</h2>

<ul
class=unstyled inline>

<xsl:for-each
select=$items[contains(@OU., concat($id,’;#’,$ou))]>

<xsl:sort
select=@Title
order=descending />

<li
style=width:170px>

<div
class=wrapper
style=position:relative; text-align:center>

<xsl:value-of
select=@CertificationIcon0
disable-output-escaping=yes />

<div>

<a
title=@Title
href={@FileRef}
target=_blank>View</a>

</div>

</div>

</li>

</xsl:for-each>

</ul>

<div
style=clear:both></div>

</div>

</xsl:for-each>

 

As a final note, performance implications are quite noticeable here.

I have n+1 operations on the result set (1 to get the groups, n to extract values for the specific group I’m processing).

And I use a contains criteria to get the items back.

I could accept this since this library will only have a bunch of items (a few dozens), but this may not always be the case 😛

 

Extending Base Xslt Web Parts with custom control bindings

What a long title! Maybe it requires a little bit of explanation. Here it is.

First of all, by Base XSLT Web Parts I mean a whole family of OOTB SharePoint Web Parts that leverage XSLT transformation to produce markup out of XML data coming from a data source. If you dig into the class hierarchy starting, say, from the XsltListViewWebPart or the ContentByQueryWebPart, you’ll find something like this:

image

If you navigate up the hierarchy, you’ll see that several features are implemented by the base DataFormWebPart class. One of these features is the capability to resolve dynamic parameters, i.e. values that come from query string, postback parameters and a few other sources.

More accurately speaking, the sources for dynamic parameters and the logics of parameters substitution are defined by an internal method of DataFormWebPart, named ResolveParameterBindingsToParameterValues.

Below you can find an excerpt of the method implementation:

image

As you can observe, you can actually set the ParameterBindings web part property to an XML string with proper semantics, thus inject dynamic values as XSLT parameters for the rendering transformation.

You can choose from the following sources:

  • Query String
  • Postback Parameters
  • Form Parameters
  • Web Part Connections
  • Web Part Variables
  • Resources
  • Server Variables
  • CAML Variables
  • Controls

Since XSLT and XPath are a powerful query and transformation engine, you will be able to define conditional blocks and data manipulation expressions based on these dynamic bindings.

But there’s more!

Did you notice the last binding source, the Control source?

The way it works is easy: you have to specify the server ID of an ASP.NET control available on the page and the name of one of its properties. The implementation of DataForWebPart will recursively walk the ASP.NET control tree upwards until it finds a control with the specified ID: then, with a couple of Reflection tricks the value of the specified property will be retrieved and made available to the web part rendering.

You can try this behavior adding, say, a TextBox or a DropDownList control on the page and using the value coming from the Text and SelectedValue properties respectively.

What I like most of this approach (hence the reason of this post’s title) is that you are not limited to OOTB web controls: you can “attach” pretty much any kind of control which exposes a readable property in a plain format (complex data types would be serialized using the ToString method, so the usefulness of these properties really depends on the overridden implementation of the ToString method).

By any kind of control I mean any kind of controls, including any custom control that you may develop in order to extend the XSLT* Web Parts capabilities.

From the extensibility point of view, this means that you (as a developer) can write a suite of simple web controls that are not meant to be used for their output markup, but just as a data source for existing, SharePoint web parts. Then, a colleague who is capable of building no-code solutions using SharePoint Designer will be able to leverage your extensions, using CQWPs or CoreResultsWPs without reinventing the wheel (i.e. building everything from scratch).

Nice, isn’t it?

Below you can find a sample implementation of a web control that exposes the current UI culture, and that may be useful in multilingual scenarios where you want your aggregation web parts (CQWPs, etc…) to behave differently according the the selected language.

But you can easily imagine that this technique can be applied in an infinite number of ways!

   1:  namespace GreenTeam.SharePoint2010.Multilanguage
   2:  {
   3:    public class CultureControl : Control
   4:    {
   5:      [WebBrowsable]
   6:      public Int32 UICultureLCID 
   7:      {
   8:        get
   9:        {
  10:          return Thread.CurrentThread.CurrentUICulture.LCID;
  11:        }      
  12:      }
  13:   
  14:      [WebBrowsable]
  15:      public Int32 CultureLCID
  16:      {
  17:        get
  18:        {
  19:          return Thread.CurrentThread.CurrentCulture.LCID;
  20:        }
  21:      }
  22:   
  23:      protected override void Render(HtmlTextWriter writer)
  24:      {
  25:        // Do not emit markup at all!
  26:      }
  27:    }
  28:  }

XSLT and verbatim content with CDATA sections

Here’s a quick tip you may find useful while writing some – maybe complex – XSLT transformations.

I adopted this solution recently for some library code that emits RSS complaint XML documents.

My need was to create XML fragments that contain unescaped HTML content, so that I could easily create HTML formatted elements values without worrying too much about HTML encoding.

Of course, CDATA sections were a suitable (and easy) option.

But you cannot include CDATA sections as such in the processing transformation, since it would have caused an automatic escaping of the inner content…. unless… you specify that one or more of the output elements will indeed contain CDATA sections.

You can achieve this goal just by specifying a cdata-section-elements as an attribute of the XSL element output (see this excerpt from the XSL reference documentation):

<output>
method = xml | html | text | qname-but-not-ncname
version = nmtoken
encoding = string
omit-xml-declaration = yes | no
standalone = yes | no
doctype-public = string
doctype-system = string
cdata-section-elements = qnames
indent = yes | no
media-type = string
Model: EMPTY
</output>

Credits go to Bernie Zimmermann, whose detailed post you can find here: http://www.bernzilla.com/2008/02/12/utilizing-cdata-section-elements-in-xsl/