Tuesday, November 25, 2014

Creating Responsive Tables


When building a responsive website where the main responsibility is to display data in tables, one needs to put some thought into how to present this data on smaller screens. Tables are, of course, as wide as the number of data columns, so this presents some challenges with how to display it when the view width is limited.

Using Bootstrap's table-responsive

Bootstrap's table-responsive class offers one solution, where the table is given a horizontal scrollbar. This is not ideal as it requires the user to scroll the table to see all the data in a row. This can be done by touching the table but this is not obvious and some users may think they need to use the scrollbar. This then reduces usability when there are more than a few rows in the table - the user would scroll to the bottom of the table before scrolling horizontally, leading to some frustrating vertical scrolling if the data they are looking at is back up at the top of the table.

Rotating the Data

The following is an alternative approach. The idea is to "rotate" the data so that columns become rows and rows become collections of rows. So this table:



Looks like this on a smaller screen:


Achieving this with CSS poses a couple of challenges. How do you lay out table header cells vertically, and flow the data cells in this way? Well obviously you can't, so you need to employ a couple of tricks.

The first thing to do is set up a simple table. It can use the bootstrap table class as well as our custom responsive-table class (note  this is different to the bootstrap table-responsive class).

<table class="table responsive-table">
   
<thead>
   
<tr>
       
<th>Number</th>
       
<th>First Name</th>
       
<th>Last Name</th>
       
<th>Address</th>
       
<th>Points</th>
   
</tr>
   
</thead>
   
<tbody>
   
<tr>
       
<td data-content="Number">1</td>
       
<td data-content="First Name">Sam</td>
       
<td data-content="Last Name">Smith</td>
       
<td data-content="Address">12 Smith Road</td>
       
<td data-content="Points">87</td>
   
</tr>
   
<tr>
       
<td data-content="Number">2</td>
       
<td data-content="First Name">Bob</td>
       
<td data-content="Last Name">Jones</td>
       
<td data-content="Address">99 Angle Street</td>
       
<td data-content="Points">43</td>
   
</tr>
   
<tr>
       
<td data-content="Number">3</td>
       
<td data-content="First Name">Terrence</td>
       
<td data-content="Last Name">Rogers</td>
       
<td data-content="Address">999 Letsby Avenue</td>
       
<td data-content="Points">85</td>
   
</tr>
   
<tr>
       
<td data-content="Number">4</td>
       
<td data-content="First Name">Lawrence</td>
       
<td data-content="Last Name">Burnfish</td>
       
<td data-content="Address">69 The Matrix</td>
       
<td data-content="Points">0</td>
   
</tr>
   
</tbody>
</table>

Of course in a real scenario, this data would be dynamic.

Note the use of the data attribute. We'll get to that shortly. Otherwise this is a basic table. No css is required for the full screen version, unless you want to add some formatting, as this is handled by bootstrap's table class.

The CSS

It's in the media query where the magic happens. You will need to decide on your breakpoint size depending on the expected minimum width of the table, which might depend on the number of columns, and width of the data. For this example, we'll set the breakpoint to 600 pixels.

@media only screen and (max-width: 600px) {
   
.responsive-table {
       
border-top: 1px solid #ccc;
   
}
   
   
.responsive-table th,
   
.responsive-table thead{
       
display: none;
   
}

    .responsive-table, .responsive-table tbody, .responsive-table tr, .responsive-table td {
       
display: block;
   
}
 
   
.responsive-table tr {
       
border-bottom: 1px solid #ccc; 
   
}
 
   
.responsive-table td {
        /* important to override bootstrap */
        padding-left: 50% !important;
       
border-top: 0 !important;       
        text-align: left;
       
position: relative;
       
border-bottom: 0;      
   
}
 
   
/* Now like a table header */
   
.responsive-table td:before {      
       
font-weight: bold;
       
font-size: 0.85em;       
       
position: absolute;
       
margin-left: -50%;
       
width: 50%;
       
white-space: nowrap;
       
content: attr(data-content);
   
}
}

Setting the display property of the tr and td elements to block forces the cells to flow vertically.

.responsive-table, .responsive-table tbody, .responsive-table tr, .responsive-table td {
       
display: block;
   
}

Each cell has left padding of 50% which matches the width and negative margin-left of our td:before pseudo element:

margin-left: -50%;       
width: 50%;

To display the table headers we're hiding the actual th elements and using the value of the data-content attribute of each table cell. This is done using the td:before pseudo element and by setting the content css property using attr(data-content).

.responsive-table td:before {      
       
font-weight: bold;
       
font-size: 0.85em;       
       
position: absolute;
       
margin-left: -50%;
       
width: 50%;
       
white-space: nowrap;
       
content: attr(data-content);
   
}

The beauty of using the data-* attribute is that you can set these in the html to something dynamic such a javascript or @razor variable. As they will be the same for each row, this  means they can be set within the loop which displays the rows.

Presentation

We're hiding the borders on the table cells and setting a border-bottom on the table rows, so that our data is neatly separated into a collection of rows.  Notice the table itself has a top border, just to frame it.

Browser compatibility

As this trick uses media queries, and media queries only work in IE9+, we only need to ensure the other features work in IE9+. (Below this the table will not refactor when the screen shrinks anyway).  Content, data-*, and pseudo elements all work in ie9, but unfortunately negative margins don't, and combined with position:absolute, which is essential, this causes a pretty significant issue.  




Of course this only becomes a problem when an IE9 user (currently about 2% globally) reduces their browser window, and won't be a problem on actual mobile devices which this technique is targetted for, so it's up to you whether you decide to mitigate this by putting the media query into a conditional statement.

Wrapping text in titles

Watch out for cell titles with more than one word. These could wrap when the screen size is reduced and will wrap onto the next line if you have white-space set to normal in the td:before css. One way around this is to make your table cell heights 2em or more, although this obviously means all sub-rows will be double height even when there isn't a large title.

Conclusion

Refactoring tables to display vertially can be a very useful alternative technique for presenting wide tables on smaller devices. The technique may require tailoring to your specific data shape and presentation requirements, but overall the tables are easy to read and interact with.


Resources

Tuesday, October 21, 2014

5 Reasons you should start doing code reviews

  1. It will help catch quality issues earlier, another perspective on your code might reveal possible bugs
  2. It will help you be more aware of other work going on
  3. It will increase the quality of your coding practices
  4. You will learn from other people's perspectives on your code
  5. You will learn by looking at other people's code
    Books are made better by editors. Coders are made better by pair programming and code reviews. - Scott Hanselman
    Why not add Code Reviews to your Definition of Done today?