Note
This post originally appeared on my MSDN blog:
Since
I no longer work for Microsoft, I have copied it here in case that blog
ever goes away.
In
part 4 of this series, I provided a sample SharePoint Server 2010 solution
solution based on Dan Cederholm's Tugboat sample
site (from his most recent book, Handcrafted
CSS : More Bulletproof Web Design).
In that sample, however, all of the content on the home page of the site is rendered
as static HTML. In other words, it doesn't truly leverage the power of SharePoint
in order to make the site dynamic based on content stored throughout the site.
In this post, I'll show you how to take a small part of the home page (specifically
This Week's Specials) and render it from items specified in a SharePoint
list.
In case you aren't yet familiar with the Tugboat sample site, the following screenshot
shows the site home page.
Figure 1: Tugboat - Home page
When I put on my "SharePoint Architect" hat and look at the content in the This
Week's Specials section, I see a SharePoint list with columns like
Title (e.g. "Fisherman's Brew"), Price (e.g. "$9.98
/ lb."), and Rollup Image (e.g. "/PublishingImages/boat.jpg").
Depending on the size of the fictitious Tugboat Coffee company, we might choose
to use the Business Connectivity Services in SharePoint 2010 to pull this data from
some external system. However, for the sake of this sample, let's assume that Tugboat
isn't anywhere near being considered a threat to Starbucks, and we want to start
out by managing the "weekly specials" using a simple SharePoint list.
Let's start by designing the custom SharePoint list used to specify the data for
the weekly specials.
My decision to use the Title and Rollup Image
columns should be relatively straightforward. When you create a custom list in SharePoint,
you get a Title column by default (so it just makes sense to use
that for the name of the product). Similarly, the Publishing Image
column type (used for the Rollup Image column) provides the best
user experience for selecting an image from the SharePoint site (as opposed to creating
a custom column using the Hyperlink or Picture column type).
However, we need to think a little bit about the Price column.
We could certainly choose to add a custom column using the Single line of text
column type, which would contain values like "$9.98 / lb." However, besides
putting an unnecessary burden on the people maintaining the data in the list --
because they would need to explicitly type in the currency symbol and unit of measure
when adding new items to the list -- using a Single line of text
column type for the price wouldn't really be leveraging the full power of SharePoint.
It would also allow for mistakes like:
- "9.98 / lb." (no currency symbol)
- "$9.98" (missing " / lb." from the price)
- "$9.98/lb." (no spaces)
- "$9.98 /lb." (inconsistent spacing)
- "$9.98 / lb" (missing period on the abbreviation)
- "$9.98 / bl." (yikes, what is a "bl."?!)
Therefore, let's instead break the price into two parts: Unit Price
and Unit of Measure. We can then use a calculated column for
Price (in order to concatenate the values specified in the other two
columns).
Data for "This Week's Specials"
Title
|
Unit Price
|
Unit of Measure
|
Price
|
Rollup Image
|
Fisherman's Brew
|
$9.98
|
/ lb.
|
$9.98 / lb.
|
/PublishingImages/boat.jpg
|
Boathouse Bold
|
$12.50
|
/ lb.
|
$12.50 / lb.
|
/PublishingImages/ropes.jpg
|
Deadly Decaf
|
$7.49
|
/ lb.
|
$7.49 / lb.
|
/PublishingImages/fame.jpg
|
Here's a breakdown of the custom SharePoint list:
- Name: Specials
- Description: Items in this list appear in the "This Week's Specials" section
of the site home page.
- Columns:
- Title (Single line of text, Required)
- Unit Price (Currency, Required)
- Unit of Measure (Choice, Required)
- Choices:
- / lb. (default value)
- ea.
- Price (Calculated)
- Formula:
- =DOLLAR([Unit Price]) & " " &[Unit of Measure]
- Rollup Image (Publishing Image, Required)
- Created By (Person or Group)
- Modified By (Person or Group)
Important
Even though the Unit Price column is defined using the Currency
type and specifies a Currency format of $123,456.00
(United States), you must specify the DOLLAR function
in the formula for the calculated column. Otherwise, the values in the Price
column would not appear as expected (e.g. "12.5 / lb.").
Note
I chose to name the list "Specials" instead of "Weekly Specials" for a reason. If
this were a real world solution, then there would be a significant chance the business
rules would change over time (for example to show "Today's Specials" or "This Month's
Specials"). By simply naming the list "Specials", we can avoid tightly coupling
the implementation to the current business rules.
Assuming you are proficient in SharePoint, within a matter of a few minutes you
should be able to create the list and populate the sample data shown in the following
screenshot.
Figure 2: Specials list
The next step is to render the items in the list using the semantic markup previously
specified using static HTML. In other words, instead of rendering the SharePoint
list items in a table, we need to render them using an ordered list (along with
the corresponding CSS class names):
<h2>This Week's Specials</h2>
<ol class='specials group'>
<li class='group'>
<div class='special'>
<div class='special-img'>
<a href='#'>
<img src='/PublishingImages/boat.jpg' alt='coffee' />
<span><strong>Fisherman’s Brew</strong>
<em>$9.98 / lb.</em></span></a>
</div>
</div>
</li>
<li class='group'>
<div class='special'>
<div class='special-img'>
<a href='#'>
<img src='/PublishingImages/ropes.jpg' alt='coffee' />
<span><strong>Boathouse Bold</strong>
<em>$12.50 / lb.</em></span></a>
</div>
</div>
</li>
<li class='group third'>
<div class='special'>
<div class='special-img'>
<a href='#'>
<img src='/PublishingImages/fame.jpg' alt='coffee' />
<span><strong>Deadly Decaf</strong>
<em>$7.49 / lb.</em></span></a>
</div>
</div>
</li>
</ol>
Start SharePoint Designer 2010 and open the site containing the Specials
list. In the Site Objects pane, click Site Pages.
In the ribbon, click Web Part Page and then select the first page
layout (i.e. the layout with exactly one Web Part zone) to create a new page (Untitled_1.aspx).
Click the new page to view the settings for the page. In the Customization
section, click Edit file.
In the page editor, in the Insert tab on the ribbon, click
Data View and then click Specials to insert a new
Web Part. Select the List View Web Part, click the Design tab on
the ribbon, click Customize XSLT, and then click Customize
Entire View.
Switch to the Code view for the page and locate the <WebPartPages:XsltListViewWebPart>
element. Right-click the
<xsl:stylehsheet>
element within the
<xsl>
element for the Web Part, click Select Tag, and then delete the
default XSL stylesheet.
Note that the XsltListViewWebPart provides a
<dsQueryResponse>
XML document containing the items from underlying the SharePoint list. Each list
item is represented as a row:
<dsQueryResponse ViewStyleID="" BaseViewID="1" TemplateType="100" RowLimit="30">
<Rows>
<Row ID="1"
PermMask="0x7fffffffffffffff"
Title="Fisherman&#39;s Brew"
UnitPrice="$9.98" UnitPrice.="9.98000000000000"
UnitOfMeasure="/ lb."
Price="9.98 / lb."
PublishingRollupImage="<img alt="" src="/PublishingImages/boat.jpg" style="border:px solid" />" />
<Row ID="2"
PermMask="0x7fffffffffffffff"
Title="Boathouse Bold"
UnitPrice="$12.50"
... />
<Row ID="3"
PermMask="0x7fffffffffffffff"
Title="Deadly Decaf"
... />
</Rows>
</dsQueryResponse>
Tip
To view the "raw" XML document, insert the
XSLT identity transform into the
<xsl> element of
the
XsltListViewWebPart, save the page, and then browse to the
page in Internet Explorer.
At this point, I recommend using Visual Studio to create two files (e.g. Specials.xml
and Specials.xslt) for development purposes. Paste the data that you want to transform
into the XML file (i.e. the <dsQueryResponse>
document). Then iterate on the XSLT file until you get the final HTML that you are
looking for.
Since we know that we want to generate HTML from the XML document, start by specifying
the corresponding output element and a template that simply emits a heading:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
exclude-result-prefixes="msxsl">
<xsl:output method='html' indent='yes'/>
<xsl:template match='dsQueryResponse'>
<h2>This Week's Specials</h2>
</xsl:template>
</xsl:stylesheet>
Next, enhance the dsQueryResponse template to output an ordered
list. Add a second XSL template to render a list item element containing the value
of the Title field for each "row" of data (in other words, for
each SharePoint list item):
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
exclude-result-prefixes="msxsl">
<xsl:output method='html' indent='yes'/>
<xsl:template match='dsQueryResponse'>
<h2>This Week's Specials</h2>
<ol class='specials group'>
<xsl:apply-templates select='Rows/Row'/>
</ol>
</xsl:template>
<xsl:template match='Row'>
<li>
<xsl:value-of select='@Title'/>
</li>
</xsl:template>
</xsl:stylesheet>
At this point, the HTML rendered by the XsltListViewWebPart is:
This Week's Specials
- Fisherman's Brew
- Boathouse Bold
- Deadly Decaf
As you can see, we need to tweak the XSLT a little bit in order to render special
characters as expected (such as an encoded apostrophe). This is simply a matter
of adding the disable-output-escaping
attribute:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
exclude-result-prefixes="msxsl">
<xsl:output method='html' indent='yes'/>
<xsl:template match='dsQueryResponse'>
<h2>This Week's Specials</h2>
<ol class='specials group'>
<xsl:apply-templates select='Rows/Row'/>
</ol>
</xsl:template>
<xsl:template match='Row'>
<li>
<xsl:value-of select='@Title' disable-output-escaping='yes'/>
</li>
</xsl:template>
</xsl:stylesheet>
Now that we have the basic implementation working, let's replace the content of
the <li>
element with the actual markup from
the static HTML, and replace the various pieces of content with corresponding placeholders:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
exclude-result-prefixes="msxsl">
<xsl:output method='html' indent='yes'/>
<xsl:template match='dsQueryResponse'>
<h2>This Week's Specials</h2>
<ol class='specials group'>
<xsl:apply-templates select='Rows/Row'/>
</ol>
</xsl:template>
<xsl:template match='Row'>
<li class='group'>
<div class='special'>
<div class='special-img'>
<a href='#'>
<xsl:value-of select='@PublishingRollupImage'
disable-output-escaping ='yes'/>
<xsl:text> </xsl:text>
<span>
<strong>
<xsl:value-of select='@Title' disable-output-escaping ='yes'/>
</strong>
<xsl:text> </xsl:text>
<em>
<xsl:value-of select='@Price'/>
</em>
</span>
</a>
</div>
</div>
</li>
</xsl:template>
</xsl:stylesheet>
At this point, our HTML is very close to what we want it to be:
Obviously the image URLs are invalid (outside the context of the SharePoint site
-- due to server-relative URLs), but that's okay...the images should appear as soon
as we view this within the actual SharePoint site.
However, there is still one minor discrepancy between the dynamic HTML generated
using the above XSLT and the original static HTML created by Dan. The problem is
that the third list item needs to have an additional CSS class name specified (in
other words, for the last item in the list we need to render
<li class="group third">
instead of
<li class="group">
).
Fortunately, this is very easy to achieve using a little more XSLT. Here is the
final version of the XSL stylesheet that I developed to render items in the
Specials list to match the original HTML specified by the Web designer
(Dan):
<xsl:stylesheet version='1.0'
xmlns:xsl='http://www.w3.org/1999/XSL/Transform'
xmlns:msxsl='urn:schemas-microsoft-com:xslt'
xmlns:ddwrt2='urn:frontpage:internal'
exclude-result-prefixes='msxsl'>
<xsl:output method='html' indent='yes'/>
<xsl:template match='dsQueryResponse'>
<h2>This Week's Specials</h2>
<ol class='specials group'>
<xsl:apply-templates select='Rows/Row'/>
</ol>
</xsl:template>
<xsl:template match='Row'>
<li>
<xsl:choose>
<xsl:when test='position() = 3'>
<xsl:attribute name='class'>group third</xsl:attribute>
</xsl:when>
<xsl:otherwise>
<xsl:attribute name='class'>group</xsl:attribute>
</xsl:otherwise>
</xsl:choose>
<div class='special'>
<div class='special-img'>
<a href='#'>
<xsl:value-of select='@PublishingRollupImage'
disable-output-escaping ='yes'/>
<xsl:text> </xsl:text>
<span>
<strong>
<xsl:value-of select='@Title' disable-output-escaping ='yes'/>
</strong>
<xsl:text> </xsl:text>
<em>
<xsl:value-of select='@Price'/>
</em>
</span>
</a>
</div>
</div>
</li>
</xsl:template>
</xsl:stylesheet>
Copy this into the the <xsl>
element for the Web Part (using SharePoint
Designer) and save the page. Then view the page in Internet Explorer to verify that
it renders the expected HTML.
Once you have a test page rendering the SharePoint list items using the HTML you
want (rather than the out-of-the-box table-based layout), you can export the Web
Part to a .webpart file and subsequently import it onto the home page (at which
point, the custom Tugboat CSS rules will be applied and the content should exactly
match the static HTML previously used to render This Week's Specials).
There's still quite a bit I want to cover on this topic, but I need to move on to
my "day job". In my next post, I'll show you how to programmatically configure the
Specials list (yep, you guessed it...upon feature activation),
add some sample items to the list, and replace the static HTML content on the home
page with an instance of the XsltListViewWebPart configured with
the custom XSLT.
There are also some "bugs" in this new solution. For example, what happens if there
are more than three items in the Specials list? Don't worry, this
is easy to fix -- thanks to the power of SharePoint.
Stay tuned...
Update (2011-05-02)