Share on FacebookDemo source links (big download warning)
Links to this demo project, which is created from MVC Storefront - (Updated: link to the demonstration page)
MVC Storefront. is an ecommerce demo and is a perfect starting place for Silverlight enhancement - you can get help for MVC Storefront here: http://www.codeplex.com/mvcsamples
This html/silverlight demo is built as it is to enable the site to conform with a Web standards-based approach. On the UI layer, conforming to Web standards means 100% separation of presentation from content and structure, as well as the scripting behavior of UI elements. On the back end, this means limiting the mixing of UI code in the Web applications and CMS code that may need periodic updates, it means applying the same strict separation as to any other static screen.
There’s a fine line here, at the same time we need to avoid the appearance of "cloaking". "Cloaking" is the tactic of detecting search engine spiders when they visit and varying the HTML code specifically for the spiders in order to improve rankings. This is only acceptable in a very limited use: namely, as a way of simplifying search engine unfriendly links. If you are in any way selectively modifying the page content, this is nothing less than a bait-and-switch. Search engines have undercover spiders that masquerade as regular visitors to detect such unscrupulous behavior.
In this demo, The Silverlight control will be responsible for both retrieving it ‘s content from the server as well as hiding the indexable content using CSS. By having the Silverlight menu hide the indexable menu we have just solved the problem of how to know if the user has Silverlight Installed and how to know if the user has Javascript enabled.
Let’s look at the Silverlight project: I’m going to start off by opening an mvc project that I downloaded from Codeplex, the MVC Storefront. This is an ecommerce demo and is a perfect starting place for Silverlight enhancement.
The requirements for the menu are this: If the client doesn't support Silverlight ( bots, clients who haven't installed it yet, older browsers, etc.) - keep the page in pure html. If the client does support Silverlight, load this menu from the exact same source that the MVC view menu is using.
As stated previously, the Silverlight control is constructed in such a way that it will – if created – retrieve it’s contents from the server and after formatting it’s self – hide the html version. Once we have our Silverlight, we don’t really give control back to the html and that avoids any further delays to the user.
Let’s look at how this is setup. Here is the MVC Storefront application. Let’s have a really quick overview of how an MVC app is constructed. First, Global.asax file has code in application startup to assign controller classes to specific urls. All the controller files are located in folder: Controllers. When a request for one of the assigned urls is received at the server, that controller is called along with the default action. In our case, the MVC storefront default page goes into the CatalogController,and uses the function of Index. Index method will gather the menu details into a list of type CatalogData and pass that list over to the View also called Index - which will use that data to present the user page. All the view files are located in the folder called View. So, we can find our Index view in folder View/Catalog/Index.aspx. Index view renders a control called CategoryList.ascx which presents the CatalogData list in the form of an html menu. Here’s what this page looks like.
<div id="sl-categoryNavigation" ></div>
<div id="categoryNavigation" class="categoryNavigation">
<%foreach (Category parent in ViewData) { %>
<h3><%=parent.Name %></h3>
<ul>
<%foreach (Category child in parent.SubCategories) { %>
<li>
<%=Html.ActionLink<CatalogController>(x=>x.Index(parent.Name,child.Name),child.Name) %>
</li>
<%} %>
</ul>
<%} %>
</div>
View the source of this MVC page, and you’ll see clear, clean html. No mangled ID’s. No ViewState. When this page loads, this Index.aspx view, there is some javascript contained in an included file that comes into play. The javascript is contained in an included file to preserve the separation of presentation from content and structure. This script is is executed when the page is ready. The file is called menu.js, and it uses JQuery to simplify scripting.
We’ll see that in great detail in our next demo. For this demo, we’re going to skip that part and get right to the Silverlight.
What we’re interested in here is in how this Silverlight menu can be created from the same source as the html menu. That saves me as the programmer from a lot of maintenance during the life of this application.
Let’s look at the Silverlight control first. In our Silverlight project there’s a control called menu.xaml. In menu.xaml we have a Grid which contains a StackPanel which contains a ListBox. There is a template applied to the ListBox which enables another ListBox to be embedded within. All this xaml is written in such a way so as to make it skinnable.
We’re going to skip over the part where this Silverlight control contacts the server for it’s menu details and save that for later. Right now, we’ll look at what happens when fully formatted web.sitemap xml is ready to be turned into a menu. Here’s what the web.sitemap this control works with looks like.
<?xml version="1.0" encoding="utf-8" ?>
<siteMap >
<siteMapNode>
<siteMapNode url="" title="Apparel" description="Apparel" menugroup="Apparel" itemType="menuTitle" >
<siteMapNode url="/store/Apparel/Boots" title="Boots" description="Boots" menugroup="Apparel" itemType="menuItem" />
<siteMapNode url="/store/Apparel/Hats" title="Hats" description="Hats" menugroup="Apparel" itemType="menuItem" />
<siteMapNode url="/store/Apparel/Sunglasses" title="Sunglasses" description="Sunglasses" menugroup="Apparel" itemType="menuItem" />
</siteMapNode>
...
<siteMapNode url="" title="Featured Items" description="Featured Items" menugroup="Featured Items" itemType="menuTitle" >
<siteMapNode url="/store/Featured Items/All Items" title="All Items" description="All Items" menugroup="Featured Items" itemType="menuItem" />
</siteMapNode>
</siteMapNode>
</siteMap>
This data is passed into the code behind of menu.xaml first as a string. (open the menu.xaml.cs file and move to method setMenu())
In the setMenu method,
I’m going to build some business objects to represent the Silverlight menu and I’m going to use LINQ over Xml ( Xlinq )
One thing I ran into while working with LINQ over Xml that I’ll just mention real quick and that is: namespaces can complicate the code. And in my case, since it was my site map, talking to my server, I just removed the namespace which was in the first node. This saved me from writing some code to match namespaces.
To get the Xdocument to load up in my Silverlight control from my Sitemap XML, I created two new Business Object classes in my project. The first class represents a SiteMapNode. The second class represents a MenuGroup.
Here’s what the LINQ looks like which is going to parse the XML into an IEnumerable list of my sitemap objects, which are the menu items. Use the XDocument.Parse to get my xml string into an XDocument. And then I’m going to select from xdoc.Decendentants(“siteMapNode”) new SitemapBO objects – and load em up right there. The url property is assigned from the sitemap attribute “url”. The title is assigned from attribute title, and so on.
The sitemapbo class, which we have just made a list of is just a property bag which stores and organizes my XML for me.
SiteMapBO.cs:
public class SiteMapBO
{
public string url { get; set; }
public string title { get; set; }
public string description { get; set; }
public string menugroup { get; set; }
public string itemType { get; set; }
}
XLINQ:
xdoc = XDocument.Parse(sitemapXML);
IEnumerable<SiteMapBO> menuitems = from menuitem in xdoc.Descendants("siteMapNode")
select new SiteMapBO
{
url = (string)menuitem.Attribute("url"),
title = (string)menuitem.Attribute("title"),
description = (string)menuitem.Attribute("description"),
menugroup = (string)menuitem.Attribute("menugroup"),
itemType = (string)menuitem.Attribute("itemType")
};
Once I have this list of sitemapbo objects, I still need to organize it into a list of MenuGroup objects.
How I format the menu using "menuItem" and "menuTitle" of the sitemap.
What I want is to have my menu titles to be independently skinnable from my menu items. It gives me freedom, I can make my menu look special because I can touch each type of object within it with a skin. I have the ability to make this Silverlight something special and That's what the MenuGroups class is for. After getting a list of SiteMapBO's from the Xdocument, in the olden days, I would then re-traverse the list to load a new IEnumerable list of MenuGroups - and this is the list that gets bound to my ListBox.
Today, I’m able to do this using LINQ to Objects.
MenuGroup class consists of a Title property , and a list of SiteMapBO’s.
MenuGroups.cs:
public class MenuGroups
{
public string title { get; set; }
public List<SiteMapBO> itemsList { get; set; }
public MenuGroups()
{
itemsList = new List<SiteMapBO>();
}
}
Here’s the code that will load the list which will become my ItemsSource of the menu ListBox using LINQ to Objects, Framework V3.5:
List<MenuGroups> menus = new List<MenuGroups>();
if (menuitems.Count() > 0)
{
foreach (var menugroup in menuitems.Where(grp => grp.itemType == "menuTitle"))
{
MenuGroups mnu = new MenuGroups();
mnu.title = menugroup.title;
mnu.itemsList = menuitems.Where(p => p.itemType != "menuTitle" && p.menugroup == menugroup.menugroup).ToList();
menus.Add(mnu);
}
this.MenuList.ItemsSource = menus;
//the object is ready to show itself - this is javascript call.
HtmlPage.Window.Invoke("showSlMenu");
//here, we'll show ourself with an animation
this.showMyself.Begin();
}
Ok, at the end, then, when I’ve finished building the list of MenuGroup objects, I just set the ItemsSource of the MenuGrid Listbox to this list, menus. And the Styling takes care of the rest of it – that’s it, we’re done.
This is how I build this Silverlight menu control so that it is skinnable and that way I can re-use it in different projects.
In the XAML, notice that this solution allows for individual formatting of the header and the items.
This is the menu: (a stackpanel which contains an image and a listbox.)
<StackPanel x:Name="MenuGrid" >
<Image Source="images/bdlogo.png" Stretch="Fill"/>
<ListBox x:Name="MenuList" Style="{StaticResource menuTitleStyle}" >
</ListBox>
</StackPanel>
The listbox name is Menulist, and it’s styled with a StaticResource, menuTitleStyle. The menuTitleStyle is contained in the top of the XAML file, inside the UserControl.Resources section.
Here is the menuTitleStyle:
It has an Item Template which contains a TextBlock and a ListBox. The TextBlock is using databinding to set it to the MenuGroup property, Title. And the ListBox is databound to the itemsList property of that menugroup.
<Style TargetType="ListBox" x:Key="menuTitleStyle" >
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding title}"
Style="{StaticResource menuTitle}" />
<ListBox ItemsSource="{Binding itemsList}" SelectionChanged="MenuList_SelectionChanged"
Style="{StaticResource menuItemsStyle}"/>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
So, to finish this piece I need to discuss how the Silverlight menu replaces the html menu. While the Silverlight menu is getting it’s xml, formatting it’s xml, putting it’s face on – it’s not visible on the web page. Once it’s ready there is a line of code here that runs a javascript which is linked to the web page. Here’s the line of code that executes the javascript, which will take care of sizing the div which contains our Silverlight control to place it over the top of the Html control (this is a modified version of CSS Image replacement technique)
//the object is ready to show itself - this is javascript call.
HtmlPage.Window.Invoke("showSlMenu");
Following this line is the animation (Silverlight) which fades in the new menu.
//here, we'll show ourself with an animation
this.showMyself.Begin();
Let’s look at showSLMenu in the file menu.js file
First, in this SLMenu function we get the width and height of the html menu.
Then, The div which holds the Silverlight control is then resized to match the original menu. In our css, we’ve set the z-index property of this div very high which will position it over any html content with a lower z-index.
And lastly, the Silverlight contol’s opacity is brought from 0 to 100 which fades it into place.