Click here to Skip to main content
Click here to Skip to main content

Building a Reasonable Looking Contact List Web Page "Component"

, , 15 May 2014 CPOL
Rate this:
Please Sign up or sign in to vote.
Adventures in Javascript, JQuery, JQuery-UI, JQuery-tablesorter, and of course CSS and HTML

Update

CPian "Kornfeld Eliyahu Peter" has kindly provided an ASP.NET implementation, the source code for which I've added to the repository as well as updated the article to the slight differences between the Ruby ERB format and the ASPX format. Regarding the ASP.NET implementation, here are the comments / suggestions from Peter:

When looking into your project I immediately though about ASP.NET MVC, Ruby on Rails is almost identical to it. For that I decided to make the port to ASP.NET Web Forms, to make it more interesting. However you may be surprised how little I had to change. In fact I never touched JavaScript and HTML (that make sense I believe, but interesting nevertheless).

In fact I did changed HTML in two cases, that not really related.

  1. You never closed <input> elements and my environment is a bit crazy about such things – so I did closed them to get rid of the messages!
  2. In the on-off switch you used <div> inside <label>. Again that’s something HTML do not like and my IDE don’t like either. So I changed them to span and added display: block; to the relevant CSS.

As you can see the differences are not more than the differences between the two framework/language - your logic is eternal! To be honest I was surprised how little changed while porting. Anyway – I do not know what you final purpose with this ‘control’, but I still see the problem of large number of contacts in your list. I tried with 700 contacts, and the page became a bit slow… I also want to tell, that this was a port of your code, no more.

Source Code

The source code is on GitHub: https://github.com/cliftonm/ContactListDemo, including the ASP.NET version.

Introduction

I decided to take on the task of putting together a decent looking contact list that can eventually become a component in a website I'm putting together. I had these basic requirements in mind:

  • a table of contacts
  • sortable by first name and last name
  • a slider button that lets you switch first / last name columns (affecting the sorting)
  • searchable with an A-Z index (like a physical address book with little "A", "B", "C..." etc. tabs)
  • and with columns for email address, home / work / cell phone numbers that can be selected by a button-set

What follows is my adventure in pulling together various technologies and finagling with them to get the behavior and appearance that I wanted. Among various challenges were:

  • Optimizing the use of screen space -- radio buttons and checkboxes, besides being somewhat archaic, also suck up screen space, as well as the default behaviors of JQuery-UI elements.
  • Consistent fonts -- it's amazing how many different fonts, sizes, and weightings these controls have, resulting in a very messy look.
  • jQuery / Javascript to do things like filtering table data
  • API's -- every component has a different API, and every API has a different "mental model" of how to use the component.

Following Along

I decided to provide separate pages for each of the steps that I'm going to walk through here. It's important to do this because you can see the differences as I add behaviors and styling to the components. I'll point out the HTML file for the screenshot so that you can review the full HTML for each step of the process.

Getting Started

While the back-end is in Ruby on Rails, you won't see much Ruby code here, because this article is almost completely about the client-side.

Setting Up A Webpage Scratchpad

Ruby

After creating an initial Rails project in the RubyMine IDE, I created a database schema with one table:

create_table "contacts", :force => true do |t|
  t.string "first_name"
  t.string "last_name"
  t.string "email"
  t.string "home_phone"
  t.string "work_phone"
  t.string "cell_phone"
  t.datetime "created_at", :null => false
  t.datetime "updated_at", :null => false
end

I seeded it with a some random data (apologies to any readers of the fairer sex, all my first names are male):

Contact.delete_all

# http://en.wikipedia.org/wiki/List_of_most_popular_given_names
first_names = ['James', 'John', 'Robert', 'Michael', 'William', 'David', 'Richard', 'Charles', 'Joseph', 'Thomas']

# http://names.mongabay.com/most_common_surnames.htm
last_names = ['SMITH', 'JOHNSON', 'WILLIAMS','JONES', 'BROWN', 'DAVIS', 'MILLER', 'WILSON', 'MOORE', 'TAYLOR', 'ANDERSON', 'THOMAS', 'JACKSON', 'WHITE', 'HARRIS', 'MARTIN', 'THOMPSON', 'GARCIA', 'MARTINEZ', 'ROBINSON']

20.times do
  fname = first_names[rand(first_names.length)].capitalize
  lname = last_names[rand(last_names.length)].capitalize
  home = rand.to_s[2..4]+'-'+rand.to_s[2..4]+'-'+rand.to_s[2..5]
  work = rand.to_s[2..4]+'-'+rand.to_s[2..4]+'-'+rand.to_s[2..5]
  cell = rand.to_s[2..4]+'-'+rand.to_s[2..4]+'-'+rand.to_s[2..5]
  Contact.create(:first_name => fname, :last_name => lname, :email => "#{fname}.#{lname}@mail.com", :home_phone => home, :work_phone => work, :cell_phone => cell)
end

ASP.NET

using System;
using System.Data;

namespace ContactListDemo
{
  public class Schema : DataTable
  {
    public Schema()
    {
      TableName = "contacts";

      Columns.Add( "first_name", typeof( string ) );
      Columns.Add( "last_name", typeof( string ) );
      Columns.Add( "email", typeof( string ) );
      Columns.Add( "created_at", typeof( DateTime ) ).AllowDBNull = false;
      Columns.Add( "updated_at", typeof( DateTime ) ).AllowDBNull = false;
      Columns.Add( "home_phone", typeof( string ) );
      Columns.Add( "work_phone", typeof( string ) );
      Columns.Add( "cell_phone", typeof( string ) );
    }
  }

  public class Contacts : Schema
  {
    // http://en.wikipedia.org/wiki/List_of_most_popular_given_names
    private string[ ] _FirstNames = { "James", "John", "Robert", "Michael", "William", "David", "Richard", "Charles", "Joseph", "Thomas" };

    // http://names.mongabay.com/most_common_surnames.htm
    private string[ ] _LastNames = { "SMITH", "JOHNSON", "WILLIAMS", "JONES", "BROWN", "DAVIS", "MILLER", "WILSON", "MOORE", "TAYLOR", "ANDERSON", "THOMAS", "JACKSON", "WHITE", "HARRIS", "MARTIN", "THOMPSON", "GARCIA", "MARTINEZ", "ROBINSON" };

    public Contacts ( )
    {
      Random oRnd = new Random( );

      for ( int i = 0; i < 200; i++ )
      {
        string szFName = _FirstNames[ oRnd.Next( _FirstNames.Length ) ].ToUpper( );
        string szLName = _LastNames[ oRnd.Next( _LastNames.Length ) ].ToUpper( );
        string szEmail = string.Format( "{0}.{1}@mail.com", szFName, szLName );
        string szHome = string.Format( "{0}-{1}-{2}", oRnd.Next( 10, 9999 ), oRnd.Next( 10, 9999 ), oRnd.Next( 10, 99999 ) );
        string szWork = string.Format( "{0}-{1}-{2}", oRnd.Next( 10, 9999 ), oRnd.Next( 10, 9999 ), oRnd.Next( 10, 99999 ) );
        string szCell = string.Format( "{0}-{1}-{2}", oRnd.Next( 10, 9999 ), oRnd.Next( 10, 9999 ), oRnd.Next( 10, 99999 ) );

        Rows.Add( szFName, szLName, szEmail, DateTime.Today, DateTime.Today, szHome, szWork, szCell );
      }
    }
  }
}

And this concludes our tour of the back-end.

Javascript Files

We do however need to configure a few components. Whatever server platform you are using, you will need:

  • jQuery
  • jQuery-UI
  • jQuery-tablesorter

Working With jQuery-tablesorter

HTML: app\views\contacts\build1.html.erb

This was, in many ways, the easiest part.

To create a table with sortable columns, like this one:

...put together some basic HTML:

Ruby

<div>
  <table id='contactsTable'>
    <thead>
      <tr>
        <th>Last Name</th>
        <th>First Name</th>
        <th>Email</th>
        <th>Home</th>
        <th>Work</th>
        <th>Cell</th>
      </tr>
    </thead>
    <% @contacts.each do |contact|%>
      <tr>
        <td><%= contact.last_name%></td>
        <td><%= contact.first_name%></td>
        <td><%= contact.email%></td>
        <td><%= contact.home_phone%></td>
        <td><%= contact.work_phone%></td>
        <td><%= contact.cell_phone%></td>
      </tr>
    <% end %>
  </table>
</div>

ASP.NET

<body>
  <form id="form1" runat="server">
    <div>
      <table id='contactsTable' class='tablesorter-default'>
        <thead>
          <tr>
            <th>Last Name</th>
            <th>First Name</th>
            <th>Email</th>
            <th>Home</th>
            <th>Work</th>
            <th>Cell</th>
          </tr>
        </thead>
        <% foreach ( System.Data.DataRow oContact in Contacts.Rows ) { %>
          <tr>
            <td><%= oContact["last_name"]%></td>
            <td><%= oContact["first_name"]%></td>
            <td><%= oContact["email"]%></td>
            <td><%= oContact["home_phone"]%></td>
            <td><%= oContact["work_phone"]%></td>
            <td><%= oContact["cell_phone"]%></td>
          </tr>
        <% } %>
      </table>
    </div>
  </form>
</body>

and a one-liner Javascript call:

<script type="text/javascript">
  $(document).ready(function()
  {
    $("#contactsTable").tablesorter();
  });
</script>

Usability and Presentation Issues

While this looks rather nice on the first take, it has a few problems. The major issues are:

  • Not all fields should be sortable. It makes little sense to sort on email address and the home, work, and cell phone numbers.
  • Information overload. When I look up a contact, I usually want only one thing, not the whole shebang. If I'd included physical address and mailing address, these would add two more fields from the collection of four that we already have for each contact.

Medium issues:

  • Sometimes I know the person's first name and I want to look them up that way, sometimes I want to look someone up by their last name. Rather than having to shift my eyes to the second column to find people by their first name, then shift left to see if the last name matches, then shift right again for the relevant information I want, I'd like to be able to swap the first and last name columns, so that when I visually search the list, I'm mostly taking a left-to-right path with my eyeballs. I'll be addressing this issue in a separate section.

The minor issues are:

  • It takes up the whole screen.
  • I'd like "zebra" striping to create a less monochromatic (and monotonous) list.

Polishing the Table

HTML: app\views\contacts\build2.html.erb

Removing the Sort Feature for Specific Columns

The first thing to do is remove the ability to sort the email and phone fields. jQuery-tablesorter is a very nice component, and getting it to not sort specific columns is very easy:

<script type="text/javascript">
$(document).ready(function() {
  $("#contactsTable").tablesorter({
    headers : {
      0: { sorter: "text" },
      1: { sorter: "text" },
      2: { sorter: false },
      3: { sorter: false },
      4: { sorter: false },
      5: { sorter: false },
      6: { sorter: false }
    }
  })
});
</script>

Fixing the Width

Well that's easy, except we should always follow the rule that if you're going to fix the width of something, write an outer div for the desired width and and set the inner div width to 100%. This way, the inner component can be re-used without fussing with things like physical dimensions, which is application-specific implementation. You will also note that this comes in handy later! So:

<div style="width:500px">
  <div style="width:100%">
    ...  etc ...

Yes, this is actually bad practice, but at some point (not in this article) I'll actually use this component with a grid system like Foundation and remove the fixed width -- this is mainly for me to see what it looks like in a narrower presentation, as other stuff is intended to go onto the final page.

Zebra Striping

This is easily specified in the tablesorter (there are also many other nifty features you can read up on in the documentation):

$("#contactsTable").tablesorter({
  widgets : [ 'zebra' ],
  ... etc ...

We now have the following:

Getting better!

Removing the Noise

HTML: app\views\contacts\build3.html.erb

Next, we need a way to select the specific information that we want rather than polluting our grid with a lot of things we don't need when we look up a contact. For that I'll use the jQuery-UI buttonset. This requires a modicum of HTML:

<div id="MyButtonList">
  <input type="checkbox" id="toggle_email"><label for="toggle_email">email</label>
  <input type="checkbox" id="toggle_home"><label for="toggle_home">home</label>
  <input type="checkbox" id="toggle_work"><label for="toggle_work">work</label>
  <input type="checkbox" id="toggle_cell"><label for="toggle_cell">cell</label>
</div>

and another one-liner in the document ready event:

$('#MyButtonList').buttonset();

Of course, it looks like bad because it's huge and we've just added a second font to the layout. But we'll deal with that later.

Now we need to write the implementation for clicking on the button set. We'll write a four unobtrusive event click handlers:

$('#MyButtonList').buttonset(); $("#toggle_email").click(function()
{
  showOrHide('#toggle_email', 3)
});

$("#toggle_home").click(function()
{
  showOrHide('#toggle_home', 4)
});

$("#toggle_work").click(function()
{
  showOrHide('#toggle_work', 5)
});

$("#toggle_cell").click(function()
{
  showOrHide('#toggle_cell', 6)
});

function showOrHide(button, colNum)
{
  if ($(button).is(":checked"))
  {
    $('#contactsTable tr *:nth-child('+colNum+')').removeClass('hidden');
  }
  else
  {
    $('#contactsTable tr *:nth-child('+colNum+')').addClass('hidden');
  }
}

Usability and Presentation Issues

At this point we have a mostly functional implementation (it's missing one more feature) but there are yet again usability and presentation issues:

Major issues:

  • When we do a page refresh:, the grid doesn't reflect the selected data columns
  • Mismatching fonts and much to large of a visualization
  • It's completely unclear to me in the buttonset with a gray button means "selected" or a white button means "selected." In fact, when I implemented the underlying Javascript, I was surprised to notice that gray meant "unselected!"
  • Lots of dead space between columns because we aren't selecting all of them anymore.

Setting Selected Columns on Refresh

This is handled by forcibly setting the column state in the document ready event:

showOrHide('#toggle_email', 3)
showOrHide('#toggle_home', 4)
showOrHide('#toggle_work', 5)
showOrHide('#toggle_cell', 6)

Font Mismatch and Sizing

Poking around the tablesorter CSS, I discovered that the font being used is:

font: 12px/18px Arial, Sans-serif;

...which I'll specify in the CSS for MyButtonList. The next issue is the sizing. After much fussing, I decided on a line-height style of 0.8. This was determined by matching it with the button slider height that I'll be implementing next.

Making it Clearer When a Button is "Checked"

To give the button more indication that it is selected, I also chose to bold the selected text. The final CSS for the button-set looks like this:

<style>
  #MyButtonList .ui-button.ui-state-active .ui-button-text {
    font: 12px/18px Arial, Sans-serif;
    line-height: 0.8;
    color: black;
    background-color: white;
    font-weight:bold;
}

  #MyButtonList .ui-button .ui-button-text {
    font: 12px/18px Arial, Sans-serif;
    line-height: 0.8;
    color: black;
    background-color: #eeeeee;
  }
</style>

And the look now is:

Removing the Dead Space

Now that we've removed columns we don't want to see, let's also remove the dead space but preserve the full width of the grid background. Also, as a usability issue, I find it helpful to put the name on the left and the contact data (email and phones) on the right. I find this to be a visually pleasing way of separating the contact's name from the contact's other information. We achieve this with column styling:

<th style="white-space:nowrap">Last Name</th>
<th style="white-space:nowrap">First Name</th>
<th style="white-space:nowrap; width:99%"></th>
<th style="white-space:nowrap;">Email</th>
<th style="white-space:nowrap;">Home</th>
<th style="white-space:nowrap;">Work</th>
<th style="white-space:nowrap;">Cell</th>

We do the same thing for the table body rows (not shown.)

Notice the third column is an empty column with a width of 99%. The name columns now adjust to fit the content and the remaining "data" columns are moved to the right, for example:

Select First / Last Name Column Ordering

HTML: app\views\contacts\build4.html.erb

A very nifty CSS only flip switch (or button slider, as I've been calling it) can be found here. The complete CSS is bulky and not necessary to show, but this is what I ended up with initially:

The implementation of the button slider is straight-forward. Again we watch for a click event and toggle the sequence of the first and second columns:

$("#myonoffswitch").click(function()
{
  var tbl = $('#contactsTable');
  moveColumn(tbl, 1, 0);
});

Also, in the document ready event, we want to set the state the column sequence when the page is refreshed:

if (!$("#myonoffswitch").is(":checked"))
{
  var tbl = $('#contactsTable');
  moveColumn(tbl, 1, 0);
}

The moveColumn function does all the work:

function moveColumn(table, from, to) {
  var rows = $('tr', table);
  var cols;
  rows.each(function() {
    cols = $(this).children('th, td');
    cols.eq(from).detach().insertBefore(cols.eq(to));
  });
}

Usability and Presentation Issues

  • Yet again, the font is different
  • I don't like that the "off" mode has a different background. This isn't really an on/off switch as simply a "state" control.
  • It's not aligned well with the button-set. Ideally, the button-set should be right-justified to the edge of the grid and the slider should be the same height and vertically aligned with the button-set

Font, Size, Color Issues

Fixing these issues was fairly straight forward. The font became the same as used by the table. In the code you'll notice I have comments for the three places that have to be touched when fussing with the width of the control. I also made the border narrower.

Alignment Issues

To left-justify the button slider and right-justify the button-set requires a little div footwork within the div that has a width of 500px:

For the button slider:

<div style="width:100%">

For the button-set:

<div id="MyButtonList" style="float:right">

We also need to clear the div for the table so that it is forced to a new row:

<div style="clear:left; width:100%">

We now have something that appears like this:

Adding An Index Filter

HTML: app\views\contacts\build5.html.erb

Lastly, I want to add an A to Z index down the left. When the user clicks on a letter, it filters the contact list with people's names beginning with that letter. If last name is the first column, then it filters last names. If first name is the first column, then it filters first names.

To implement this requires moving everything over by 20 pixels, so we start with the slider button div:

<div style="margin-left:20px; width:100%">

We also move the table over by 20 pixels and (by the way) adjust the top padding a little bit to give us some room from the controls above it):

<div style="margin-left:20px; width:100%; padding-top: 3px">

And then, before the table div, we add a div with some ruby code to generate the index, set ID's and call a Javascript function with the letter index the user clicks on:

Ruby

<div class="index-filter">
  <table style="font: 12px/18px Arial, Sans-serif;">
    <tr>
      <td>
        <a href='#' id='filter-none' onclick='showAll()'>*</a>
      </td>
    </tr>
    <% ('A'..'Z').each do |s| %>
    <tr>
      <td>
        <% filter_by = %Q|filterBy("#{s}")| %>
          <a href='#' id='<%="filter-#{s}" %>' onclick='<%="#{filter_by}"%>'><%= "#{s}" %></a>
      </td>
    </tr>
    <% end %>
  </table>
</div>

ASP.NET

<div class="index-filter">
  <table style="font: 12px/18px Arial, Sans-serif;">
    <tr>
      <td>
        <a href='#' id='filter-none' onclick='showAll()'>*</a>
      </td>
    </tr>
    <% foreach ( char s in Enumerable.Range( 'A', 'Z' - 'A' + 1 ).Select( x => x ) ) { %>
      <tr>
        <td>
          <a href='#' id='filter-<%= s %>' onclick='filterBy("<%= s %>")'><%= s %></a>
        </td>
      </tr>
    <% } %>
  </table>
</div>

(Yes, the table style should be in CSS not embedded in the HTML.)

We need some CSS to get the list to appear in the right location, which is on the left aligned with the top of the table body (not the header):

.index-filter {
  clear:left;
  float:left;
  width:2px;
  margin-top: 25px;
  margin-left: 2px;
}

Notice I precede the index list with an asterisk to deselect filtering. The showAll and filterBy method looks like this:

function showAll()
{
  $("#filterable").find("tr").each(function(idx, row){
  row.hidden=false;
  });
}

// Filter by the first letter of the first column, which will be either last name or first name.
function filterBy(letter)
{
  var rows = $("#filterable").find("tr");
  rows.each(function(idx, row)
  {
    if (row.children[0].innerHTML.indexOf(letter)==0)
    {
      row.hidden=false;
    }
    else
    {
      row.hidden=true;
    }
  });
}

We also need to wrap the rows in a tbody tag with the a "filterable" ID:

Ruby

<tbody id='filterable'>
  <% @contacts.each do |contact|%>
    <tr>
      <td style="white-space:nowrap"><%= contact.last_name%></td>
      <td style="white-space:nowrap"><%= contact.first_name%></td>
      <td style="white-space:nowrap; width:99%"></td>
      <td style="white-space:nowrap;"><%= contact.email%></td>
      <td style="white-space:nowrap;"><%= contact.home_phone%></td>
      <td style="white-space:nowrap;"><%= contact.work_phone%></td>
      <td style="white-space:nowrap;"><%= contact.cell_phone%></td>
    </tr>
  <% end %>
</tbody>

ASP.NET

<td style="white-space: nowrap"><%= oContact["last_name"]%></td>
<td style="white-space: nowrap"><%= oContact["first_name"]%></td>
<td style="white-space: nowrap; width: 99%"></td>
<td style="white-space: nowrap;"><%= oContact["email"]%></td>
<td style="white-space: nowrap;"><%= oContact["home_phone"]%></td>
<td style="white-space: nowrap;"><%= oContact["work_phone"]%></td>
<td style="white-space: nowrap;"><%= oContact["cell_phone"]%></td>

We now have something that works but looks like this:

An Important Lesson

Incidentally, I was first using the jQuery function like this:

$("#filterable").find("tr").hide();
... show only selected rows ...

But this was giving me a lot of grief because, as it turns out, jQuery is setting CSS with the hide() function whereas I was trying to make the rows visible with the hidden attribute! This is a very important lesson -- make sure that your usage of attributes and CSS is consistent for the behavior that you want!

Usability and Presentation Issues

  • The indices are showing up as links and the vertical spacing is too wide.

Fixing the Look of the Indices

CSS to the rescue:

.index-filter tr td {
  line-height:9px;
}

.index-filter tr td a {
  text-decoration: none;
  color: black;
}

.index-filter tr td a:hover {
  color: white;
  background-color: black;
  font-weight:bold;
}

Now we've removed the underlining and have a clearer indicator of what index letter over which the mouse is hovering:

About Scrollbars, Pagination, and Color

I decided not to implement scrolling bars or pagination. I find pages that have internal scrollbars to be annoying -- I much rather prefer to scroll the entire browser window. Also, for something like a contact list, I don't think pagination is appropriate. It is incredibly fast to scroll through a list, even a long one, when it's sorted. Pagination just gets in the way. Lastly, I would have liked to color the button-set with green for selected and red for unselected, but again, that's technically bad UI design for people that are red-green color blind.

Conclusion

I hope you have enjoyed this tour and find the contact list presentation that I've created at least somewhat aesthetically pleasing! Special thanks to Peter for putting together the ASP.NET port!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Authors

Marc Clifton

United States United States
Marc is the creator of two open source projets, MyXaml, a declarative (XML) instantiation engine and the Advanced Unit Testing framework, and Interacx, a commercial n-tier RAD application suite.  Visit his website, www.marcclifton.com, where you will find many of his articles and his blog.
 
Marc lives in Philmont, NY.

Kornfeld Eliyahu Peter
Software Developer (Senior)
Israel Israel
Born in Hungary, got my first computer at age 12 (C64 with tape and joystick). Also got a book with it about 6502 assembly, that on its back has a motto, said 'Try yourself!'. I believe this is my beginning...
 
Started to learn - formally - in connection to mathematics an physics, by writing basic and assembly programs demoing theorems and experiments.
 
After moving to Israel learned two years in college and got a software engineering degree, I still have somewhere...
 
Since 1997 I do development for living. I used 286 assembly, COBOL, C/C++, Magic, Pascal, Visual Basic, C#, JavaScript, HTML, CSS, PHP, ASP, ASP.NET, C# and some more buzzes.
 
Since 2005 I have to find spare time after kids go bed, which means can't sleep to much, but much happier this way...
Follow on   Twitter   Google+   LinkedIn

Comments and Discussions

 
QuestionVery Nice Article PinmemberDinesh.V.Kumar14-May-14 20:50 
QuestionUse a framework? PinmemberPinx28-Apr-14 23:29 
AnswerRe: Use a framework? PinprotectorMarc Clifton4-May-14 8:08 
QuestionVery nice! PinprofessionalKornfeld Eliyahu Peter26-Apr-14 23:39 
AnswerRe: Very nice! PinprotectorMarc Clifton27-Apr-14 3:08 
GeneralRe: Very nice! PinprofessionalKornfeld Eliyahu Peter27-Apr-14 3:20 
GeneralRe: Very nice! PinprotectorMarc Clifton27-Apr-14 3:29 
GeneralRe: Very nice! PinprofessionalKornfeld Eliyahu Peter27-Apr-14 3:31 
GeneralRe: Very nice! PinprotectorMarc Clifton14-May-14 13:56 
GeneralRe: Very nice! PinprofessionalKornfeld Eliyahu Peter14-May-14 19:15 
QuestionRe: Very nice! PinprofessionalKornfeld Eliyahu Peter15-May-14 0:58 
AnswerRe: Very nice! PinprotectorMarc Clifton15-May-14 1:01 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web03 | 2.8.141015.1 | Last Updated 15 May 2014
Article Copyright 2014 by Marc Clifton, Kornfeld Eliyahu Peter
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid