Application-Wide Help in SharePoint Using DVWPs and List-Based Content

In a recent client project, we wanted to offer some sort of online help capability. While SharePoint has a help capability built into it (those little image icons on every page which usually take you to a page that has nothing to do with what you are actually doing – Microsoft should really fix that), we wanted something a bit different.

In this case, we were using SharePoint uniquely. Without going into all of the specifics, we were using SharePoint as the new front end to a long-standing existing system which was built with .NET on top of SQL. We were going to have hundreds of sites, if not thousands, with every site having exactly the same pages.

The way this worked was that we set up about 20 different page layouts. Each of those page layouts would be used by exactly one page in each site, and we controlled the names of all of the pages. At least in the near- to mid-term, end users wouldn’t be creating pages of their own. SharePoint would not be used to any significant degree for its collaboration capabilities, but as a way to “Webify” the existing application relatively easily.

There are some other really interesting and cool aspects to what we did, but in this post, I want to focus just on the simple help system we built.

Along with all of the identical sites (though each relied on different content and permissions from the back end system) that we were building, we also set up a Training site. The goal for that site was to hold all of the documents and video content that users would need to get up to speed with the system. We also decided to store the help “pages” there so that we could reuse the same content for both purposes.

Rather than using a publishing model, we decided to use a list to hold the help content. I’m a *huge* fan of list-based content over the publishing model for most internal systems. It gives you the possibility of reusing the same content in multiple ways using different delivery channels far more easily. There are those who think otherwise, but there you go. It always depends.

The simple list for the help content looked like this:

image

The actual help content was stored in the Help Text column, which was set up to hold Extended Rich Text. All we needed to know about the content to tie it to the appropriate page was the ASPX Name. This looked like “BuildingStatistics.aspx” or “BuildingReportOverView.aspx”. (Yes, each site represented a single building.) Every site would have the exact same set of aspx pages, so the scheme was simple, yet robust enough to do what we needed. We just used the Title to hold a nice title for the help page, which you’ll see below.

Next I added a Data View Web Part (DVWP) into the master page – gasp! A DVWP in the master page!?!? Yes, you can absolutely put DVWPs into master pages. In many cases it might not be a good idea, since it adds overhead to every page that uses it, but that was exactly what we wanted to do in this case. I’ve done this in quite a few production systems to great effect.

The DVWP in the master page read from the help list and determined just if there was a help item for the page we were on. If there was, the DVWP simply put a help icon into the header of the page, like this one:

image

If we didn’t have help for that page yet, then the icon didn’t show up. Simple enough. Here’s the XSL for that DVWP:

<xsl:template match="/" xmlns:x="http://www.w3.org/2001/XMLSchema" xmlns:d="http://schemas.microsoft.com/sharepoint/dsp" xmlns:asp="http://schemas.microsoft.com/ASPNET/20" xmlns:__designer="http://schemas.microsoft.com/WebParts/v2/DataView/designer" xmlns:SharePoint="Microsoft.SharePoint.WebControls">
  <xsl:call-template name="dvt_1"/>
</xsl:template>

<xsl:template name="dvt_1">
  <xsl:variable name="PageName">
    <xsl:call-template name="GetPageName">
      <xsl:with-param name="URL" select="$URL"/>
    </xsl:call-template>
  </xsl:variable>
  <xsl:variable name="Rows" select="/dsQueryResponse/Rows/Row[contains(@ASPX_x0020_Name,$PageName) and string-length(@Help_x0020_Text) &gt; 0]"/>
  <xsl:if test="count($Rows) &gt; 0">
    <div class="zzz-help-icon">
      <a href="/Training/Help.aspx?FromPage={$PageName}" target="_blank">
        <img alt="Help with this Page" style="border:0;" src="/AppImages/Help-icon.png"/>
      </a>
    </div>
  </xsl:if>
</xsl:template>

<xsl:template name="GetPageName">
  <xsl:param name="URL"/>
  <xsl:choose>
    <xsl:when test="contains($URL,'/')">
      <xsl:call-template name="GetPageName">
         <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>

When the user clicked on the icon, a new browser window would open up with a help page like the one below. I think we called it something like PageHelp.aspx, and it lived in the Training site. Sorry about the blurred out content, but I think you can see the overall structure of the page.

image

The help page simply displayed content from the help list on the left, as well as any associated videos on the right, both with DVWPs. We had the videos hosted on ScreenCast.com for easy streaming and reuse, though they will probably be hosted in-house with the application in the near future.

The help text DVWP’s XSL looked like this:

<xsl:template match="/" xmlns:x="http://www.w3.org/2001/XMLSchema" xmlns:d="http://schemas.microsoft.com/sharepoint/dsp" xmlns:asp="http://schemas.microsoft.com/ASPNET/20" xmlns:__designer="http://schemas.microsoft.com/WebParts/v2/DataView/designer" xmlns:SharePoint="Microsoft.SharePoint.WebControls">
  <xsl:call-template name="PageHelp"/>
</xsl:template>

<xsl:template name="PageHelp">
  <xsl:variable name="dvt_StyleName">Table</xsl:variable>
  <xsl:variable name="Rows" select="/dsQueryResponse/Rows/Row"/>
  <xsl:choose>
    <xsl:when test="count($Rows) = 0">
      <xsl:call-template name="PageHelp.empty"/>
    </xsl:when>
    <xsl:otherwise>
      <table border="0" width="100%" cellpadding="2" cellspacing="0">
        <xsl:for-each select="$Rows">
          <xsl:call-template name="PageHelp.rowview" />
        </xsl:for-each>
      </table>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

<xsl:template name="PageHelp.rowview">
  <tr>
    <td class="ms-vb">
      <h3><xsl:value-of select="@Title"/></h3>
    </td>
  </tr>
  <tr>
    <td class="ms-vb">
      <xsl:value-of select="@Help_x0020_Text" disable-output-escaping="yes"/>
    </td>
  </tr>
</xsl:template>

<xsl:template name="PageHelp.empty">
  <table border="0" width="100%">
    <tr>
     <td class="ms-vb">
        There is no help available for this page.
      </td>
    </tr>
  </table>
</xsl:template>

The videos DVWP’s XSL looked like this:

<xsl:template match="/" xmlns:x="http://www.w3.org/2001/XMLSchema" xmlns:d="http://schemas.microsoft.com/sharepoint/dsp" xmlns:asp="http://schemas.microsoft.com/ASPNET/20" xmlns:__designer="http://schemas.microsoft.com/WebParts/v2/DataView/designer" xmlns:SharePoint="Microsoft.SharePoint.WebControls">
  <xsl:call-template name="dvt_1"/>
</xsl:template>

<xsl:template name="dvt_1">
  <xsl:variable name="Rows" select="/dsQueryResponse/Rows/Row"/>
  <xsl:choose>
    <xsl:when test="count($Rows) = 0">
      <xsl:call-template name="dvt_1.empty"/>
    </xsl:when>
    <xsl:otherwise>
      <table border="0" width="100%" cellpadding="2" cellspacing="0">
        <xsl:call-template name="dvt_1.body">
          <xsl:with-param name="Rows" select="$Rows"/></xsl:call-template>
      </table>
    </xsl:otherwise>
  </xsl:choose>
</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>

<xsl:template name="dvt_1.rowview">
  <tr>
    <td class="ms-vb">
      <h3><xsl:value-of select="@URL.desc"/></h3>
    </td>
  </tr>
  <tr>
    <td class="ms-vb">
       <object id="scPlayer"  width="300" height="250" type="application/x-shockwave-flash" data="http://content.screencast.com/users/[pathtovideos]/media/{@VideoGUID}/mp4h264player.swf" >
        <param name="movie" value="http://content.screencast.com/users/[pathtovideos]/media/{@VideoGUID}/mp4h264player.swf" />
        <param name="quality" value="high" />
        <param name="bgcolor" value="#FFFFFF" />
        <param name="flashVars" value="thumb=http://content.screencast.com/users/[pathtovideos]/media/{@VideoGUID}/FirstFrame.jpg&amp;containerwidth=1283&amp;containerheight=877&amp;analytics=UA-24180918-1&amp;content=http://content.screencast.com/users/[pathtovideos]/media/{@VideoGUID}/{@VideoFilename}&amp;blurover=false" />
        <param name="allowFullScreen" value="true" />
        <param name="scale" value="showall" />
        <param name="allowScriptAccess" value="always" />
        <param name="base" value="http://content.screencast.com/users/[pathtovideos]/media/{@VideoGUID}/" />
        <iframe type="text/html" frameborder="0" scrolling="no" style="overflow:hidden;" src="http://www.screencast.com/users/[pathtovideos]/media/{@VideoGUID}/embed" height="877" width="1283" ></iframe>
      </object>
    </td>
  </tr>
</xsl:template>

<xsl:template name="dvt_1.empty">
  <table border="0" width="100%">
    <tr>
      <td class="ms-vb">
        There are no training videos available for this page.
      </td>
    </tr>
  </table>
</xsl:template>

Note that I’ve replace the actual path to the videos at ScreenCast.com with [pathtovideos] above. We had several columns in the video list that contained the bits of information that we needed for each video: @VideoFilename and @VideoGUID. If the videos were hosted elsewhere, the pieces of information you would need as well as the player specifics would undoubtedly be different.

By keeping things simple and list-based, we very rapidly (in a matter of hours) built out a robust help capability for the whole system. If need be, it can be extended to display other types of content on the help page, such as documents. We could also make the ASPX Page column a multi-select lookup if we wanted to be able to offer up the same help content for multiple pages within the same site.

All in all, we were pretty proud of this as a solution. Simple, yet elegant at the same time. That’s always a good feeling.