Thursday 21 July 2011

SharePoint Dataviews - Items Per Row?

While DVWPs (Dataview Web Parts) undoubtedly rock, styling them has always been a nightmare of wading through line after line of inexplicable XSLT. To make matters worse, MOSS currently only supports v1.0 and uses the DDWRT namespace.

This means you can't rely on many of the online tutorials and examples you might come across when it comes to styling this beast.

Thankfully, SharePoint Designer provides many useful UIs to assist in this area but IMO they still fall short when it comes to something as simple as stating HOW MANY ITEMS PER ROW you might like to display. Which, of course, brings us finally to the point of this post. :)

The plan is to go from this:

NameTitleDescr
Item1First ItemDull
Item2Second ItemDrab
Item3Third ItemBoring
Item4Fourth ItemKill me now

To something like this:

First Item

Wow!

Second Item

Great stuff!

Third Item

Terrific!

Fourth Item

Wizard!

In my search for a solution to this problem I came across a few examples but sadly none of them was able to provide a straightforward example of how to achieve this. To be fair to Microsoft, there is a two column layout which is what provided me with the base code. But it is terribly inflexible and doesn't allow for simple variations. So, pencils at the ready. Here we go...

Let's Get Started

First things first, you want to either convert an existing List View or add a new Dataview from the Insert menu. Add or remove data fields and header rows as you like; apply pagination, limits, sort order...just don't group by anything. That would add an additional complication to what we're trying to achieve.

For the purpose of this exercise accept the default table template as well. i.e. Don't change the layout.

In Source view, look for the opening tag. Then scroll down until until you get to the first template definition.

<xsl:template match="/"...

Breaking Things Down

For the sake of this exercise I will be styling an image library view but the steps will apply to any type of list. The first thing we're going to do is reduce the meat of this template down to its bare essentials like so:

<table id="myID">
    <xsl:call-template name="dvt_1"/>
</table>

The reason for the ID should be obvious. Apart from providing an easy method for applying custom styles, it also facilitates targeting the table with jQuery or any other language. That's our first template down. Now let's move down to the next two for the body. You may not need to make any changes to these but I provide the code below just in case.

<xsl:template name="dvt_1">
    <xsl:variable name="dvt_StyleName">Table</xsl:variable>
    <xsl:variable name="Rows" select="/dsQueryResponse/Rows/Row"/>
    <xsl:call-template name="dvt_1.body">
        <xsl:with-param name="Rows" select="$Rows"/>
    </xsl:call-template>
</xsl:template>
<xsl:template name="dvt_1.body">
    <xsl:param name="Rows"/>
    <xsl:for-each select="$Rows">
        <xsl:call-template name="dvt_1.rowview"/>
    </xsl:for-each>
</xsl:template>

Using Custom Templates and Variables

Now because this is an image library I've created an additional template to define the thumbnails. While this won't be relevant for other lists I include it as an example of how you can easily create your own custom variables and see how they can be applied. Skip this if you already know.


<xsl:template name="getThumb">
    <xsl:param name="str"/>
    <!-- Default file type for MOSS preview pics -->
    <xsl:variable name="ext">.jpg</xsl:variable>
    <xsl:choose>
        <xsl:when test="contains($str, $ext)">
            <xsl:value-of select="substring-before($str, $ext)"/>
            <!-- Default suffix for MOSS preview pics -->
            <xsl:text>_jpg.jpg</xsl:text>
            <xsl:call-template name="getThumb">
              <xsl:with-param name="str" select="substring-after($str, $ext)"></xsl:with-param>
            </xsl:call-template>
        </xsl:when>
        <xsl:otherwise>
            <xsl:value-of select="$str"></xsl:value-of>
        </xsl:otherwise>
    </xsl:choose>
</xsl:template>

Now For the Fun Part

Now we need to define the contents of our rows. While this looks needlessly complicated (it is!) I've included comments to help break it down. If it still doesn't make sense, don't worry about it. It works. Trust me, ok? ;)

You can either modify the existing template, which will already contain your preferred data fields, or delete everything and start from scratch. You can, of course, omit the imageThumb variable and just start with the opening table cell.

<xsl:template name="dvt_1.rowview">
    <!-- Create a variable to hold our thumbnail template. -->
    <xsl:variable name="imageThumb">
        <!-- Call our template -->
        <xsl:call-template name="getThumb">
            <xsl:with-param name="str" select="@NameOrTitle"></xsl:with-param>
        </xsl:call-template>
    </xsl:variable>
    <!-- START HERE!!
            Now we define our row. You can define this content any way you want
            within the confines of the table cell. Just use the appropriate
            field names and layout for your list. -->
    <td>
        <!-- Title field -->
        <h4><xsl:value-of select="@Title"/></h4>
        <!-- Create a hyperlink to the item display form and wrap it
                around our thumbnail. -->
        <a href="{@FileRef}" 
              title="{@Title}: {@Description}" 
              name="{@Title}">
            <img src="/{@FileDirRef}/_t/{$imageThumb}" alt="{@Description}" />
        </a>
    </td>
    <!-- This is where the magic happens. We use the mod function to define how
            many items we'd like per row. We then test for our position (item
            count)in the data array. If it's not a multiple of our number then
            we ignore it and continue rendering our item template. If it is,
            we close the row and start a new one. -->
    <xsl:if test="position() mod 4 = 0" ddwrt:cf_ignore="1">
        <xsl:text disable-output-escaping="yes"></tr></xsl:text>
        <xsl:if test="position() != last()" ddwrt:cf_ignore="1">
            <xsl:text disable-output-escaping="yes"><tr></xsl:text>
        </xsl:if>
    </xsl:if>
    <!-- Don't forget to apply your item count here too! -->
    <xsl:if test="position() = last()" ddwrt:cf_ignore="1">
        <xsl:if test="position() mod 4 != 0" ddwrt:cf_ignore="1">
            <!-- This never seems to gets called but is somehow required anyway! -->
           <span></span>
        </xsl:if>
    </xsl:if>
</xsl:template>

And that's it! Hope you found this useful.