SharePoint 2013: Generate dynamic content using XSLT and a comma-delimited string

In SharePoint, the Content Query Web Part uses XSLT templates to display the results of each query. This is normally a fairly straightforward process (for XSLT). For an example, see the “Promo boxes” demo I wrote a while back.

There’s one area, though, where SharePoint lists and XSLT collide: Checkbox columns.

If you have a column in SharePoint that allows multiple choices, when you grab that column data in XSLT, SharePoint sends the data in a string that looks like this:

;#Choice1;#Choice2;#Choice3;#

Just to display that in a human-friendly way, you would have to run it through the translate function at the very least. But what if you want to render customized content based on each of those choices? SharePoint only supports XSLT 1.0, and there is nothing built into 1.0 that automatically splits a string into a traversible array.

In this post, I’ll provide a sample use case that does just that.

New Contracts rollup

The web part we’re going to build displays a list of recently-won bids or contracts. It includes the customer, the contract name, the contract amount, and the department in your company that won the contract. The hitch? A contract may be shared by two or more departments, and we want to credit them all.

For this we will need:

  1. A SharePoint list
  2. An XSLT template to display the results;
  3. A CQWP that points to the list;
  4. Some CSS to style the results

The list

Create a new SharePoint list named “New Contracts”. Give it the following columns:

  1. Title: Contract name
  2. AccountName: “Single Line of Text” column; The customer with whom the contract was signed.
  3. ContractAmount: “Currency” column; the amount of the contract.
  4. WinningDepartments: “Choice” column. Set the choices to whatever makes sense (We’ll use “UX”, “AppDev” and “ContactCenter” for this post). Choose “Display choices using: Checkboxes”.

As an aside, I always make my column names and “Choice” options one word, in camel case, with no special characters. This simplifies things when you’re configuring the CQWP and writing the XSLT. If you have a space in a column name, for instance, SharePoint renders the space as “_x0020_ ” in its internal list. Special characters get encoded in non-obvious ways, too. Avoid them whenever you can.

The XSLT

In SharePoint Designer, go to All Files -> Style LIbrary -> XSL Style Sheets, and open ItemStyle.xsl. Paste the following templates at the bottom of the file, just before the closing </xsl:stylesheet> tag.

    <!-- NewContracts -->
    <xsl:template name="NewContracts" match="Row[@Style='NewContracts']" mode="itemstyle">
        <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="ContractAmount" select="@ContractAmount"/>
        <xsl:variable name="Departments" select="translate(@Departments,';#',',')"/>

            <div class="win-item">
                <div class="win-icons">
                
                    <xsl:call-template name="renderDepartmentIcons">
                        <xsl:with-param name="text" select="$Departments"/>
                    </xsl:call-template>
            
                </div>
                <div class="win-content">
                    <h4>
                           <xsl:value-of select="@AccountName"/>
                    </h4>
                    <p><xsl:value-of select="$DisplayTitle"/><span class="contract-amount">$<xsl:value-of select="format-number($ContractAmount,'###,###,###')"/></span></p>
                </div>
            </div>
    </xsl:template>    
    <!-- NewContracts -->

    <!-- DepartmentIcon render -->
    <xsl:template match="string/text()" name="renderDepartmentIcons">
        <xsl:param name="text" select="."/>
        <xsl:param name="sep" select="','"/>
        <xsl:choose>
            <xsl:when test="contains($text, $sep)">
                <xsl:variable name="iconName" select="normalize-space(substring-before($text, $sep))"/>
                <xsl:if test="string-length($iconName) &gt; 0">
                    <span class="win-icon {$iconName}">.</span>
                </xsl:if>
                <xsl:call-template name="renderDepartmentIcons">
                    <xsl:with-param name="text" select="substring-after($text, $sep)"/>
                </xsl:call-template>
            </xsl:when>
        </xsl:choose>
    </xsl:template>
    <!-- DepartmentIcon render -->

Let’s go through it in detail.

    <!-- NewContracts -->
    <xsl:template name="NewContracts" match="Row[@Style='NewContracts']" mode="itemstyle">
        <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="ContractAmount" select="@ContractAmount"/>
        <xsl:variable name="Departments" select="translate(@Departments,';#',',')"/>

We have a template called “NewContracts”, configured to show up as a selectable template in a SharePoint CQWP. The first thing we do is define three variables. “Title” and “ContractAmount” are straightforward.

“Departments” is the choice list. As I mention above, it arrives as a string with the choices separated by “;#”. So we run it through the translate function to turn those “;#” into commas. This makes them more reader-friendly if we just want to display the list, as well as making them ready for parsing into separate choices.

            <div class="win-item">
                <div class="win-icons">
                
                    <xsl:call-template name="renderDepartmentIcons">
                        <xsl:with-param name="text" select="$Departments"/>
                    </xsl:call-template>
            
                </div>
                <div class="win-content">
                    <h4>
                           <xsl:value-of select="@AccountName"/>
                    </h4>
                    <p><xsl:value-of select="$DisplayTitle"/><span class="contract-amount">$<xsl:value-of select="format-number($ContractAmount,'###,###,###')"/></span></p>
                </div>
            </div>
    </xsl:template>    
    <!-- NewContracts -->

Here is the actual markup for each item. Each item is divided into two parts: A “win-icons” div that will display an icon for each Department associated with the contract, and a “win-content” div that will display information about the contract: the client, contract name, and contract amount. Note that we’re using the format-number function to display the contract amount in traditional U.S. currency style.

You’ll notice that inside the “win-icons” div, we’re calling a template named renderDepartmentIcons, and passing it the (now comma-delimited) Departments string. That external template is what parses the string and renders content based on each value. So let’s take a look at it now:

    <!-- DepartmentIcon render -->
    <xsl:template match="string/text()" name="renderDepartmentIcons">
        <xsl:param name="text" select="."/>
        <xsl:param name="sep" select="','"/>
        <xsl:choose>
            <xsl:when test="contains($text, $sep)">
                <xsl:variable name="iconName" select="normalize-space(substring-before($text, $sep))"/>
                <xsl:if test="string-length($iconName) &gt; 0">
                    <span class="win-icon {$iconName}">.</span>
                </xsl:if>
                <xsl:call-template name="renderDepartmentIcons">
                    <xsl:with-param name="text" select="substring-after($text, $sep)"/>
                </xsl:call-template>
            </xsl:when>
        </xsl:choose>
    </xsl:template>
    <!-- DepartmentIcon render -->

The template takes the passed list of Departments and assigns it to the parameter “text”. It then defines the parameter “sep” as a comma. In other words, it’s a function designed to handle a comma-delimited string, which is why we converted the Departments string to use commas before passing it along.

Because the template can only handle comma-delimited strings, it tests to see if the string contains a comma, using the contains function.

If the string contains a comma, the template grabs the part of the string before the first comma (using the substring-before function) and assigns it to a variable called “iconName”.

It then tests iconName to make sure iconName isn’t blank. If iconName passes the test, the template renders out a span of class “win-icon”, with the iconName value as an additional class.

Regardless of whether a span was rendered or not, we call the renderDepartmentsIcons template again — but only pass it the part of the Department string AFTER the first comma, using the substring-after function. In other words, we chop off the part of the string we just evaluated. This loop continues until there are no more commas in the passed string.

Thus, if there is a single Department involved in the contract, the template will render out a single span, like this:

<span class="win-icon AppDev">.</span>

But if we have multiple departments, the template will render out multiple spans, like this:

<span class="win-icon AppDev">.</span>
<span class="win-icon UX">.</span>
<span class="win-icon ContactCenter">.</span>

There’s a period inside each span because otherwise SharePoint likes to lop off the closing </span> tag, resulting in broken markup.

The HTML

The above XSLT will generate HTML that looks like this for each item:

            <div class="win-item">
                <div class="win-icons">
                    <span class="win-icon AppDev">.</span>
                    <span class="win-icon UX">.</span>
                </div>
                <div class="win-content">
                    <h4>ABC Financial</h4>
                    <p>Really cool web application<span class="contract-amount">$98,800</p>
                </div>
            </div>

The CSS

Finally, we need a little CSS to tie it all together. Assume you’ve created a set of icons that each 25×25 pixels. Then the following CSS will stack those icons up on the left side of each item:

.win-icons {
    float:left;
    width:25px;
}
.win-content {
    margin-left:40px;
}
.win-icon {
    display:block;
    width:25px;
    height:25px;
    margin-bottom:5px;
    color:transparent;
    background: url(images/win-icons/default.png) top left no-repeat transparent;
}
.win-icon.AppDev {
    background-image: url(images/win-icons/AppDev.png);
}
.win-icon.UX {
    background-image: url(images/win-icons/UX.png);
}
.win-icon.ContactCenter {
    background-image: url(images/win-icons/ContactCenter.png);
}

Note that we set the color of the “win-icon” spans to “transparent” so that you can’t see the period we used to keep the <span> from collapsing.

The CQWP

Finally, place a Content Query Web Part on your page, point it at the “New Contracts” list, and choose the “NewContracts” template from the “Item Template” dropdown.

Social Media and Your Organization: Are You Easy to do Business With?

Social media platforms have become one of the most popular ways for customers to interact with businesses. Unfortunately, they are also one of the most common pain points for organizations attempting to conduct routine customer interactions.

All too often, companies devote too little resources to maintaining social media presence. When this happens, consumers who expect fast and effective communication via social media are left wondering whether the organization cares about their problems. And, as social media platforms are widely open to the public, one consumer’s poor customer experience quickly becomes common knowledge in his or her social circle, and the organization’s own presence.

In short, a poorly handled or under-invested social media presence serves little to no purpose; in fact, such a social media effort can lead to customer dissatisfaction. Conversely, a well-run social media effort can lead directly to customer satisfaction and loyalty.

Do you have the bandwidth to quickly respond to customer requests or comments via social media? Does staff have the training and support needed to interact with customers via Twitter, Facebook, LinkedIn and other social media platforms? Are you prepared for the event of a social media crisis?

Carefully consider your social media strategies to be sure that customer engagement and response are as effective as possible.

Save Money by Transitioning to a Hosted Lync Voice Environment

One of the most important considerations when choosing any enterprise solution is cost. With so much of the decision making process tied to ROI and upfront expenses, choosing a solution that is affordable and effective is imperative. Considering all of the costs associated with the solution is also vital. Hosted Lync Voice services offer many cost-saving benefits over on-premise solutions. Savings can be found in a number of areas, including:

Network and hardware updates

Running a powerful communications solution on your own equipment can be taxing. If hardware or network capabilities are not up to the task of running the solution, expensive upgrades may be needed. While it may be necessary to make small tweaks to your network to run Hosted Lync Voice, you will not have to invest heavily in a complete overhaul of the system or replacement of key equipment.

Pre-purchase exploration

Many on-premise solutions require a costly proof of concept exercise before a purchase decision can be made. Rather than requiring this expensive step, Hosted Lync Voice services offer trial periods, allowing your organization to explore the solution at its own pace, often without cost.

SPLA (service Provider Licensing Agreement)

This licensing model chosen specifically to provide a cost-effective method of providing services to Enterprise customers while allowing them to still remain on a monthly plan.

For more information on the benefits of transitioning to a cloud hosted Microsoft Lync Voice environment, visit our Hosted Lync Voice page.