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.

8 Comments

  1. Hi Mark. Good stuff.
    I too use a very similar approach for application online help. I do not use a web part, but rather jQuery, jquery UI an XSL jQuery plugin and SPServices to show the help popup on each page… This takes the overhead burden out of having to include a webpart (even the javascript is loaded only when the user “clicks” on the help button).
    Instead of page name, I use the relative path to the page front the site URL (example: /Lists/Documents/Edit.aspx)… because the same topic may be displayed in multiple pages, I enclose each one in brackets (example: [page 1 URL][page 2 URL] etc). I also have a special token [ALL] that means the topic applies to all pages (example: basic information about navigation).

    My list has a few more columns:

    GROUPING – the value is used to group similar help topics. When displaying the topics I do so in an accordion styled UI with each expandable being the GROUPING.

    ROLE – this is a pre defined picker having the following as the allowed values. VIEW, CONTRIBUTE, FULL. I use these to further filter the help topics based on the user’s permission privilege. VIEW maps to someone that is a visitor. CONTRIBUTE maps to Members and is inclusive of the VIEW. FULL maps to Full Control and is inclusive of both previous values.

    One other advantage of having help defined in a table is that I can also reuse the topics through out the application as context sensitive help.

    Paul.

    Reply
      • Well done Marc!

        My first thought was exactly what Paul suggested – why not use Web services? We know that you master both DVWP and Web services, so I am thinking that a blog post where you explain when and why you choose one over the other would be very interesting.

        Reply
        • I guess the choice between DVWP and Web services boils down to whether you want server-side processing or client-side processing.

          For certain common / repetitive sections on the page (like help, menus, etc.) I would want to offload the work to the server, rather than fetching data separately and processing it at the client side.

          Reply
        • Christophe:

          There were two main reasons I decided to go with a DVWP-based approach over using script and the Web Services:
          * We didn’t have a good handle on the profile of the user machines, but we were fairly sure that there would be some which were very low end. I didn’t want to risk pushing to much processing demand onto them without having good data.
          * We wanted to get people familiar with the fact that there was a whole training site available to them. By opening a new browser window with the Training site’s chrome around it, we could effectively promote that content store.

          M.

          Reply
          • Thanks Marc, it makes sense.

            I’d still love to hear about your general criteria to help choose between the two :-) For example, one of my concerns these days is reuse, and how customizations behave when saved as site templates. My take is that Web services that rely on list names will be more flexible than DVWPs that rely on GUIDs. To take a specific example: how hard would it be to save this design, and package it to redistribute it as a help solution?

            Reply
            • Christophe:

              DVWPs can use list names rather than GUIDs by changing the ListID parameter to ListName. Conversely, Web Services calls can use GUIDs rather than list names.

              This solution required one DVWP in the master page, and several others in the page in the Training site. That makes it far more “packageable” than something which requires script or a DVWP in many pages.

              M.

              Reply

Have a thought or opinion?