1.800.323.3639 | Support | Training

Avtex

Creating a Bootstrap Carousel in SharePoint

| No Comments

So you have a Bootstrapped SharePoint site. Everything’s great — until you want to add an image carousel. The default SharePoint image carousel looks like SharePoint, not Bootstrap, and is pretty bare bones.

Sure, you could put together a custom Content by Search display template. But that’s a lot of messing with the SharePoint API.

Here’s how to do it using a Content Query Web Part and some custom XSLT.

This example assumes you already have Bootstrap added to your site, and that your Bootstrap build includes the Carousel functionality.

Create a Picture Library

  1. Go to Site Contents, choose “Add an app”, and add a Picture Library.
  2. Throw in a few test pictures so we can ensure the carousel is working.

Configure a CQWP

Place a CQWP on the page and configure it:

  1. Point the CQWP at your Picture Library
  2. Name it “Image Carousel” or whatever you want.
  3. Set the custom fields so that Title uses “Title;” and Description uses “Description;”

Export the CQWP

Place a CQWP on your page, click the down-arrow in the upper right corner, and choose “Export”.

Edit the CQWP

With CQWPs, SharePoint uses a series of XSL templates to display content. There is ContentQueryMain.xsl, which handles the outer display; and there is “ItemStyle.xsl”, which handles each individual item being rendered.

The problem is that ContentQueryMain is a generic template used by all CQWPs. We need to customize that markup, but we only want it to affect the image carousel. So we have to customize this CQWP to use a custom version of both ContentQueryMain and ItemStyle.

Here’s how:

  • Open the exported CQWP in a text editor like notepad.
  • Search for a <property> tag named “MainXslLink”.  It will look like this:
    <property name="MainXslLink" type="string" />
  • Change it from self-closing to having a closing </property> tag, and then put the relative URL to your custom ContentQueryMain file inside the tag, like this:
    <property name="MainXslLink" type="string">/Style Library/XSL Style Sheets/carouselCQM.xsl</property>
  • Save the file, and upload it back to your SharePoint site.

 Create your custom ContentQueryMain

  1. Go to Style LIbrary -> XSL Style Sheets
  2. Duplicate ContentQueryMain.xsl, and name the duplicate carouselCQM.xsl
  3. Replace the entire contents with the following code:
<xsl:stylesheet
 version="1.0"
 exclude-result-prefixes="x xsl cmswrt cbq"
 xmlns:x="http://www.w3.org/2001/XMLSchema"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:cmswrt="http://schemas.microsoft.com/WebPart/v3/Publishing/runtime"
 xmlns:cbq="urn:schemas-microsoft-com:ContentByQueryWebPart">
 <xsl:output method="xml" indent="no" media-type="text/html" omit-xml-declaration="yes"/>
 <xsl:param name="cbq_isgrouping" />
 <xsl:param name="cbq_columnwidth" />
 <xsl:param name="Group" />
 <xsl:param name="GroupType" />
 <xsl:param name="cbq_iseditmode" />
 <xsl:param name="cbq_viewemptytext" />
 <xsl:param name="cbq_errortext" />
 <xsl:param name="SiteId" />
 <xsl:param name="WebUrl" />
 <xsl:param name="PageId" />
 <xsl:param name="WebPartId" />
 <xsl:param name="FeedPageUrl" />
 <xsl:param name="FeedEnabled" />
 <xsl:param name="SiteUrl" />
 <xsl:param name="BlankTitle" />
 <xsl:param name="BlankGroup" />
 <xsl:param name="UseCopyUtil" />
 <xsl:param name="DataColumnTypes" />
 <xsl:param name="ClientId" />
 <xsl:param name="Source" />
 <xsl:param name="RootSiteRef" />
 <xsl:param name="CBQPageUrl" />
 <xsl:param name="CBQPageUrlQueryStringForFilters" />
 <xsl:param name="EffectiveDeviceChannel" />

 <xsl:template match="/">
 <xsl:call-template name="OuterTemplate" />
 </xsl:template>
 <xsl:template name="OuterTemplate">
 <xsl:variable name="Rows" select="/dsQueryResponse/Rows/Row" />
 <xsl:variable name="RowCount" select="count($Rows)" />
 <xsl:variable name="IsEmpty" select="$RowCount = 0" />
 <div id="{concat('cbqwp', $ClientId)}" class="cbq-layout-main">
 <xsl:if test="$cbq_iseditmode = 'True' and string-length($cbq_errortext) != 0">
 <div class="wp-content description">
 <xsl:value-of disable-output-escaping="yes" select="$cbq_errortext" />
 </div>
 </xsl:if>
 <xsl:choose>
 <xsl:when test="$IsEmpty">
 <xsl:call-template name="OuterTemplate.Empty" >
 <xsl:with-param name="EditMode" select="$cbq_iseditmode" />
 </xsl:call-template>
 </xsl:when>
 <xsl:otherwise>
 <xsl:call-template name="OuterTemplate.Body">
 <xsl:with-param name="Rows" select="$Rows" />
 <xsl:with-param name="FirstRow" select="1" />
 <xsl:with-param name="LastRow" select="$RowCount" />
 </xsl:call-template>
 </xsl:otherwise>
 </xsl:choose>
 </div>
 </xsl:template>


 <xsl:template name="OuterTemplate.Empty">
 <xsl:param name="EditMode" />
 <xsl:choose>
 <xsl:when test="$EditMode = 'True' and string-length($cbq_errortext) = 0">
 <div class="wp-content description">
 <xsl:value-of disable-output-escaping="yes" select="$cbq_viewemptytext" />
 </div>
 </xsl:when>
 <xsl:otherwise>
 <xsl:comment>empty</xsl:comment>
 </xsl:otherwise>
 </xsl:choose>
 </xsl:template>



 <xsl:template name="OuterTemplate.Body">
 <xsl:param name="Rows" />
 <xsl:param name="FirstRow" />
 <xsl:param name="LastRow" />

 <div id="bootstrap-carousel" class="carousel slide" data-ride="carousel">

 <ol class="carousel-indicators">
 <xsl:for-each select="$Rows">
 <xsl:variable name="position">
 <xsl:value-of select="position()"/>
 </xsl:variable>
 <xsl:choose>
 <xsl:when test="position() = 1">
 <li class="active" data-target="#bootstrap-carousel" data-slide-to="{$position}"></li>
 </xsl:when>
 <xsl:otherwise>
 <li data-target="#bootstrap-carousel" data-slide-to="{$position}"></li>
 </xsl:otherwise>
 </xsl:choose>
 </xsl:for-each>
 </ol>
<div class="carousel-inner" role="listbox">
 <xsl:for-each select="$Rows">
 <xsl:call-template name="OuterTemplate.CallItemTemplate"/>
 </xsl:for-each>
 </div>

 <a class="left carousel-control" href="#bootstrap-carousel" role="button" data-slide="prev"><i class="fa fa-chevron-left"> </i></a>
 <a class="right carousel-control" href="#bootstrap-carousel" role="button" data-slide="next"><i class="fa fa-chevron-right"> </i></a>

 </div>
 </xsl:template>


 <xsl:template name="OuterTemplate.CallItemTemplate">
 <xsl:choose>
 <xsl:when test="position() = 1">
 <div class="item active">
 <xsl:apply-templates select="." mode="itemstyle">
 </xsl:apply-templates>
 </div>
 </xsl:when>
 <xsl:otherwise>
 <div class="item">
 <xsl:apply-templates select="." mode="itemstyle">
 </xsl:apply-templates>
 </div>
 </xsl:otherwise>
 </xsl:choose>
 </xsl:template>
 <xsl:template name="OuterTemplate.GetSafeLink">
 <xsl:param name="UrlColumnName"/>
 <xsl:if test="$UseCopyUtil = 'True'">
 <xsl:value-of select="concat($RootSiteRef, '/_layouts/15/CopyUtil.aspx?Use=id&amp;Action=dispform&amp;ItemId=',@ID,'&amp;ListId=',@ListId,'&amp;WebId=',@WebId,'&amp;SiteId=',$SiteId,'&amp;Source=',$Source)"/>
 </xsl:if>
 <xsl:if test="$UseCopyUtil != 'True'">
 <xsl:call-template name="OuterTemplate.GetSafeStaticUrl">
 <xsl:with-param name="UrlColumnName" select="$UrlColumnName"/>
 </xsl:call-template>
 </xsl:if>
 </xsl:template>
 <xsl:template name="OuterTemplate.GetTitle">
 <xsl:param name="Title"/>
 <xsl:param name="UrlColumnName"/>
 <xsl:param name="UseFileName" select="0"/>
 <xsl:choose>
 <xsl:when test="string-length($Title) != 0 and $UseFileName = 0">
 <xsl:value-of select="$Title" />
 </xsl:when>
 <xsl:when test="$UseCopyUtil = 'True' and $UseFileName = 0">
 <xsl:value-of select="$BlankTitle" />
 </xsl:when>
 <xsl:otherwise>
 <xsl:variable name="FileNameWithExtension">
 <xsl:call-template name="OuterTemplate.GetPageNameFromUrl">
 <xsl:with-param name="UrlColumnName" select="$UrlColumnName" />
 </xsl:call-template>
 </xsl:variable>
 <xsl:choose>
 <xsl:when test="$UseFileName = 1">
 <xsl:call-template name="OuterTemplate.GetFileNameWithoutExtension">
 <xsl:with-param name="input" select="$FileNameWithExtension" />
 </xsl:call-template>
 </xsl:when>
 <xsl:otherwise>
 <xsl:value-of select="$FileNameWithExtension" />
 </xsl:otherwise>
 </xsl:choose>
 </xsl:otherwise>
 </xsl:choose>
 </xsl:template>
 <xsl:template name="OuterTemplate.FormatColumnIntoUrl">
 <xsl:param name="UrlColumnName"/>
 <xsl:variable name="Value" select="@*[name()=$UrlColumnName]"/>
 <xsl:if test="contains($DataColumnTypes,concat(';',$UrlColumnName,',URL;'))">
 <xsl:call-template name="OuterTemplate.FormatValueIntoUrl">
 <xsl:with-param name="Value" select="$Value"/>
 </xsl:call-template>
 </xsl:if>
 <xsl:if test="not(contains($DataColumnTypes,concat(';',$UrlColumnName,',URL;')))">
 <xsl:value-of select="$Value"/>
 </xsl:if>
 </xsl:template>
 <xsl:template name="OuterTemplate.FormatValueIntoUrl">
 <xsl:param name="Value"/>
 <xsl:if test="not(contains($Value,', '))">
 <xsl:value-of select="$Value"/>
 </xsl:if>
 <xsl:if test="contains($Value,', ')">
 <xsl:call-template name="OuterTemplate.Replace">
 <xsl:with-param name="Value" select="substring-before($Value,', ')"/>
 <xsl:with-param name="Search" select="',,'"/>
 <xsl:with-param name="Replace" select="','"/>
 </xsl:call-template>
 </xsl:if>
 </xsl:template>
 <xsl:template name="OuterTemplate.Replace">
 <xsl:param name="Value"/>
 <xsl:param name="Search"/>
 <xsl:param name="Replace"/>
 <xsl:if test="contains($Value,$Search)">
 <xsl:value-of select="concat(substring-before($Value,$Search),$Replace)"/>
 <xsl:call-template name="OuterTemplate.Replace">
 <xsl:with-param name="Value" select="substring-after($Value,$Search)"/>
 <xsl:with-param name="Search" select="$Search"/>
 <xsl:with-param name="Replace" select="$Replace"/>
 </xsl:call-template>
 </xsl:if>
 <xsl:if test="not(contains($Value,$Search))">
 <xsl:value-of select="$Value"/>
 </xsl:if>
 </xsl:template>
 <xsl:template name="OuterTemplate.GetSafeStaticUrl">
 <xsl:param name="UrlColumnName"/>
 <xsl:variable name="Url">
 <xsl:call-template name="OuterTemplate.FormatColumnIntoUrl">
 <xsl:with-param name="UrlColumnName" select="$UrlColumnName"/>
 </xsl:call-template>
 </xsl:variable>
 <xsl:value-of select="cmswrt:EnsureIsAllowedProtocol($Url)"/>
 </xsl:template>
 <xsl:template name="OuterTemplate.GetColumnDataForUnescapedOutput">
 <xsl:param name="Name"/>
 <xsl:param name="MustBeOfType"/>
 <xsl:if test="contains($DataColumnTypes,concat(';',$Name,',',$MustBeOfType,';'))">
 <xsl:value-of select="@*[name()=$Name]"/>
 </xsl:if>
 </xsl:template>
 <xsl:template name="OuterTemplate.GetPageNameFromUrl">
 <xsl:param name="UrlColumnName"/>
 <xsl:variable name="Url">
 <xsl:call-template name="OuterTemplate.FormatColumnIntoUrl">
 <xsl:with-param name="UrlColumnName" select="$UrlColumnName"/>
 </xsl:call-template>
 </xsl:variable>
 <xsl:call-template name="OuterTemplate.GetPageNameFromUrlRecursive">
 <xsl:with-param name="Url" select="$Url"/>
 </xsl:call-template>
 </xsl:template>
 <xsl:template name="OuterTemplate.GetPageNameFromUrlRecursive">
 <xsl:param name="Url"/>
 <xsl:choose>
 <xsl:when test="contains($Url,'/') and substring($Url,string-length($Url)) != '/'">
 <xsl:call-template name="OuterTemplate.GetPageNameFromUrlRecursive">
 <xsl:with-param name="Url" select="substring-after($Url,'/')"/>
 </xsl:call-template>
 </xsl:when>
 <xsl:otherwise>
 <xsl:value-of select="$Url"/>
 </xsl:otherwise>
 </xsl:choose>
 </xsl:template>
 <xsl:template name="OuterTemplate.GetGroupName">
 <xsl:param name="GroupName"/>
 <xsl:param name="GroupType"/>
 <xsl:choose>
 <xsl:when test="string-length(normalize-space($GroupName)) = 0">
 <xsl:value-of select="$BlankGroup"/>
 </xsl:when>
 <xsl:otherwise>
 <xsl:choose>
 <xsl:when test="$GroupType='URL'">
 <xsl:variable name="Url">
 <xsl:call-template name="OuterTemplate.FormatValueIntoUrl">
 <xsl:with-param name="Value" select="$GroupName"/>
 </xsl:call-template>
 </xsl:variable>
 <xsl:call-template name="OuterTemplate.GetPageNameFromUrlRecursive">
 <xsl:with-param name="Url" select="$Url"/>
 </xsl:call-template>
 </xsl:when>
 <xsl:otherwise>
 <xsl:value-of select="$GroupName" />
 </xsl:otherwise>
 </xsl:choose>
 </xsl:otherwise>
 </xsl:choose>
 </xsl:template>
 <xsl:template name="OuterTemplate.CallPresenceStatusIconTemplate">
 <xsl:if test="string-length(@SipAddress) != 0">
 <span class="ms-imnSpan">
 <a href="#" onclick="IMNImageOnClick(event);return false;" class="ms-imnlink">
 <span class="ms-spimn-presenceWrapper ms-imnImg ms-spimn-imgSize-10x10">
 <img src="/_layouts/15/images/spimn.png?rev=41" class="ms-spimn-img ms-spimn-presence-disconnected-10x10x32" onload="IMNRC('{@SipAddress}')" ShowOfflinePawn="1" alt="" id="{concat('MWP_pawn_',$ClientId,'_',@ID,'type=sip')}"/>
 </span>
 </a>
 </span>
 </xsl:if>
 </xsl:template>
 <xsl:template name="OuterTemplate.GetFileNameWithoutExtension">
 <xsl:param name="input"/>
 <xsl:variable name="extension">
 <xsl:value-of select="substring-after($input, '.')"/>
 </xsl:variable>
 <xsl:choose>
 <xsl:when test="contains($extension, '.')">
 <xsl:variable name="afterextension">
 <xsl:call-template name="OuterTemplate.GetFileNameWithoutExtension">
 <xsl:with-param name="input" select="$extension"/>
 </xsl:call-template>
 </xsl:variable>
 <xsl:value-of select="concat(substring-before($input, '.'), $afterextension)"/>
 </xsl:when>
 <xsl:otherwise>
 <xsl:choose>
 <xsl:when test="contains($input, '.')">
 <xsl:value-of select="substring-before($input, '.')"/>
 </xsl:when>
 <xsl:otherwise>
 <xsl:value-of select="$input"/>
 </xsl:otherwise>
 </xsl:choose>
 </xsl:otherwise>
 </xsl:choose>
 </xsl:template>
 </xsl:stylesheet>

This template does one thing: creates the outer wrapper markup for the slideshow, then calls the ItemStyle template to render out each individual slide.

Create a custom Item template

  1. Open ItemStyle.xsl
  2. Add the following code to the bottom of it:
<!-- Image carousel -->
 <xsl:template name="ImageGallery" match="Row[@Style='ImageGallery']" mode="itemstyle">
 <xsl:variable name="SafeLinkUrl">
 <xsl:call-template name="OuterTemplate.GetSafeLink">
 <xsl:with-param name="UrlColumnName" select="'LinkUrl'"/>
 </xsl:call-template>
 </xsl:variable>
 <img src="{$SafeLinkUrl}" style="max-width:100% !important; width:100% !important;" />

 <div class="carousel-caption">
 <h3><xsl:value-of select="@Title"/></h3>
 <xsl:value-of select="@Description" />
 </div>
 </xsl:template>

This handles the display for each slide — the slide itself, as well as the Title and Description, if present.

Set your Image Carousel CQWP to use  the new XSL templates

  1. Edit your Image Carousel web part on the SharePoint page
  2. Under “Presentation”, set the Group Style to “Default” and the Item Style to “ImageGallery”.

You’re done! Your carousel should now be functional.

Let’s Talk About CX

X