Living a SharePoint life

Thursday, July 31, 2014

Creating a bookshelf with the Content Query Webpart in SharePoint

A lot of information these days comes in form of an eBook or similar format like a simple PDF. In SharePoint the easiest way to present this information to the user is as a list. For the more ambitious there is the metadata navigation, which brings a lot of beauty for handling list content. I will use a more sophisticated approach via the Content Query Webpart (CQWP) to present the information like books on the bookshelf.


Preparation


Before we can start, we’ll need to step back for a moment and think about the data structure we’ll need in order to support the view we are creating here. There are a few questions you must ask yourself:

  • Where and in which document library will you store you eBooks?
  • Will you mix different content types in that document library or will you create a new library for every document type?

In my case, I have more documents other than just eBooks. I’ve decided to keep all documents in a single document library and to identify the documents by their content type. Therefore, one of the first steps will be to create the library, the columns and the content type.
This post is

The library


If you don’t already have a document library you can use, create a new one. Therefore, from the Site Actions menu select New document library and create a new document library where you can store the eBooks later.

The columns


The very first column we’ll need is a Hyperlink. The eBooks on the bookshelf will be showing the book cover, and for this we need the ability to save the cover. Now we can approach this column with a simple hyperlink field or we can use the publishing feature and select the Image with formatting and constraints for publishing field. I’ve chosen the Image with formatting, because I’ll have more comfort when I work with the content type later.

If you work with SharePoint 2013 you can choose to use the image rendition feature.

To create new columns, go to the Site Actions menu and select the Site Settings. Then select the Site columns link from the Galleries. Add a new column, name it Cover and use the Image with formatting and constraints for publishing field. Adjust the rest of the settings as you need.


Continue adding columns and name the next one BookSubject. The BookSubject column will be used later to filter the view of the bookshelf. The field of this column can be either Choice or Managed Metadata. I’ve chosen Metadata so I can easily expand the choices later on without tempering too much with the content type.


Now we have the most basic columns needed to get things starting. You can however add more columns for different purposes. For instance you can add a recommendation column if you like to high light a title, or a difficulty column to express the complexity factor of an eBook. Think a little bit about it and I’m sure you’ll come up with some nice ideas on your own. Feel free to post your ideas in the comments section at the end of this article and share your ideas with the others.

The Content Type


When you’re done creating the columns it’s time to continue with the content type. Now remember what you are planning to implement here. If you’re only storing eBooks in the document library then you might be fine by just creating one content type. If you’re however working on a larger site with different webs and areas to use different filtered CQWP to present your information in different ways, you might want to use inheriting content types. Maybe you want to distinguish between manuals, booklets or price lists. Then it would be more expedient for more than one content type. However, for the sake of simplicity I’ll use a single content type.

Create your content type by navigating to the Site Actions menu and then Site settings. Pick the Site content types link from the Galleries section. Create a new content type, name it ebook and make sure you inherent the Parent Content Type from Documents. This is essential because we need to store the eBook later somehow. Add the columns you created earlier by following the link Add from existing site columns.


Go to the document library and open the Library settings. Follow the link to the Advanced settings and set the Content Types to Yes.


Return to the Library Settings page and follow the Add from existing site content types link to add the content type.


Finish everything up and then continue by creating the web page were your bookshelf will be located.

The web page


By now you probably already know where you want to use the CQWP. If so go there, otherwise just create a new page from the Site action menu.

If you haven’t yet worked with the CQWP, you might want to read about the basics first. I also have another blog post where I explain how to change the display templates for the CQWP, which you might find worth reading as well.

Start editing the page and add a single Content Query Webpart to the page. Open the Webpart configuration and expand the Query section. From Source select the Show items from the following list and select the document library you’ll use to store the eBooks. For now save everything out and export the Webpart to your local hard drive and open the file with your favorite XML editor.



Changing the Look & Feel


We will need another look for our CQWP. For this we’ll create our own template and apply it to the Webpart.

Open the CQWP webpart file you just exported in an editor and change the following parameter in the configuration:

<property name="ItemXslLink" type="string">/sites/siteCollection/Style%20Library/XSL%20Style%20Sheets/BookshelfItemStyle.xsl</property>

<property name="MainXslLink" type="string">/sites/siteCollection/Style%20Library/XSL%20Style%20Sheets/BookshelfContentQueryMain.xsl</property>


You need to change the paths to fit your needs.

I’ve created the template in advance at codepen.io where you can have a look at the HTML and CSS. You can look there how it's implemented.

http://codepen.io/samurai-ka/details/vEjJy/

The trick is to mix my own code with the XSLT. Lucky for you you’ll only need to copy & paste it ;) If you want to understand how it works you can study the code directly at codepen.io and even fiddle around with it.

Create two new documents in you XSLT editor. Name them as follow:

  • BookshelfItemStyle.xsl
  • BookshelfContentQueryMain.xsl
Add the following code to the BookshelfItemStyle.xsl document:

<xsl:stylesheet 
  version="1.0" 
  exclude-result-prefixes="x d xsl msxsl cmswrt"
  xmlns:x="http://www.w3.org/2001/XMLSchema" 
  xmlns:d="http://schemas.microsoft.com/sharepoint/dsp" 
  xmlns:cmswrt="http://schemas.microsoft.com/WebParts/v3/Publishing/runtime"
  xmlns:ddwrt="http://schemas.microsoft.com/WebParts/v2/DataView/runtime"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt">
  <xsl:param name="ItemsHaveStreams">
    <xsl:value-of select="'False'" />
  </xsl:param>
  <xsl:variable name="OnClickTargetAttribute" select="string('javascript:this.target=&quot;_blank&quot;')" />
  <xsl:variable name="ImageWidth" />
  <xsl:variable name="ImageHeight" />
  <xsl:template name="Default" match="*" mode="itemstyle">
        <xsl:variable name="SafeLinkUrl">
            <xsl:call-template name="OuterTemplate.GetSafeLink">
                <xsl:with-param name="UrlColumnName" select="'LinkUrl'"/>
            </xsl:call-template>
        </xsl:variable>
        <xsl:variable name="SafeImageUrl">
            <xsl:call-template name="OuterTemplate.GetSafeStaticUrl">
                <xsl:with-param name="UrlColumnName" select="'ImageUrl'"/>
            </xsl:call-template>
        </xsl:variable>
        <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>
        <div class="item">
            <xsl:if test="string-length($SafeImageUrl) != 0">
                <div class="image-area-left"> 
                    <a href="{$SafeLinkUrl}">
                      <xsl:if test="$ItemsHaveStreams = 'True'">
                        <xsl:attribute name="onclick">
                          <xsl:value-of select="@OnClickForWebRendering"/>
                        </xsl:attribute>
                      </xsl:if>
                      <xsl:if test="$ItemsHaveStreams != 'True' and @OpenInNewWindow = 'True'">
                        <xsl:attribute name="onclick">
                          <xsl:value-of disable-output-escaping="yes" select="$OnClickTargetAttribute"/>
                        </xsl:attribute>
                      </xsl:if>
                      <img class="image" src="{$SafeImageUrl}" title="{@ImageUrlAltText}">
                        <xsl:if test="$ImageWidth != ''">
                          <xsl:attribute name="width">
                            <xsl:value-of select="$ImageWidth" />
                          </xsl:attribute>
                        </xsl:if>
                        <xsl:if test="$ImageHeight != ''">
                          <xsl:attribute name="height">
                            <xsl:value-of select="$ImageHeight" />
                          </xsl:attribute>
                        </xsl:if>
                      </img>
                    </a>
                </div>
            </xsl:if>
            <div class="link-item">
              <xsl:call-template name="OuterTemplate.CallPresenceStatusIconTemplate"/>
                <a href="{$SafeLinkUrl}" title="{@LinkToolTip}">
                  <xsl:if test="$ItemsHaveStreams = 'True'">
                    <xsl:attribute name="onclick">
                      <xsl:value-of select="@OnClickForWebRendering"/>
                    </xsl:attribute>
                  </xsl:if>
                  <xsl:if test="$ItemsHaveStreams != 'True' and @OpenInNewWindow = 'True'">
                    <xsl:attribute name="onclick">
                      <xsl:value-of disable-output-escaping="yes" select="$OnClickTargetAttribute"/>
                    </xsl:attribute>
                  </xsl:if>
                  <xsl:value-of select="$DisplayTitle"/>
                </a>
                <div class="description">
                    <xsl:value-of select="@Description" />
                </div>
            </div>
        </div>
    </xsl:template>
 <xsl:template name="Bookshelf" match="Row[@Style='Bookshelf']" mode="itemstyle">
     <xsl:variable name="SafeLinkUrl">
            <xsl:call-template name="OuterTemplate.GetSafeLink">
                <xsl:with-param name="UrlColumnName" select="'LinkUrl'"/>
            </xsl:call-template>
        </xsl:variable>
        <xsl:variable name="SafeImageUrl">
            <xsl:call-template name="OuterTemplate.GetSafeStaticUrl">
                <xsl:with-param name="UrlColumnName" select="'ImageUrl'"/>
            </xsl:call-template>
        </xsl:variable>
        <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:choose>
   <xsl:when test="string-length($SafeImageUrl) != 0">
    <img src="{$SafeImageUrl}" title="{@ImageUrlAltText}" />
   </xsl:when>
   <xsl:otherwise>
    <img src="/sites/Site/SiteCollectionImages/BookTemplate300px.jpg" title="{@ImageUrlAltText}" />
   </xsl:otherwise>
  </xsl:choose>
  <div class="bookshelf-caption bottom-to-top">
   <a href="{$SafeLinkUrl}" title="{@LinkToolTip}">
    <xsl:if test="$ItemsHaveStreams = 'True'">
     <xsl:attribute name="onclick">
      <xsl:value-of select="@OnClickForWebRendering"/>
     </xsl:attribute>
    </xsl:if>
    <xsl:if test="$ItemsHaveStreams != 'True' and @OpenInNewWindow = 'True'">
     <xsl:attribute name="onclick">
      <xsl:value-of disable-output-escaping="yes" select="$OnClickTargetAttribute"/>
     </xsl:attribute>
    </xsl:if>
    <xsl:value-of select="$DisplayTitle"/>
   </a>
   <div class="description">
    <xsl:value-of select="@Description" />
   </div>
  </div>
 </xsl:template>
 <xsl:template name="ShowAllRows" match="Row[@Style='ShowAllRows']" mode="itemstyle">
  <xsl:for-each select="@*">
   <xsl:value-of select="name()" /> : <xsl:value-of select="."/><br />
  </xsl:for-each>
  <br /><br />
 </xsl:template>
 <xsl:template name="HiddenSlots" match="Row[@Style='HiddenSlots']" mode="itemstyle">
    <div class="SipAddress">
      <xsl:value-of select="@SipAddress" />
    </div>
    <div class="LinkToolTip">
      <xsl:value-of select="@LinkToolTip" />
    </div>
    <div class="OpenInNewWindow">
      <xsl:value-of select="@OpenInNewWindow" />
    </div>
    <div class="OnClickForWebRendering">
      <xsl:value-of select="@OnClickForWebRendering" />
    </div>
  </xsl:template>
</xsl:stylesheet>


You will need to change a path in row 104 and add a picture to the SiteCollectionImages library in your Site collection.
<img src="/sites/Site/SiteCollectionImages/BookTemplate300px.jpg" title="{@ImageUrlAltText}" />


Of course you can use any other path as you wish. If you’re wondering what this is for; It’s a failsafe in case you haven’t added a picture to your eBook entry in the document library. Due to copyright reasons I cannot link you a picture here but I’m sure you’ll find a nice dummy picture on your own. Just make sure the picture is 300 pixels high.

Add the following code to the BookshelfContentQueryMain.xsl document:

<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="Zone" />
    <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:variable name="BeginList" select="string('&lt;ul class=&quot;bookshelf&quot;&gt;')" />
  <xsl:variable name="EndList" select="string('&lt;/ul&gt;')" />
  <xsl:variable name="BeginListItem" select="string('&lt;li class=&quot;bookshelf-book&quot;&gt;')" />
  <xsl:variable name="EndListItem" select="string('&lt;/li&gt;')" />
  <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:if test="$FeedEnabled = 'True' and $PageId != ''">
                <div class="cqfeed">
                    <xsl:variable name="FeedUrl1" select="concat($SiteUrl,$FeedPageUrl,'xsl=1&amp;web=',$WebUrl,'&amp;page=',$PageId,'&amp;wp=',$WebPartId,'&amp;pageurl=',$CBQPageUrl,$CBQPageUrlQueryStringForFilters)" />
                    <a href="{cmswrt:RegisterFeedUrl( $FeedUrl1, 'application/rss+xml')}"><img src="\_layouts\images\rss.gif" border="0" alt="{cmswrt:GetPublishingResource('CbqRssAlt')}"/></a>
                </div>
            </xsl:if>
    </xsl:template>
    <xsl:template name="OuterTemplate.Empty">
        <xsl:param name="EditMode" />
            <xsl:if 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:if>
    </xsl:template>
    <xsl:template name="OuterTemplate.Body">
      <xsl:param name="Rows" />
      <xsl:param name="FirstRow" />
      <xsl:param name="LastRow" />
      <xsl:variable name="BeginColumn1" select="string('&lt;ul class=&quot;dfwp-column dfwp-list&quot; style=&quot;width:')" />
      <xsl:variable name="BeginColumn2" select="string('%&quot; &gt;')" />
      <xsl:variable name="BeginColumn" select="concat($BeginColumn1, $cbq_columnwidth, $BeginColumn2)" />
      <xsl:variable name="EndColumn" select="string('&lt;/ul&gt;')" />
      <xsl:for-each select="$Rows">
            <xsl:variable name="CurPosition" select="position()" />
            <xsl:if test="($CurPosition &gt;= $FirstRow and $CurPosition &lt;= $LastRow)">
                <xsl:variable name="StartNewGroup" select="@__begingroup = 'True'" />
                <xsl:variable name="StartNewColumn" select="@__begincolumn = 'True'" />
                <xsl:choose>
                    <xsl:when test="$cbq_isgrouping != 'True'">
                        <xsl:if test="$CurPosition = $FirstRow">
                            <xsl:value-of disable-output-escaping="yes" select="$BeginColumn" />
                        </xsl:if>
                    </xsl:when>
                    <xsl:when test="$StartNewGroup and $StartNewColumn">
                        <xsl:choose>
                            <xsl:when test="$CurPosition = $FirstRow">
                                <xsl:value-of disable-output-escaping="yes" select="$BeginColumn" />
                                <xsl:call-template name="OuterTemplate.CallHeaderTemplate"/>
                            </xsl:when>
                            <xsl:otherwise>
                                <xsl:call-template name="OuterTemplate.CallFooterTemplate"/>
                                <xsl:value-of disable-output-escaping="yes" select="concat($EndColumn, $BeginColumn)" />
                                <xsl:call-template name="OuterTemplate.CallHeaderTemplate"/>
                            </xsl:otherwise>
                        </xsl:choose>
                    </xsl:when>
                    <xsl:when test="$StartNewGroup">
                        <xsl:call-template name="OuterTemplate.CallFooterTemplate"/>
                        <xsl:call-template name="OuterTemplate.CallHeaderTemplate"/>
                    </xsl:when>
                    <xsl:when test="$StartNewColumn">
                        <xsl:choose>
                            <xsl:when test="$CurPosition = $FirstRow">
                                <xsl:value-of disable-output-escaping="yes" select="$BeginColumn" />
                            </xsl:when>
                            <xsl:otherwise>
                                <xsl:value-of disable-output-escaping="yes" select="concat($EndColumn, $BeginColumn)" />
                            </xsl:otherwise>
                        </xsl:choose>
                    </xsl:when>
                    <xsl:otherwise>
                    </xsl:otherwise>
                </xsl:choose>
                <xsl:call-template name="OuterTemplate.CallItemTemplate">
                    <xsl:with-param name="CurPosition" select="$CurPosition" />
                </xsl:call-template>
                <xsl:if test="$CurPosition = $LastRow">
                  <xsl:if test="$cbq_isgrouping = 'True'">
                    <xsl:call-template name="OuterTemplate.CallFooterTemplate"/>
                  </xsl:if>
                  <xsl:value-of disable-output-escaping="yes" select="$EndColumn" />
                </xsl:if>
            </xsl:if>
        </xsl:for-each>
    </xsl:template>
    <xsl:template name="OuterTemplate.CallHeaderTemplate">
      <xsl:value-of disable-output-escaping="yes" select="$BeginListItem" />
      <xsl:apply-templates select="." mode="header">
        </xsl:apply-templates>
      <xsl:value-of disable-output-escaping="yes" select="$BeginList" />
    </xsl:template>
    <xsl:template name="OuterTemplate.CallItemTemplate">
    <xsl:param name="CurPosition" />
      <xsl:value-of disable-output-escaping="yes" select="$BeginListItem" />
      <xsl:choose>
              <xsl:when test="@Style='NewsRollUpItem'">
                  <xsl:apply-templates select="." mode="itemstyle">
                     <xsl:with-param name="EditMode" select="$cbq_iseditmode" />
                  </xsl:apply-templates>
              </xsl:when>
              <xsl:when test="@Style='NewsBigItem'">
                  <xsl:apply-templates select="." mode="itemstyle">
                     <xsl:with-param name="CurPos" select="$CurPosition" />
                  </xsl:apply-templates>
              </xsl:when>
              <xsl:when test="@Style='NewsCategoryItem'">
                  <xsl:apply-templates select="." mode="itemstyle">
                     <xsl:with-param name="CurPos" select="$CurPosition" />
                  </xsl:apply-templates>
              </xsl:when>
              <xsl:otherwise>
                  <xsl:apply-templates select="." mode="itemstyle">
                  </xsl:apply-templates>
              </xsl:otherwise>
          </xsl:choose>
      <xsl:value-of disable-output-escaping="yes" select="$EndListItem" />
    </xsl:template>
    <xsl:template name="OuterTemplate.CallFooterTemplate">
      <xsl:value-of disable-output-escaping="yes" select="$EndList" />
      <xsl:value-of disable-output-escaping="yes" select="$EndListItem" />
    </xsl:template>
    <xsl:template name="OuterTemplate.GetSafeLink">
        <xsl:param name="UrlColumnName"/>
        <xsl:if test="$UseCopyUtil = 'True'">
            <xsl:value-of select="concat($RootSiteRef,'/_layouts/CopyUtil.aspx?Use=id&amp;Action=dispform&amp;ItemId=',@ID,'&amp;ListId=',@ListId,'&amp;WebId=',@WebId,'&amp;SiteId=',$SiteId,'&amp;Zone=',$Zone,'&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="presence-status-icon"><img src="/_layouts/images/imnhdr.gif" onload="IMNRC('{@SipAddress}')" ShowOfflinePawn="1" alt="" id="{concat('MWP_pawn_',$ClientId,'_',@ID,'type=sip')}"/></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>


Upload both XSLT documents to the Site Collection Style Library on the SharePoint Server. Don’t forget to publish the file after uploading them to the server otherwise the users won’t be able to use them.


That is Style


Now delete the CQWP you added earlier to the web page and import your modified web part configuration back into the page. You should now be able to select the Bookshelf template from the list. But because we’re still missing the CSS for the new template you should see much.


So it's time to create a new CSS file in a text editor and copy the following content into the file. Afterwards you upload the file also to the Site Collection Style Library.

<style>
.bookshelf-book {
  list-style-type:none;
  float:left;
 margin:10px; 
  
  height:300px;
  -webkit-box-shadow: 0px 0px 12px rgba(0,0,0,0.5);
  -moz-box-shadow:    0px 0px 12px rgba(0,0,0,0.5);
  box-shadow:         0px 0px 12px rgba(0,0,0,0.5);
 position:relative;
  overflow:hidden;
  -webkit-animation: trans 2s;
  animation: trans 2s;
}
.bookshelf-book img {
  height:100%;
}
.ribbon-new {
  width: 100px;
  height: 100px;
  overflow: hidden;
  position: absolute;
}
.ribbon-new > div {
  font: bold 15px sans-serif;
  color: #333;
  text-align: center;
  text-shadow: rgba(255,255,255,0.5) 0px 1px 0px;
  -webkit-transform: rotate(-45deg);
  -moz-transform:    rotate(-45deg);
  -ms-transform:     rotate(-45deg);
  -o-transform:      rotate(-45deg);
  position: relative;
  padding: 7px 0;
  left: -30px;
  top: 15px;
  width: 120px;
  background-color: #66BA59;
  background: #c9de96;
  background: -webkit-linear-gradient(top,  #c9de96 0%,#66ba59 100%);
  background:        -webkit-gradient(linear, left top, left bottom, color-stop(0%,#c9de96), color-stop(100%,#66ba59));
  background:    -moz-linear-gradient(top,  #c9de96 0%, #66ba59 100%);
  background:      -o-linear-gradient(top,  #c9de96 0%,#66ba59 100%);
  background:     -ms-linear-gradient(top,  #c9de96 0%,#66ba59 100%);
  background:         linear-gradient(to bottom,  #c9de96 0%,#66ba59 100%);
  filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#c9de96', endColorstr='#66ba59',GradientType=0 );
  -webkit-box-shadow: 0px 0px 3px rgba(0,0,0,0.3);
  -moz-box-shadow:    0px 0px 3px rgba(0,0,0,0.3);
  box-shadow:         0px 0px 3px rgba(0,0,0,0.3);
}
.bookshelf-caption {
  background-color:#000;
  color:#fff;
  opacity: 0.8;
  filter: alpha(opacity=80);
  position:absolute;
  height:300px;
  width:95%;
  padding:10px;
}
.bookshelf-caption a{
  font-size:2em;
}
.bookshelf-caption div.description {
  font-size:1.4em;
  color:#fff;
}
.bottom-to-top {
  top:100%;
 }
.bookshelf-book:hover .bottom-to-top {
  top:0%;
  transition: all 0.5s ease;
  -webkit-transition: all 0.5 ease;  
}
@-webkit-keyframes trans {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}
@keyframes trans {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}
</style>


Now you can add the Content Editor Webpart to your web page and link the CSS code to the page.



Cleaning up…


Finally you’ll tweak your CQWP to show the content you like. We have created our own content type for the document library, so we need to narrow down our selection on our own content type. You will also need to adjust the Fields to display in your CQWP and link your columns to the display. Add Title to the title field, Comments to the Description field and Cover to the Image field.

Still remembering that we added the BookSubject column to our content type? You can use this column to filter for the different books you wish to show to your audience. You can use the PageQueryString Option as a filter and simply add the filtering parameter to the URL when you navigate to the page. If you play around a little I’m sure you’ll come up with some nice ideas on your own.



The last step is to add some content to the document library. Upload a document, select the eBook as the content type and apply a Subject. For the picture you need to take a screenshot of the PDF, or whatever you uploaded. You do not need to be very precise about the aspect ratio but you must make sure that the picture is 300 pixels high. The CSS will scale the picture, but if the thumbnail is too small it will be blurry. You don’t want it to be to large either otherwise you’ll waste precious space and need to transfer more information to the browser than necessary.


That's it


I hope you liked this post and you’re able to use some of the information I gave you here. Please feel free to leave a comment in the section down below and don’t forget to share this posting with all of your friend on Twitter and facebook.

Featured Post

How are Microsoft Search quota consumed?

With Office 365 Search, Microsoft has created a central entry point for the modern workplace. In one convenient spot, users can access all ...