Building a LinkedIn Style Address Book with the ListView and LinqDataSource Controls

LinkedIn has a nice looking address book widget that displays all of your connections partitioned by last name.  To the left of the contact listing is an index that allows you to jump right to a section.  This control isn't overly fancy, but I was curious what HTML/CSS/JavaScript was used to create it.  So I took it apart using the IE Developer Toolbar and rebuilt it using ASP.NET 3.5 - making use primarily of LINQ to XML, the LinqDataSource and a couple of ListView's.  Feel free to browse the source code by following the download link.  And, of course I recommend checking out the live demo as well.  If you are interested in how I created the sample, a quick description of the implementation details can be found just below the screen shot.  And the best part - excluding the CSS, only 115 lines of markup/code are required to build this sample. 

Live Demo (IE6, IE7, FF, Opera 9.24) | Download (.NET 3.5)

image

Creating the LinqDataSource

The demo makes use of a single LinqDataSource that feeds all three ListView controls found on the page.  This datasource returns a record for each of the letters of the alphabet - {A, B, ... Z}.  Additionally, each of these 26 records contains a collection of all of the contacts whose last name starts with the current letter.  If no contacts have last names starting with a given letter, an empty collection is returned.  The table below shows this structure - notice the empty Contacts collection for letters 'C' and 'Z'.  

First Letter of Last Name Contacts
A
FirstName LastName
Syed Abbas
Francois Ajenstat
Alberts Amy
B
FirstName LastName
David Bradley
Kevin Brown
Eric Brown
C
FirstName LastName
... ...
Z
FirstName LastName

To generate this structure, I use LINQ to extract the contact records from an XML file and join them by the first letter of their last name to an in-memory character array that contains all of the letters in the alphabet.  After these structures are joined together, I select out the attributes required to build the interface.  The resulting structure is similar to one that could have been created by grouping the contacts by the first letter of their last name.  I used the join instead because I want to make sure I include all letters - even those that do not have any associated contacts (I use all of the letters to build the index piece of the address book).

image 

This LINQ query is wired to a LinqDataSource by placing the code into the datasources Selecting event handler.

image

Contact Listing ListView

After the datasource was created, I moved on to adding the ListView that will generate the listing of contacts for the address book.  The core portion of the address book displays contacts grouped by last name.  I used two ListViews to generate an OL structure for grouping the contacts.  Each of the LI elements within the OL contains a TABLE element that has a row for each contact within the current group.  Before the TABLE element is a H2 element that contains the heading for the current group.  The screen shot below shows how this general structure would be applied to elements listed in the table above. 

image
The outer ListView binds directly to the LinqDataSource we previously created.  This outer ListView generates the grouping information contained within the outer OL element.  Within the ItemTemplate of this ListView is another nested ListView that binds to the Employees property and generates the nested TABLE that contains items for each of the contacts within the current group.

  image

Contact Index ListView

Next, I went ahead and created the markup structure for generating the contact index.  If a letter in the index (like Z or C) does not have any associated contacts, I still want to render the letter.  If there are associated contacts, I want to render an anchor element that will scroll the page automatically to the contacts for the selected index.  Again, I used the ListView to create a OL/LI structure for these items.  If a letter doesn't have any associated contacts, I render the letter within a SPAN.  Otherwise, I render an anchor element and set the href to the ID of the grouping header element I created using the previous ListView.

 image

Some CSS Trickery

The most complex part of the addressbook widget is the CSS that controls how the index and grouping headers are aligned - both accomplish this by making use of negative margins.  The index DIV floats left and has a negative margin set, making sure it displays to the left of the addressbook container. 

image

The 2px solid line that appears before each grouping of contacts is actually the top border of the table (the table is shifted up by setting the top margin to a negative value).  It is shifted up just enough that it aligns with the middle of the H2 element above it.

image

That's it.  Enjoy!


TrackBack

TrackBack URL for this entry:
http://mattberseth.com/blog-mt/mt-tb.fcgi/105

Comments


Awesome!

I like all your blogs. Beautifully explained and the components are also usable. Thanks for your good contribution to the World Wide Web.

Bye
Bjorn

The Netherlands

Posted by: RickshawDriver on February 26, 2008 12:00 AM

You always have amazing examples that are a great show of simplistic beauty, where form does not overshadow function.

You make my day!

Posted by: Jeremy on February 27, 2008 12:00 AM

Thank you for all your examples that you have done. This one is one of the best i think.

Jeremy

Thats a pretty neat implementation!

Wow, this is a clever implementation. I like.

I also find it quite interesting to "take sites apart" to see how certain aspects work. Its the best way to learn new tricks. Well, it was the best way to learn until this blog came along ;)

Posted by: Nisar Khan on February 28, 2008 12:00 AM

@Matt: just curious to know.. lets say if i want to have hyperlink on name and when i click on the name i want the new window open or open in a same window?
is that possible?

thanks.

Posted by: Roshawn on April 1, 2008 12:00 AM

This is some sweet stuff. Im gonna see if I can create something similar for my site.

Thanks a bunch.

Roshawn

Posted by: Roshawn on April 1, 2008 12:00 AM

I couldnt get this to work in VB.NET. None of the code converters on the web could convert it correctly. Any ideas?

Posted by: David Macchia on April 21, 2008 12:00 AM

This is good stuff!

I have one issue that I am having troubling resolving with your code. Maybe you can help...

I have replaced all the Eval() with Literal controls. The literals are being populated during the lvContacts.ItemDataBound event. However, I am unable to access the literal that I have placed in the "lvItems" listview. You have nested this listview inside the "lvContacts" listview. I have tried all different types of variations using the FindControl method, but nothing seems to help.

Do you have any ideas on how to access it?

Thanks,
David

Posted by: todd on June 3, 2008 12:00 AM

Hey Matt,

Great stuff! Question though. This looks great for small address books but how would you handle 2000 + records?

Todd

Posted by: Dragon64 on August 6, 2008 09:38 AM

I love your blog and its contents, is there anyway for you to include VB into your samples for guys like me who doesn't know anything about C#. Thanks much and keep up the good work

Posted by: Boo on August 28, 2008 06:19 PM

Hello,
I am trying to convert your Linq To Xml example here into Linq To SQL, at the same time translating from C# into VB. The VB compiler is complaining about the first Into clause (Into groups) - saying that it expected the end of the statement before the Into clause.

Any help would be much appreciated.

Dim tConnection As SqlConnection = New SqlConnection("ConnectionString")
Dim tContext As DataContext = New DataContext(tConnection)
'Dim tAlphabet As Char() = {"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"}
Dim tAlphabetTable As Table(Of Alphabet) = tContext.GetTable(Of Alphabet)()
Dim tLinksTable As Table(Of Link) = tContext.GetTable(Of Link)()

tArgs.Result = From a In tAlphabetTable _
Join l In tLinksTable On _
a.Letter Equals l.CategoryName.Chars(0) Into groups

Posted by: Jason Parry on September 5, 2008 07:01 AM

I had trouble with converting to use SQL and VB, i dont know very much about LINQ but the following seem to work

Dim myQuery = From myLetters In myAlphabet Join myProfiles In myProfileQuery On myLetters Equals myProfiles.LastName.Chars(0) _
Group By myLetters Into myGroup = Group _
Order By myLetters _
Select New With _
{ _
.Key = myLetters, _
.Count = myGroup.Count, _
.Contacts = From C In myGroup _
Order By C.myProfiles.LastName _
Select New With _
{ _
.FirstName = C.myProfiles.LastName _
} _
}

Post a comment

(If you haven't left a comment here before, you may need to be approved by the site owner before your comment will appear. Until then, it won't appear on the entry. Thanks for waiting.)

Consulting Services

Yep - I also offer consulting services. And heck, I'll do just about anything. If you enjoy my blog just drop me an email describing the work you need done.

Recent Comments

  • Jason Parry wrote: I had trouble with converting to use SQL and VB, i dont know very much about LINQ but the following ...
  • Boo wrote: Hello, I am trying to convert your Linq To Xml example here into Linq To SQL, at the same time trans...
  • Dragon64 wrote: I love your blog and its contents, is there anyway for you to include VB into your samples for guys ...
  • todd wrote: Hey Matt, Great stuff! Question though. This looks great for small address books but how would you ...
  • David Macchia wrote: This is good stuff! I have one issue that I am having troubling resolving with your code. Maybe yo...
  • Roshawn wrote: This is some sweet stuff. Im gonna see if I can create something similar for my site. Thanks a bun...
  • Roshawn wrote: I couldnt get this to work in VB.NET. None of the code converters on the web could convert it corre...
  • Josh Stodola wrote: Wow, this is a clever implementation. I like. I also find it quite interesting to "take sites apar...