Using the ListView in tiled mode (Part 2) - CSS layout
Using the ListView in tiled mode (Part 1)
Updated 12/01/2007 for VS 2008 RTM
In this second part of my post on customizing the ListView UI, we are going to look at how to consume a Flickr RSS feed and display the feed items in a ListView (tiled mode). The layout will be done using only CSS (no tables). We will also make use of the new DataPager control to display a pager at the top and bottom of the page. Each page will contain 9 photos with 3 photos per row.
I am using an RSS feed of some nice photos from Wagner Campelo (I don't know the guy but found him while looking for photos on flickr for this post).
To get started, we add a ListView control and LinqDataSource control to the page and set the DataSource property of the ListView control to the LinqDataSource. We then implement an event handler to handle the Selecting event of the Linqdatasource. We do this to write a custom LINQ expression in the code behind which will fetch the RSS feed and extract the fields we are interested in. This data is assigned to the Result property of the LinqDataSourceSelectEventArgs object. The code behind is shown below:
1: protected void LinqDataSource1_Selecting(object sender, LinqDataSourceSelectEventArgs e)
2: {
3: XDocument rssFeed = XDocument.Load("http://api.flickr.com/services/feeds/photos_public.gne?id=86361978@N00&tags=colors&lang=en-us&format=rss_200");
4: e.Result = from item in rssFeed.Descendants("item")
5: select new
6: {
7: Title = item.Element("title").Value,
8: Description = item.Element("description").Value
9: };
10: }
Note that we have written an LINQ expression that enumerates all root "item" nodes and extracts the "title" and "description" node values. To learn more about this, read Scott's tutorial on LINQ to XML
The code above is going to parse the RSS feed every time a post back occurs making it non-optimal. What we should do instead is to cache the result of the RSS feed for a set amount of time. The code can be refactored to support caching like so:
1: public partial class _Default : System.Web.UI.Page
2: {
3: private const string FeedUrl = "http://api.flickr.com/services/feeds/photos_public.gne?id=86361978@N00&tags=colors&lang=en-us&format=rss_200";
4: private const string PhotoCache = "photoCache";
5:
6: protected void LinqDataSource1_Selecting(object sender, LinqDataSourceSelectEventArgs e)
7: {
8: e.Result = GetFlickrFeed();
9: }
10:
11: private object GetFlickrFeed()
12: {
13: Object o = Cache[PhotoCache];
14: if (o == null)
15: {
16: XDocument rssFeed = XDocument.Load(FeedUrl);
17: var photos = from item in rssFeed.Descendants("item")
18: select new
19: {
20: Title = item.Element("title").Value,
21: Description = item.Element("description").Value
22: };
23: o = photos.ToList();
24: UpdateRefreshDate();
25: //We are caching for 5 seconds only for demonstration purposes
26: Cache.Insert(PhotoCache, o, null, DateTime.Now.AddSeconds(5), Cache.NoSlidingExpiration);
27: }
28: return o;
29: }
30:
31: /// <summary>
32: /// Update the label control in the list view with
33: /// the last refreshed time.
34: /// </summary>
35: private void UpdateRefreshDate()
36: {
37: Label lblLastRefresh = (Label)ListView1.FindControl("lblLastRefresh");
38: lblLastRefresh.Text = "Last Refreshed at " + DateTime.Now.ToString();
39: }
40: }
Note that we call the ToList() method to evaluate the LINQ expression and create a List<T> from the result. Without doing this, we would have stored the Linq expression in cache which would defeat the purpose.
The ListView control supports paging by associating one or more DataPager controls with it. The DataPager, a new control is the default paging control for the ListView control. It can be placed either inside or outside the ListView control. When placed outside the ListView control, you have to specify the ListView Control whose data you wish to page by specifying the PagedControlID property like so: PagedControlID="ListView1". When placed inside a ListView control, you do not need to specify this property - it is implied. The DataPager control is flexible in how you want the pager UI to look like. Note that you can have as many DataPagers as you want placed anywhere on the page!
Moving on to the UI. The code for the aspx page is shown below:
1: <asp:ListView ID="ListView1" runat="server" GroupItemCount="3" DataSourceID="LinqDataSource1">
2: <GroupSeparatorTemplate>
3: <div class="groupSeperator">
4: </div>
5: </GroupSeparatorTemplate>
6: <GroupTemplate>
7: <div id="itemPlaceholder" runat="server">
8: </div>
9: </GroupTemplate>
10: <ItemTemplate>
11: <span class="itemStyle">
12: <div class="photoHeading">
13: <%#Eval("Title") %>
14: </div>
15: <%#Eval("Description") %>
16: </span>
17: </ItemTemplate>
18: <EmptyItemTemplate>
19: <span class="itemStyle"></span>
20: </EmptyItemTemplate>
21: <AlternatingItemTemplate>
22: <span class="altItemStyle">
23: <div class="photoHeading">
24: <%#Eval("Title") %></div>
25: <%#Eval("Description") %></span>
26: </AlternatingItemTemplate>
27: <LayoutTemplate>
28: <div class="layoutRegion">
29: <div class="headingRegion">
30: Wagner Campelo
31: <div class="subHeading">
32: Photos from Flickr
33: </div>
34: </div>
35: <div class="pagerRegion">
36: <asp:DataPager ID="DataPager1" runat="server" PageSize="6">
37: <Fields>
38: <asp:NextPreviousPagerField ButtonType="Button" ShowFirstPageButton="True" ShowLastPageButton="True"
39: ButtonCssClass="pagerBtn" FirstPageText=" « " NextPageText=" › " PreviousPageText=" ‹ "
40: LastPageText=" » " />
41: </Fields>
42: </asp:DataPager>
43: </div>
44: <div id="groupPlaceholder" runat="server">
45: </div>
46: <div class="pagerRegion">
47: <asp:DataPager ID="DataPager2" runat="server" PageSize="6">
48: <Fields>
49: <asp:NextPreviousPagerField ButtonType="Button" ShowFirstPageButton="True" ShowLastPageButton="True"
50: ButtonCssClass="pagerBtn" FirstPageText=" « " NextPageText=" › " PreviousPageText=" ‹ "
51: LastPageText=" » " />
52: </Fields>
53: </asp:DataPager>
54: </div>
55: <div class="footerRegion">
56: Copyright © 2007 |
57: <asp:Label ID="lblLastRefresh" runat="server"></asp:Label>
58: </div>
59: </div>
60: </LayoutTemplate>
61: <ItemSeparatorTemplate>
62: <div class="itemSeperator">
63: </div>
64: </ItemSeparatorTemplate>
65: </asp:ListView>
66: <asp:LinqDataSource ID="LinqDataSource1" runat="server" OnSelecting="LinqDataSource1_Selecting">
67: </asp:LinqDataSource>
Note that I am using only div and span tags in the markup and that all div and span tags have a class attribute associated with it. With an aspx markup like this, you have full control of what the UI will look like by making changes to the respective classes in the CSS file.
You can see that the ListView has the GroupItemCount property set to 3 and that I have added two DataPager controls in the LayoutTemplate.
In the UI, I have used dotted lines to separate each photo and each group using CSS. The bottom right of the sample page displays the last time the data was refreshed. To see what the final result will look like, download the sample project file from either location below:
Location 1
Location 2