Windows Live Quick Apps – Contoso Bicycle Club Part 2

latestrides_thumb Developer

Welcome back to the second part of our deep dive in the Windows Live Quick Apps Website featuring the Contoso Bicycle Club. In this part we’ll take a look at another custom user control found on the home page, the Latest Rides feed.

If you followed the first part of this deep dive then you will find a lot of things very similar with this control :-

<%@ Control Language=”C#” ClassName=”LatestRidesControl” %>

<div id=”latestRidesContent”>

<script runat=”server”>

int n = 1;

</script>

<!– setup the XML Datasource – this will query the RSS feed directly –>

<asp:XmlDataSource ID=”latestRidesDataSource” runat=”server” EnableCaching=”false”

DataFile=”<%$ AppSettings:LatestRidesFeed %>” XPath=”/rss/channel/item”></asp:XmlDataSource>

<table>

<asp:DataList ID=”latestRidesDataList” runat=”server” DataSourceID=”latestRidesDataSource”>

<ItemTemplate>

<tr class=”latestRideItem”>

<td valign=”top” class=”left”>

<a href=”javascript:showDiv($(‘contentPanel’), 100);updatePage(‘<%# XPath(“title”) %>’,'<%= latestRidesDataSource.DataFile %>’, <%= n%> ,'<%# HtmlProcessor.ExtractMapCid(XPath(“description”).ToString()) %>’,'<%# HtmlProcessor.ExtractPhotoAlbumFeed(XPath(“description”).ToString()) %>’)”>

<img width=”50″ height=”50″ alt=”<%# XPath(“title”) %>” src=”<%# HtmlProcessor.ExtractImageUrl(XPath(“description”).ToString()) %>” />

</a>

</td>

<td class=”right”>

<div class=”title”>

<a href=”javascript:showDiv($(‘contentPanel’), 100);updatePage(‘<%# XPath(“title”) %>’,'<%= latestRidesDataSource.DataFile %>’, <%= n++ %> ,'<%# HtmlProcessor.ExtractMapCid(XPath(“description”).ToString()) %>’,'<%# HtmlProcessor.ExtractPhotoAlbumFeed(XPath(“description”).ToString()) %>’)”>

<%# XPath(“title”) %>

</a>

</div>

<div class=”description”>

<%# HtmlProcessor.FirstLine(XPath(“description”).ToString()) %></div>

</td>

</tr>

</ItemTemplate>

</asp:DataList>

</table>

</div>

So let’s start at the top. The first thing you’ll see is a counter has been setup and initialized to 1. This serves the same purpose as the counter found in Events Control and simply gives each entry its own unique ID.

Next we set up the XML DataSource. This time however we’re point to a different entry in the web config file :-

<add key=”LatestRidesFeed” value=”http://contosobicycleclub.spaces.live.com/category/rides/feed.rss”/>

In the Event Control we pointed to the RSS feed gained from the Events category in the CBC Windows Live Spaces site, this time we’re pointing to the Rides feed. This feed will be used in more than one place in the sample web site but we’ll talk about it again when we get to that point in our deep dive. The XPath expression for our DataSource is the same as the one used the DataSource control in the Events Control. This is because the feed that is being distributed is based on a standard. You will find the same xml markup tags in every feed produced by Windows Live Spaces. This makes our jobs as developers easier.

XPath=”/rss/channel/item”

Here we are saying, from the root of the received feed, we are only interested in the xml that is encapsulated in the Item tag. The Item tag is found within the Channel tag which itself is found in the RSS tag under the root. The easy way to think about this is a folder hierarchy as you might find on your computer. An equivalent would be C:/Windows/System32. Here we’re looking for the contents of the System32 folder which is found under the Windows folder which is found under the root of the C: drive.

XPath expressions were designed this way so as to be easy to use and understand.

Next we start a table. This is being used purely for layout purposes as this control is slightly more complex than the Event Control and displays more data.

Within the table tag we start our repeating section using the DataList control. The DataList control gets its data from the XmlDataSource that we defined above.

We define a table row and a couple of table cells. As mentioned this is purely for presentation purposes. Within the first table cell we display an image.

<a href=” javascript:showDiv($(‘contentPanel’), 100);updatePage(‘<%# XPath(“title”) %>’,'<%= latestRidesDataSource.DataFile %>’, <%= n%> ,'<%# HtmlProcessor.ExtractMapCid(XPath(“description”).ToString()) %>’,'<%# HtmlProcessor.ExtractPhotoAlbumFeed(XPath(“description”).ToString()) %>’)”>

<img width=”50″ height=”50″ alt=”<%# XPath(“title”) %>” src=”<%# HtmlProcessor.ExtractImageUrl(XPath(“description”).ToString()) %>” />

</a>

Don’t worry, it looks way more complicated than it actually is.

First, we define a link and rather than the link taking us to new page or section we’re running some custom Javascript inside it. The first statement simply calls a Javascript function that un-hides a DIV section that is defined in the default.aspx page.

function showDiv(div, zIndex) {

div.style.visibility = ‘visible’;

div.style.zIndex = zIndex;

// Work around for Bugzilla Bug 187435 for Firefox on Mac

if(div.id.toLowerCase() == “directionspanel” || div.id.toLowerCase() == “textpanel” || div.id.toLowerCase() == “mainpanel”)

{

div.style.overflow = “auto”;

}

}

As you can see from this javascript function, we simply take a reference to the div and show it with a z-index that is also passed in.

The only thing slightly unusual about this is the call to this function :-

javascript:showDiv($(‘contentPanel’), 100);

The javascript function showDiv is expecting a reference to the actual Div, not the name of the name of the Div as you may guess from contentPanel being enclosed in quotes. This is actually using a short cut notation that is found in Asp.Net Ajax. The actual expression is “$(‘contentPanel’)”. This is equivalent of saying :-

Document.getElementById(‘contentPanel’)

Which would be a reference to the Div itself and not just the name of the Div.

Next we have a call to another JavaScript function. This function call is slightly more complex as it is a generic function for use by virtually every control on the page and therefore there are some parameters to it that are essentially optional, depending on where the function is being called from. So lets go through this step by step.

The first parameter in the updatePage function call is title of the blog post :-

<%# XPath(“title”) %>

Remember that we have already extracted the xml down the item level. Each Item is a blog post itself. So here we’re simply saying, give me the contents of what’s found in the Title Xml element in the Item.

The next parameter is the whole RSS feed itself :-

‘<%= latestRidesDataSource.DataFile %>’

Next we pass in the value of the counter that was defined at the top of the user control. This essentially gives each repetition its own ID.

The next call is to static member of a class file found in the Classes folder of the website :-

<%# HtmlProcessor.ExtractMapCid(XPath(“description”).ToString()) %>

We make a call to ExtractMapCid method, this returns an id for a virtual earth map collection that is taken from within the description of the blog post. What is this?

If you open up http://maps.live.com, in the top right of the page you’ll see a dropdown for Collections. This was a feature added to virtual earth V5. You can create your own collections or browse other peoples collections that they have made public. Each collection has its own unique ID.

Here is the code that we are calling :-

public static string ExtractMapCid(string html)

{

// Create the RegEx (conditions are find anything (including URLs) which have cid={THECIDVALUE}

Regex regex = new Regex(“;cid=(.*?)&amp”);

// Execute a search/match against the regex

Match match = regex.Match(html);

if (match.Groups.Count > 1)

return match.Groups[1].Value;

else return “”;

}

What we’re doing here is parsing through the contents of the Description XML tag looking for anything that matches “;cid=” and the cid value. This cid value is the unique ID given to collections within the maps.live.com application and extracting that value out to be passed into our updatePage javascript function.

The final parameter for out updatePage function call makes a call to another static method found in the HtmlProcessor class file :-

<%# HtmlProcessor.ExtractPhotoAlbumFeed(XPath(“description”).ToString()) %>

This time (as the function name explains) we are looking for photoalbums.

public static string ExtractPhotoAlbumFeed(string html)

{

// Create a regex to find a spaces photoalbum url.

Regex regex = new Regex(“http://(.*?).spaces.live.com/.*?PhotoAlbum.*?(cns!.*?)&”);

Match match = regex.Match(html);

if (match.Groups.Count > 2)

return string.Format(“http://{0}.spaces.live.com/photos/{1}/feed.rss”, match.Groups[1].Value, match.Groups[2].Value);

else return “”;

}

This is a snippet from one of the blog posts :-

Finished ride near some really good places to eat at London Bridge.<br></span> </div>

<div>

<p><a href=”http://maps.live.com/default.aspx?v=2&amp;cid=2BACE20A0AB578FB!180&amp;encType=1″>Map</a>

<p><a href=”http://contosobicycleclub.spaces.live.com/?_c11_PhotoAlbum_spaHandler=TWljcm9zb2Z0LlNwYWNlcy5XZWIuUGFydHMuUGhvdG9BbGJ1bS5GdWxsTW9kZUNvbnRyb2xsZXI$&amp;_c11_PhotoAlbum_spaFolderID=cns!19C180FDFB1C7EFF!138&amp;_c11_PhotoAlbum_startingImageIndex=0&amp;_c11_PhotoAlbum_commentsExpand=0&amp;_c11_PhotoAlbum_addCommentExpand=0&amp;_c11_PhotoAlbum_addCommentFocus=0&amp;_c=PhotoAlbum”>Photos</a></div><div>

As you can see it’s basically HTML markup. Within here you will notice there is a link tag pointing to a photo album that matches our regular expression :-

http://contosobicycleclub.spaces.live.com/?_c11_PhotoAlbum_spaHandler=TWljcm9zb2Z0LlNwYWNlcy5XZWIuUGFydHMuUGhvdG9BbGJ1bS5GdWxsTW9kZUNvbnRyb2xsZXI$&amp;_c11_PhotoAlbum_spaFolderID=cns!19C180FDFB1C7EFF!138&

The regular expression splits this down into 3 separate groups. The first group is the whole string above, the second is initial value found after http:// as defined by :-

http://(.*?)

And the third group is the cns number as defined in the regular expression by :-

(cns!.*?)

What we are really looking for is the second and third values. These are plugged into the return string :-

string.Format(“http://{0}.spaces.live.com/photos/{1}/feed.rss”,match.Groups[1].Value, match.Groups[2].Value);

And so what you actually end up with is this url :-

http://contosobicycleclub.spaces.live.com/photos/cns!19C180FDFB1C7EFF!138/feed.rss

In the middle of the link (anchor tag) an image is placed :-

<img width=”50″ height=”50″ alt=”<%# XPath(“title”) %>” src=”<%# HtmlProcessor.ExtractImageUrl(XPath(“description”).ToString()) %>” />

Following the same logic we have thus far, the alt parameter is defined as the title of blog and src parameter point to what is returned from the call to ExtractImageUrl, another static function call found in the HtmlProcessor class :-

public static string ExtractImageUrl(string html)

{

// Create a RegEx to find the Image

Regex regex = new Regex(“<img.*?src.*?=.*?”(.*?)””, RegexOptions.IgnoreCase);

// Execute the search/match.

Match match = regex.Match(html);

if (match.Groups.Count > 1)

return match.Groups[1].Value;

else return “”;

}

Once again we are using regular expression pattern matching to look for a specific value.

If you take a look at the source for the RSS feed, look towards the end of the first <item> tag, just before all the comments and you’ll see the following (even if you just look at the rss feed itself in IE7, you’ll see a picture attached to the end of each post) :-

img src=”http://storage.live.com&amp;#47;items&amp;#47;19C180FDFB1C7EFF&amp;#33;131&amp;#58;thumbnail

This is what we’re extracting out, the value for the source of this image tag.

The contents of the second table cell are exactly the same as the content for the Event Control (see previous article for details). The only difference between the two is that the second and third parameters for the updatePage function call actually have values this time (as explained above for the first table cell).

And there you have it, the Latest Rides user control from the Contoso web site. It’s almost identical to the Events user control, but this time we looked at a different feed and extracted out slightly more information from the description tag to enable us to display images (and also information that will be used by other controls on the page but isn’t really apparent in the display of the Latest Rides control).

So let’s extract what we’ve learned here and build upon our very basic page from the previous article.

Open up the website you created from the previous article and open the default.aspx page.

What we’ll be doing is simply adding an image next to the title for each blog post and display the first image we find that is contained within the blog in this spot.

So here is the default page from yesterday :-

<%@ Page Language=”C#” AutoEventWireup=”true” CodeFile=”Default.aspx.cs” Inherits=”_Default” %>

<!DOCTYPE html PUBLIC “-//W3C//DTD XHTML 1.0 Transitional//EN” “http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd”>

<html xmlns=”http://www.w3.org/1999/xhtml” >

<head runat=”server”>

<title>Untitled Page</title>

</head>

<body>

<form id=”form1″ runat=”server”>

<div>

<asp:TextBox ID=”Feed” runat=”server” Columns=”60″ />

<br />

<asp:Button ID=”btnSubmit” runat=”server” OnClick=”btnSubmit_Click” Text=”Get Feed” />

<br />

<br />

<asp:XmlDataSource ID=”source” runat=”server” XPath=”/rss/channel/item” DataFile=”http://msnwindowslive.spaces.live.com/feed.rss” />

<asp:Repeater DataSourceID=”source” ID=”DisplayFeed” runat=”server”>

<ItemTemplate>

<div>

<a href='<%# XPath(“link”) %>’>

<%# XPath(“title”) %>

</a>

<hr />

<%# GetFirstXCharacters(XPath(“description”).ToString(), 900)%>

<br />

<br />

</div>

</ItemTemplate>

</asp:Repeater>

</div>

</form>

</body>

</html>

Now all that we need to do is insert the following image tag just before the <%# XPath(“title”) %> that is found in the link :-

<img width=”50″ height=”50″ alt=”<%# XPath(“title”) %>” src=”<%# ExtractPics(XPath(“description”).ToString()) %>” />

Now go into your code behind and add the ExtractPics static method as so :-

public static string ExtractPics(string description)

{

Regex regex = new Regex(“<img.*?src.*?=.*?”(.*?)””, RegexOptions.IgnoreCase);

Match match = regex.Match(description);

if (match.Groups.Count > 1)

return match.Groups[1].Value;

else return “”;

}

This is basically the same pattern matching expression that we discussed above in the article. And that’s all there is to it.

In the next part of this deep dive we’ll be taking a look at the main body of the page which is really where the fun starts.