Training Site Template: Part 3 - Custom Views and Forms

SeanHi everyone,

This is Sean (I know, finally!), blogging to you with the next part of the Employee Training Scheduling and Materials site template walkthrough. I realize it's taken much too long to get the rest of this information out here - my hope is that you'll find these missing pieces useful in completing your picture of the template. Since it's been a while, you may want to refresh yourself on the lists and workflows that are in play on the site already, and perhaps review the full list of Application Templates for Windows SharePoint Services 3.0.

List of lists You may have noticed in your exploration of the Lists folder that a large number of the forms in this template are unghosted (the little Unghosting (i) next to the file indicates it's different from the version in the site definition). In particular, the so called 'big 3' forms - new, edit, and display - for Courses, Registrations, and Past Registrations have all been customized.

What we did with these pages was to add a Data Form Web Part (DFWP) that essentially replaces the default SharePoint List View Web Part (LVWP) and List Form Web Part (LFWP) with custom logic, built up using SPD's design surface.

This has some implications on how future customizations to the site will work: for one, the DFWP is not schema agnostic, and so if you alter the underlying list schema, these views and forms won't pick up on those changes (whereas standard LVWP/LFWP pages would). On the other hand, out of box SharePoint views don't run on XSLT, so it's hard to go super custom on them beyond just sorting, grouping, and filtering. An important one for this app is that there isn't an easy way to show and hide certain fields, precisely because these CAML views are schema agnostic.

Admittedly there are other ways to accomplish this - content types being a good example - but given the scenarios and the wealth of customizations we can apply to a DFWP, it seemed like a reasonable way to go.

Let me show you what I mean.

Help! I can't look into the Registrations list!

Registrations is a shared list across all Courses, managed largely by workflow. But since using these workflows requires that the users on the site are Contributors, you don't want them manually mucking about in the Registration list. Using item-level permissions (List Settings > Advanced Settings) to restrict edits to only your own items is good, but not sufficient, because I could change the course ID that my registration looks up to and basically hack my way into an already-full class.

Site administrators can work around what I'm about to show you, but for the typical Contributor coming to the site, we removed it from the quick launch bar and took out the edit links in the All Items view. Moreover, if you try to add a new item manually or edit an existing one by crafting the appropriate URL, you'll be stopped in your tracks:

image

What's going on here? Crack open Registrations/NewForm.aspx in SharePoint Designer and take a look:

Registration new form in SPD

SharePoint gets a little temperamental when you delete the LFWP here or on any of the other big 3 form pages, so instead you'll want to hide it by doing the following:

  1. Right-click on the web part > Web Part Properties...
  2. Scroll down and expand the Layout section
  3. Make sure both Close the Web Part and Hidden are checked
  4. Click OK to close the dialog

Then you can simply type a message explaining where the missing view is. Do similarly on DispForm.aspx and EditForm.aspx, and you've effectively quarantined the list from manual edits. By the way - the admin workaround is to just create a new view against the list, but at least this way we've stopped Contributors from getting an unfair advantage in the registration process.

Beyond just locking down the out of box view, in the Courses list, we'll add a DFWP below the closed web part to house our custom functionality. Here's where the ability to show and hide certain fields in the form becomes useful.

Remaking the Courses list forms

Courses new form in SPDTake a quick look in Courses/NewForm.aspx and you'll see a similar sight to the new form in the Registrations list, the difference being there's another lookalike form below the closed LFWP. This is a DFWP acting as a new form - to recreate it, follow the steps above to close the LFWP, then:

  1. Place your insertion pointer right below the closed web part
  2. Click Insert > SharePoint Controls > Custom List Form...
    1. List or document library to use for form: Courses
    2. Content type to use for form: Event
    3. Type of form to create: New item form
  3. Click OK to see the new form

Yet this isn't quite what you see in the browser - where did these extras fields come from? Well, All Day Event, Recurrence, and Workspace are coming from the Event content type that Courses is based on. Filled Seats we added in part 1. However, we don't need the instructors to fill in these fields in the base scenario (though certainly you can extend the template to make sure of them) - so we just hide them from the view. A little CSS does the job:

  1. Pop open split view by clicking the Split button in the bottom of the design surface
  2. Click on the header for All Day Event - this should move your caret in code view
  3. Locate the <tr> tag above the controls (the tag selector can help here)
  4. Add a style to the <tr> tag so it now reads: <tr style="display: none">
  5. Repeat for Recurrence, Workspace, and Filled Seats
  6. Save the page and press F12 to view it in your browser

The fields are now out of sight, but because they're still embedded in the form the default values get plumbed back into the list - works out great for fields like Filled Seats where 0 is the only value that really makes sense from the start. You also can follow the same set of steps to redo the edit and display forms, hiding any fields that aren't relevant for your implementation. Just make sure to change the type of form you're creating in substep 3 above.

The Courses mega-view

Customizations to the Courses display form Okay, after hiding form fields the first few times, it's not all that exciting. How about some more information at my fingertips? If I'm looking at the details of a particular course, there's probably a few other things I'm wondering.

  • Can I peek at the course materials?
  • Who else am I taking the class with?
  • Is there room for me? How do I sign up?

Fortunately all this can be done by progressively customizing Data Form Web Parts using the SPD design surface. I'll walk you through how to do each of these as it's presented in the template.

Window shopping

Remember the Course Materials document library we created? The idea is to have instructors upload their slides, etc. here, and set a lookup field that points back to the particular course. That makes sifting through the document library easier, but why not just show this in context with the course details? We can modify the DFWP we just created in Courses/DispForm.aspx to house a subview of the Course Materials library with this filter already applied.

Linked data sources wizardHere's how to do it:

  1. With DispForm.aspx open, click into the DFWP, then switch to the Data Source Details task pane

  2. Scroll down and click Related Data Sources > Link to another data source... , dismiss the warning

  3. Expand SharePoint Libraries and add Course Materials, then hit Next >

  4. Select the second option (join), then hit Finish

  5. Click into the last row in design view (if you've followed along, it should be Workspace)

  6. Go to Table > Insert > Row Below, and you can perform the following optional steps:

    1. In the new row, type Course Materials in the first column, then highlight it and go to Insert > Hyperlink...
    2. Use ../../Course Materials/Forms/AllItems.aspx as the URL of your link, click OK
  7. Move your cursor over to the second column, then flip over to code view

  8. Insert the following snippet into the currently selected <td>:

    <xsl:call-template name="GetCourseMaterials">
        <xsl:with-param name="Rows" select="/dsQueryResponse/Course_Materials/Rows/Row[@Course = ddwrt:AutoNewLine(/dsQueryResponse/Courses/Rows/Row/@Title)]" />
    </xsl:call-template>

  9. Move down to the nearest </xsl:template> tag within the main <xsl:stylesheet>

  10. Insert the following snippet directly below that </xsl:template>:

    <xsl:template name="GetCourseMaterials">
        <xsl:param name="Rows" />
        <xsl:choose>
            <xsl:when test="count($Rows) &lt;= 0">
                None provided <em>(instructors, <a href="../../Course Materials/Forms/Upload.aspx">upload here</a>)</em>
            </xsl:when>
            <xsl:otherwise>
                <ol>
                <xsl:for-each select="$Rows">
                    <li>
                        <a href="{@FileRef}" mce_href="{@FileRef}">
                            <xsl:value-of select="@Title" />
                        </a>
                    </li>
                </xsl:for-each>
                </ol>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>

  11. Save and hit F12 to view it in your browser

The custom XSLT isn't anything too fancy, just helps to isolate the subview logic into a separate template. You can accomplish the same thing by inserting a subview from the Data Source Details task pane, applying a filter based on the course title, and doing a little conditional formatting to handle the no matching materials case. Note - some users have reported design time errors due to the use of ddwrt functions - these should render fine in the browser though; a simple workaround would be to remove it until you're ready to save, then replace it. Also, be sure that your data source matches the one provided in this template, e.g. in /dsQueryResponse/Courses/Rows/Row/@Title, if you've added additional data sources to the page you'll have to change Courses here to match.

Finding your classmates

Data source libraryTo get a list of registrants for a particular course, you'll actually need to look in two lists - Registrations for pending classes, and Past Registrations for finished classes. We need to do this since the same view page is going to serve both types of courses. An aggregate data source can help us do exactly this:

  1. In SPD, open the Data Source Library by going to Data View > Manage Data Sources...
  2. Expand the Linked sources category and click Create a new Linked Source... , then Configured Linked Source... in the dialog
  3. In the next screen, pick Registrations, then Past Registrations, then hit Next >
  4. Select the first option (merge), then hit Finish
  5. In the General tab, name it All Registrations, then click OK to dismiss the dialog

You should now see All Registrations listed in the data source library. Click it and choose Show Data from the menu to inspect the schema of your merged data source. If you scroll down and find Created By, you can then click and drag it onto the page to insert a DFWP against Created By for items in both lists. (Recall that Created By is used to identify a particular registrant, based on the workflow that performs attendee registration.)

Filter criteria for the DFWP This'll get us the basic view, but not filtered down to just the course being shown on this page. We'll add a filter to key off the ID parameter passed in via the query string (another benefit of reusing the existing DispForm.aspx), by doing the following:

  1. Select the DFWP in design view, then click the OOUI image chevron to drop down a menu
  2. Click Filter: at the top, then in the section that reads Click here to add a new clause...
  3. Choose the following options: Field Name = Course ID, Comparison = equals, Value = Create a New Parameter...
    1. Name the new parameter CourseID
    2. Set the Parameter Source = Query String, Query String Variable = ID

From there, a few little improvements will get this view to mirror the one in the template:

  • If you prefer the numbered list style, with the DFWP selected hit the OOUI and choose to Change Layout... . Scroll down until you see the preview with the numbered list in it (5th from the bottom?)and click OK. Your view should update to the new style.
  • Also, if you want the "Courses Registration List" header to show up, right click the DFWP and select Web Part Properties... , then under the Appearance section change the Title and set the Chrome Type to Title Only.
  • To customize the text shown when nobody has registered, select the DFWP and drop down the OOUI, choose Data View Properties... and check off/type "Nobody has registered for this course." under Display text if no matching items are found.
  • You also probably noticed that the person field markup was escaped on the way back, so you can see a bunch of HTML goo in the view. To get it to render properly, select that xsl:value-of element, then hit the OOUI and choose to Format as: Rich Text. You'll get a warning to beware potential cross-site scripting attacks, but since the HTML coming back is SharePoint markup around a user field, you should feel comfortable dismissing the warning. The field should now be formatted properly.

Save the page, then browse to the site and drill into the details of one of the courses. You'll now see a view below the course details that shows who's registered for the class. Of course, this'll probably be empty until you implement the register and unregister pages. Let's go ahead and hammer out this last piece - we're almost there!

Sign me up

So you found the course you want, and you want in. Pushing the 'My Registration' button is your one-stop shop to getting on and off the seating list. That one-button registration leads us to another place where the DFWP is a rock star - enabling page flow scenarios using SharePoint workflow. By passing some interesting parameters to the next page, we can direct the flow of data traffic within the site, and ultimately set up cues for workflow to take care of the seating logic behind the scenes.

What's left for us to do is create Register.aspx and Unregister.aspx, which given the right parameters will look like one of these:

Register RegisteredNot registered Unregister 

To get here, you'll need to wire up a link in Courses/DispForm.aspx to Registrations/Register.aspx. All we need to do is pass enough context to the new page via the query string, and we'll let it take care of the rest.

  1. Locate <xsl:template name="dvt_1.body"> in the main DFWP

  2. Below the following <xsl:param>, add this code snippet:

    <xsl:variable name="IsUnlimitedSeating" select="$Rows[1]/@Total_x0020_Seats = 0" />
    <xsl:variable name="AvailableSeating" select="$Rows[1]/@Total_x0020_Seats - $Rows[1]/@Filled_x0020_Seats" />
    <xsl:variable name="IsAvailableSeating" select="$IsUnlimitedSeating or $AvailableSeating &gt; 0" />
    <xsl:variable name="RegisterURL" select="concat('../../Lists/Registrations/Register.aspx?CourseID=',$Rows[1]/@ID,'&amp;CourseTitle=',$Rows[1]/@Title,'&amp;CourseStartTime=',$Rows[1]/@EventDate,'&amp;CourseEndTime=',$Rows[1]/@EndDate,'&amp;IsAvailableSeating=',$IsAvailableSeating)" />

  3. Locate the two <td class="ms-toolbar" nowrap=""> tags - this is where the toolbar buttons for the fom reside

  4. In each, below the <td> with 99% width, add the following:

    <td class="ms-toolbar" nowrap="">
        <a onclick="GoToLink(this); return false;" target="_self">
            <xsl:attribute name="href"><xsl:value-of select="$RegisterURL" /></xsl:attribute>
            <input type="button" class="ms-ButtonHeightWidth2" value="My Registration" name="btnFormAction1" />
        </a></td>

  5. Save the page

From here, create two new ASPX pages (File > New > ASPX) called Register.aspx and Unregister.aspx. In each of these I'll leave it to you to copy the appropriate XSLT from the site template, but here's the gist of what's going on:

  • Start with a custom new form against the Registrations list
  • Set up internal parameters (OOUI > Parameters... ) for the DFWP based on the data available in the query string
  • Use these parameters to construct variables in the XSLT
  • Branch into different <xsl:template>s by testing the variables

The useful trick employed by these pages is to write different defaults into the form data, based on query string parameters. You can do this pretty generically with DFWPs bound to SharePoint lists by following these steps:

  1. Click on the control you're interested in setting a default for, it should be of type <SharePoint:FormField>
  2. Drop down its OOUI > Format as: Text Box to replace it with a standard <asp:Textbox>
  3. Switch over to code view and add a value="" attribute to the TextBox tag - you can evaluate XSLT by wrap your expression in curly braces, e.g. value="{$var}"
  4. Use our earlier trick to hide it from being viewed by the end user browsing the page
  5. Save the page and hit F12 to preview the results in your browser

It's not foolproof and certainly prone to tampering, but for the average user it's pretty effective at guiding them down the golden path for your application.

That about covers it for the custom views and forms in this app - thanks for sticking it out! While I didn't cover the non-DFWP views used in the training site template, you can easily reconstruct these yourself on the web by going to List Settings > View Name and using the fields, filters, grouping, etc. that you see as a reference guide. Start creating your new view by going to Actions > Create View - if you're working directly off the template, it's often easier start from an existing view that's close to what you want.

In the next installment we'll make sense of the customizations to default.aspx and how to build out the dashboard of the site. I'll also talk about some of the roundouts you might want to build off of the base template, and how to package up and re-deploy your changes into a new site template of your own.

Thanks,
Sean