Tag Cloud Filters with ASP.NET 3.5's LinqDataSource and ListView Controls
This past week I finally set aside some time to take a look at Silverlight. I started with ScottGu's digg demo application and continued on to silverlight.net to check out what some of the early adopters have already built. I didn't get too far before I bumped into a control on the Silverlight showcase homepage that thought was pretty interesting. The control is a datagrid of sorts that allows you to filter the contents of the grid using a tag cloud. So I took a crack at creating a similar looking grid using ASP.NET 3.5 (the screen shot is below).
Notes: The icon images used in this sample are transparent PNG's, so the demo page looks a little goofy in IE6.
Update (3/4/2008): Mark Grubner posted the following comment:
A question about this one though - could you comment on the performance issues/requirements in a "real" application? I am thinking about the fact that the xml document is parsed every time you click on one of the tags. What could/should be done about this?
Which of course is a fine point and a great question. In the solution I posted below every time a tag is clicked, the XML file is re-parsed. Clearly this is far from an ideal implementation. So I refactored my original sample to avoid this continuous reprocessing by taking the following actions:
- Created a class for holding the Icon object data. The class looks like this ...
- Added a session backed property to the page that returns an IEnumerable<Icon> collection. The first time the property is invoked LINQ to XML is used to load the Icons.xml file into a collection of Icon objects. Once the file is loaded it is placed into session. Of course depending on your requirements you might be able to use the Cache or Application containers as well. I chose Session for my fictitious example.
- Replaced the LINQ to XML queries in the two LinqDataSource Selecting event handlers with LINQ to Objects queries that operate on the IEnumerable<Icon> collection of objects.
Did I miss anything? I hope not. Anyway, in case anyone is curious, I usually focus my examples on portability (that's why I chose XML over a database for most of my samples). I know doing this looses some of the 'real worldness', but I figure the portability makes up for that (and would it be any better if I used Northwind? How close do those tables resemble your production database?).
Either way thank you Mark for the comment. I appreciate you pointing it out. The download for the updated code can be found here.
I used the sliding doors technique to create the rounded corners for this sample. If this approach is new to you, I would recommend reading the following links
Tag Cloud LinqDataSource
The data for the tag cloud and icon grid is fed from an XML file. The XML file contains a collection of icon xml elements, each with three attributes: name, imageUrl and tags. These attributes store the metadata for each of the images being displayed. The tags attribute is a comma separated list of categories the image belongs to. Here is a portion of the xml file for a few of the icons displayed above.
To build the tag cloud, I need to query this XML file and return the unique set of tag names as well as the frequency of their occurrence. Tags that occur more frequently will be displayed in larger text from within our tag cloud. Of course with LINQ to XML, getting at this information is a no-brainer. In the following 4 lines of code, I have extracted all of the icon XML elements, split the tags attribute by the comma separator and figured out how many times each unique tag value occurs.
Tag Cloud ListView
After creating the LINQ query for our data source, I went ahead and created a very simple ListView to render the tag cloud. I want my cloud to render as a series of anchor's within a DIV container, so I have configured the ListView with this in mind (ASP.NET render's LinkButton's as HTML anchors).
To control the relative size of the tags within the cloud, I have implemented a method called CreateCssClass that accepts the number of times the tag occurs and returns the corresponding CSS class. For this example, I have 5 classes, t1 through t5. Each of these classes has a different font-height applied.
And here is the CreateCssClass method that translates the tag frequency into a CSS class ...
Tag Cloud Filter Commands
Each of the LinkButtons in the tag cloud ListView have their CommandName and CommandArgument attributes set. I am using the ListView's ItemCommand event to apply the tag filter to the grid. I have the following event handler attached to this event that carries out this task. When a tag from the tag cloud is clicked, this event handler adds a parameter to the LinqDataSource's WhereParameters collection. When the Select for the grid's ListView fires, it will have access to the tag value that needs to be filtered by. When the special 'all' link is clicked, the WhereParameters collection is cleared and all of the icons are displayed.
Like the tag cloud, the Icon grid is also fed from the XML previously described. Because the Icon grid needs to support filtering based on given tag value, the Selecting event handler for the LinqDataSource needs to check if a tag value has been provided. If one has, this value needs to be used from the where clause.
Below is the event handler for the Selecting event. Notice how the WhereParameters collection is evaluated to see if a tag filter has been applied. If no tag filter exists, the where clause is short-circuited and all icons are returned. If the tags filter does exist, only icons for the tag value are displayed.
The grid displaying the Icons is also rendered using a ListView and like the tag cloud this grid is also very simple. It renders as a UL/LI structure. The LayoutTemplate contains a UL and a placeholder LI and the ItemTemplate contains the markup for the LI that contains a nested IMG element that is populated with the ImageUrl attribute from the data source.
Tie it all together and you get a fairly nice looking page.
That's it. Enjoy!