Tables in the CSS Age

As CSS began to gain popularity the use of tables for layouts, quite rightly, began to lessen. However one of the side effects of this was that less developers were learning how to use and markup a table, for any purpose. Subsequently we'd often see examples of developers using nested <div>s, painstakingly positioned with CSS, being used to layout data tables which as a result fails to provide any semantic information about what is being presented. Or in some cases actual table markup used with no idea or consideration about the semantics of the elements used, which is something that I saw again only today. Given the prevalence of those scenarios I think a simple guide to laying out a data table might be helpful to some.

Before starting with the code to use we need to consider the components of a data table and why they're used:

  1. Table Summary

    This component is one that is more frequently overlooked, as it's the one we can't see in use and the one that has no equivalent in print, however it is an important component. A table summary is added by way of a summary attribute on the opening table tag and provides a summary that can be read out by screen readers to allow visually impaired users to get a better grasp of the content and context of the table.

  2. Caption

    The caption sits outside of the main table when viewed in the browser and provides a contextual heading for what follows.

  3. Column Groups and Columns

    Data tables often have structurally related columns which can be defined within colgroup elements. The colgroup element can be used to define the width of the columns in the group and also, via the span attribute, the number of adjacent columns that is being defined within the group. If all columns in the table are structurally related then you can use the span to state this. If none are related you would just omit the colgroup element from your markup. The col element is used to define the properties of non-structurally related groups of columns (when used outside of the colgroup element), or to define changes in the properties of a subset of columns within a colgroup.

  4. The Table Header

    The table header can contain the headings for simple tables where the top row(s) of cells contain the column headings for the cells that follow. For more complex tables, the table header can still be used to define headings for the entire table, but as you can only have one table header section you can't define separate ones for subsection headings. The table header must contain at least one row, and the row must contain at least one cell, however the table header is optional rather than compulsory.

  5. The Table Footer

    The table footer can contain the footer data for simple tables, such as footnotes used to define or describe essential information. As with the table header, you can only have one footer section and so you can't define separate footers for subsections, only a single overall footer for the entire table. As with the table header, the footer is optional but if used it must contain at least one row, with the row containing at least one cell.

  6. The Table Body

    The title for this section shouldn't really read "the" table body as, unlike the table header and table footer, there can be more than one table body. This is useful for separating sections of a complex table into distinct groups of data rows. Each table body that is defined must, as with the table header and table footer, contain at least one row with at least one cell inside it. The table header, table footer and all table bodies must contain the same number of columns, however columns can be merged by using the colspan attribute should you need a different number of cells in a given row.

  7. Table Rows

    Table rows are used as structural containers for rows of data cells.

  8. Table Cells

    These are defined by <th> and <td> tags, where they create heading cells and data cells respectively. Headings are used to contain heading data that defines or describes the data that follows, while the data cells contain that data.

Now that we know and understand the components of a data table we can begin to structure one in a meaningful and accessible way:

The opening code for a simple data table:
  1. <table summary="An example of a simple data table containing dummy data">
  2. <caption>A dummy data table</caption>
  3. <colgroup span="4" width="100"></colgroup>

The code above demonstrates an example of the opening code for a table and includes the sections that define the table, including aspects of its structure, but none of the data containing aspects. Line 1 contains the opening table tag and the summary attribute which has an explanation of what the table and its data is about. Line 2 contains the caption which, in this example, is a shortened version of the summary which succinctly describes what is to follow. Line 3 contains a single colgroup element which defines the width (of the 4 columns that are a part of that group.

If you wanted to set a column to have a different width you could do so simply by including a couple of col elements nested within the opening and closing tags:

  1. <colgroup width="100">
  2. <col span="3" />
  3. <col span="1" width="250" />
  4. </colgroup>

On line 1 of the above code we have the opening colgroup tag again, however now it only contains a width attribute that defines the width of the columns in the group. Line 2 then defines the number of columns within the group that have that width setting, and line 3 defines a 4th member of the group that has a different width to the others, with line 4 closing the colgroup. Also permissable is to just use the colgroup elements as containers for col elements which are then used to define the numbers of rows, and their widths, within the colgroup, for simplicity this is the method that I most often use. For example:

  1. <colgroup>
  2. <col span="3" width="100" />
  3. <col span="1" width="250" />
  4. </colgroup>

Now that we've opened our table and defined a number of its properties we can begin to create the data containing sections:

The table header & table footer sections for a simple data table:
  1. <thead>
  2. <tr>
  3. <th scope="col">Name<sup>1</sup></th>
  4. <th scope="col">Age</th>
  5. <th scope="col">Place of Birth</th>
  6. <th scope="col">Height</th>
  7. </tr>
  8. </thead>
  9. <tfoot>
  10. <tr>
  11. <td colspan="4"><sup>1</sup> Pretend names for pretend people</td>
  12. </tr>
  13. </tfoot>

The above block of code contains the table header (<thead> on line 1, ending on line 8 with the </thead>) and table footer (<tfoot> on line 9, ending on line 13 with the </tfoot>) sections. Lines 2 & 10 and 7 & 12 define the opening and closing table row tags respectively, with lines 3-6 containing heading cells and line 11 a single data cell in the footer. Each of the heading cells contains a scope attribute, this can be set to either col or row depending on which direction it needs to be read in, here the value of col is set to define the data cells in the column below as belonging to it. I'll discuss using scope="row" in the section on the tbody later on.

For more complex tables the id and headers attributes can be used to define heading relationships between th and td cells. For information on that check the W3C section on the headers attribute, and also the axis attribute. Also, if our headings had needed to be longer and/or more complex we could have used abbreviations in conjunction with the abbr attribute.

In the table footer a single cell has been created by merging all the columns into one using the colspan attribute with a value of 1. Here a footnote, denoted by the use of a superscript #1 has been used to describe an aspect of the data in one of the heading cells.

Finally we come to the tbody (of which there may be more than one) where the data is presented:

The table body section
  1. <tbody>
  2. <tr>
  3. <th scope="row">Joe Blogs</th>
  4. <td>32</td>
  5. <td>Liverpool</td>
  6. <td>6' 1"</td>
  7. </tr>
  8. </tbody>
  9. </table>

On line 1, above, we open the first tbody section (and the only one for this example table). On line 2 we define the opening of the first table row, closing it on line 7, while on lines 3-6 we define the data cells within that row. On line 3 we have our opening data cell, which falls under the heading of Name (see the header section above) and contains the name of our example person. I've also set that data cell to be a heading cell by marking it up with <th> instead of <td>, and as this is a heading that sets a relationship with the other cells in its row it has a scope attribute with the value of row to establish this. Lines 4, 5 and 6 are simple data cells containing data that is headed by the main column headings defined in the header section earlier, but also by the row heading defined here. Finally, on line 8 we close the table body and on line 9 we close the entire table. If we put all of the component parts together we'll have the full markup for a simple data table:

The full markup for a simple data table:
  1. <table summary="An example of a simple data table containing dummy data">
  2. <caption>A dummy data table</caption>
  3. <colgroup span="4" width="100"></colgroup>
  4. <thead>
  5. <tr>
  6. <th scope="col">Name<sup>1</sup></th>
  7. <th scope="col">Age</th>
  8. <th scope="col">Place of Birth</th>
  9. <th scope="col">Height</th>
  10. </tr>
  11. </thead>
  12. <tfoot>
  13. <tr>
  14. <td colspan="4"><sup>1</sup> Pretend names for pretend people</td>
  15. </tr>
  16. </tfoot>
  17. <tbody>
  18. <tr>
  19. <th scope="row">Joe Blogs</th>
  20. <td>32</td>
  21. <td>Liverpool</td>
  22. <td>6' 1"</td>
  23. </tr>
  24. </tbody>
  25. </table>

This will then be presented in the browser like this:

A dummy data table
Name1 Age Place of Birth Height
1 Pretend names for pretend people
Joe Blogs 32 Liverpool 6' 1"

Note: that table is using the table styling set in my CSS. If you were to use that example elsewhere you would need to create your own CSS to style it otherwise it would use the browser default styling. If anyone needs tips on how to do that then let me know and I can create a follow up tutorial for that. Similarly, if there's anything that needs clarification please leave a comment and ask, I'll be happy to help.

Bookmark this:
  • del.icio.us
  • MisterWong
  • StumbleUpon
  • Twitter
  • Technorati
Up arrow

Naked Day x2

Well, CSS naked day has been and gone again this year – well, at least for me it has. The last couple of years it's been on April 5th, and I've participated each year. This year was no exception – however I may have celebrated by myself as it seems they changed the day and forgot to tell me ;)

I have a little PHP set up to automagically strip my CSS links out of my pages whenever it's April 5th anywhere in the World, and that worked perfectly again this year. The problem is, CSS Naked Day is now on the 9th, so I'm going to have to do it all again. So, here goes …

It's CSS Naked Day – Go here for more information about why I have no styles on this site, and maybe consider joining in :)

For those of you wondering why some sites are doing it on the 8th – the script used checks to see whether it's the 9th 'somewhere' in the world, and if so it then turns off the CSS. It's automagic!

Bookmark this:
  • del.icio.us
  • MisterWong
  • StumbleUpon
  • Twitter
  • Technorati
Up arrow

A feeling of deja vu

It's amazing what you can find from reading through your web stats. Things like who links to you, who visits you, where they come from, how often the search engines visit – and even who is stealing your design. Yes, it seems someone likes this design enough to pirate it.

A few weeks ago I noticed an odd referrer in my stats, http://localhost/mazochova. Anyone that knows much about computers will know that localhost refers to the local computer – ie. the one that you are using is your localhost. So, someone had a web page on their computer that was being run on a local server and was directly accessing something on this site. My suspicion was that this person was stealing my design, or some component of it, and was hotlinking my images in the process. This isn't something I wanted to happen so I blocked their IP number as a referrer. Today I found that my suspicion was right, and found that they had put up their (part done?) version at http://evisions.cz/mazochova (it has now been removed). In case you're wondering how I knew that they had put it online, they are stupidly still hotlinking some of my images – and are even still linking through to my lastFM account, my SpreadFireFox affiliate account and even my GeoURL details; they didn't bother linking to my TextLinkAds account though.

They say that mimicry is the highest for of flattery, however I'm not particularly flattered that eVisions, a supposedly professional web design company, are intending to profit from my design. While I intend to contact them (and their hosts) and ask them to remove it, I'm not sure what else I can do about this – at some stage this is going to be passed on to someone else to use, and so I doubt they'll care too much if I post it on piratedsites.com (which I still intend to do). I'll probably have to hope that they do the decent thing and remove it, but my experience of Eastern Europeans on the internet leaves a lot to be desired, and doesn't fill me with too much confidence.

I can only hope that at some point in the future Anna Mazochová, the woman who is going to be ripped off by these thieves, gets to see this at some point and takes some action for herself. Unless someone has any ideas on effective ways to stop these people, If so, I'd be grateful for the heads up. In the meantime, here's a couple of screen shots:

  • The top half of one of the inner pages of the pirate site
  • Screenshot showing the stolen design linking to my LastFM account
  • Full page view of stolen design used at evisions.cz/mazochova
Bookmark this:
  • del.icio.us
  • MisterWong
  • StumbleUpon
  • Twitter
  • Technorati
Up arrow

Advanced spam control with mod_rewrite

I can't remember where I first got the idea from, but for some time now I've been using mod_rewrite to protect against spam and hack attempts, and this has worked quite well for some time. Essentially, I have a number of rules contained in my .htaccess file which are designed to block attacks from "users" displaying common traits – with one of those common traits being the absence of a user-agent string from the request headers.

As was pointed out to me yesterday, there's no obligation for any user-agent (UA) to send a user-agent string as a part of its request headers. I have no quarrel with that statement at all – except, on this site there is. Considering the vast number (several thousand per month) of attempts to directly access comment and contact forms, or to access non-existent files with random character file names by bots, spammers or hackers whose request headers lack a user-agent string, and the fact that it is very rare in my experience for a legitimate visitor to not include one, I decided that it was a requirement for visiting here and used the following code to block them:

The mod_rewrite code used to block visitors without a user-agent string
  1. RewriteEngine On
  2. RewriteCond %{HTTP_USER_AGENT} =""
  3. RewriteRule .* - [F,L]

Line 1 turns the rewrite engine on. Line 2 sets the condition to be checked for, in this case an empty user-agent string (denoted by the absence of content between the double quote marks), and line 3 says what should happen when the condition is met – with the F stating that the request should fail. In which case it returns a 403, forbidden, error.

As I said above, that has worked quite well for some time and I've been happy with the effect that it's had on the amount of spam I've experience. However, when checking my access logs on a couple of occasions recently I noticed that something had been trying to access a file relating to the Text Link Ads service; in order to check that their adverts are working properly their server periodically checks publishers' sites to make sure that the adverts are displayed. Whilst this is a reasonable, and sensible, thing to do it appears that their server fails to include a user-agent string in its request headers – meaning that every attempt to check my site was being rejected by the server, which isn't so good. Consequently, this meant that either I had to stop blocking them, or they had to include a user-agent string in their headers

As my attempts to explain the situation to their support people seemed to be met with misunderstanding it turned out that I had to stop blocking them. Though, this wasn't as simple as just removing the code from my .htaccess as this would only result in my being bombarded with spam and hack attempts yet again. Instead I had to check for two conditions instead of one, with the extra condition being that the visitor wasn't them. To do that I also checked to see if the visitor's IP belonged to their server or not, like so:

  1. RewriteCond %{REMOTE_ADDR} !^12\.34\.567\.89$

That line of code checks to make sure that the visitor's IP is not the one listed (nb. that is just a dummy IP address rather than their actual one). If both conditions are met (not the listed IP and no user-agent string) then the visitor gets blocked. When added to the previous code we get the following:

The amended mod_rewrite code
  1. RewriteEngine On
  2. RewriteCond %{HTTP_USER_AGENT} =""
  3. RewriteCond %{REMOTE_ADDR} !^12\.34\.567\.89$
  4. RewriteRule .* - [F,L]

While that snippet of code will allow them to access my site even when they have no user-agent string in their request headers, and while there's no obligation for one to be included (as mentioned previously), I personally feel that it would be wiser for them to fix their software to ensure that it identifies itself when accessing remote servers. Not doing so means that it's quite easy to confuse them with spammers and hackers who do their best to disguise their actions and methods, and so leaves them to potentially be blocked by many other users who might take similar measures. Hopefully the support person that responded to my queries will pass the matter on to someone who will understand the issue and be able to do something about it.

Bookmark this:
  • del.icio.us
  • MisterWong
  • StumbleUpon
  • Twitter
  • Technorati
Up arrow

Z-index Oddities: Menu dropdowns overlapped by positioned elements

I use the Suckerfish Dropdowns menu quite often when building large sites and rarely have too many problems with it. However, on one of the sites I'm currently working on there have been quite a few odd bugs – though I should stress that I don't think it's specifically an issue with Suckerfish, rather with nested dropdown menus (that use absolute positioned elements within floated ones) in general.

The most common issue found with this kind of menu is that, in Internet Explorer, relative positioned elements located below the menu on a page will overlap the dropdowns, obscuring them and preventing access to the links as a result. The normal solution for this, and the one most commonly suggested, would be to use a higher z-index on the dropdown ULs. On this occasion, though, increasing the z-index of nested lists within the dropdown menu didn't have any effect – not even when setting ridiculously high z-indexes, nor when also specifically setting a lower one on the positioned element. In the end I was compelled to remove positioning from all elements where I could without it affecting anything else.

That appeared to do the trick, until it was noticed that the issue was still present on some pages of the site despite relative positioning not being involved. After further investigation I discovered that the bug was limited to data tables containing links. Though, as mentioned above, there was no positioning involved. Despite that I kept my efforts to fix the issue focused on positioning and decided to test how various positioning properties affected things. I began, and ended, with static positioning (the default) by targeting all links within tables with the following CSS:

  1. table a { position : static; }

Adding that to the bottom of my IE only style sheets fixed the issue. Thankfully! Unfortunately, things are never quite that simple where IE is concerned and I found that my fix broke an image rollover system used on 2 pages, preventing the images from displaying when the links where moused-over. To try to fix this I set all links in those 2 tables to use absolute positioning which, initially, seemed to work in this case. However, more thorough testing showed that while it fixed it for links above the page fold; all those below the fold would either vanish on mouseover or reposition themselves at the top of the table. Testing with all variations of positioning and link state proved fruitless. In the end the only answer was to fix the menu issue, and consequently the disappearing link issue, leaving IE 6 users without the thumbnail view (the use of zoom: 1; haslayout fix had no effect). Sometimes sacrifices have to be made to ensure a website remains usable for its visitors, no matter how much the site owner wants a given feature to be added.

Bookmark this:
  • del.icio.us
  • MisterWong
  • StumbleUpon
  • Twitter
  • Technorati
Up arrow