All about table


  • tutorials
  • (Updated at )

When people asking me how long it takes for webpages or small projects, I always reply depending on how the html was built, it's the part that I have no idea how long it will take. Table very straight forward and support by all GUI web development software, but most people use it in a wrong way. Table is not an old technology, it's just misunderstanding.

There are some common mistake while using table.

Hyperlink at wrong place

<table>  
<tr>  
    <td><a href="#link1">cell1</a></td>  
    <a href="#link2"><td>cell2</td></a>  
</tr>  
<a href="#link3">  
    <tr>  
        <td>cell3</td>  
        <td>cell4</td>  
    </tr>  
</a>  
</table>  
cell1 cell2
cell3 cell4

This table has 3 links but only #link1 is correct, the rest of links are wrong. #link2, #link3 want to achieve the whole <td> or <tr> could be click, not only the content inside table cell.

First thing you need to know is the <tr> is the container of <td>, it tells all the cells inside belongs to the same row, and there should not be anything between <table>, <tr> and <td> tags, everything between these tags will be move out from the table. If you ever try to put some text between them, you'll find it shows up above the table, not where you think as your source code. It's because when the html rendered in DOM Tree, it looks like this.

dom tree diagram

You may ask if this is wrong, and the result is also obviously wrong, why do people use it this way. That because it works on IE, and only on IE.

The correct way is using globe event for click action, in the same table let's replace it with onClick event.

<table>  
<tr>  
    <td><a href="#link1">cell1</a></td>  
    <td onclick="alert('#link2');">cell2</td>  
</tr>  
<tr onclick="alert('#link3');">  
    <td>cell3</td>  
    <td>cell4</td>  
</tr>  
</table>  
cell1 cell2
cell3 cell4

Since it's event, you can use Javascript to handle the event, to clean up your html code a little bit.

$('td').click(function() {  
    console.log('td clicked!');  
});  

$('tr').click(function() {  
    console.log('tr clicked!');  
});  

In real world, you will need more conditions or arguments for binding the event, from the code above it binds click event both on <tr> and <td>, since <tr> is the container of <td>, when you click on <td>, the click event on <tr> will also fired, so you will get td clicked! and tr clicked!. This includes <td> and its contents, when you click #link1 it also trigger click events on <td> and <tr>.

To avoid this, we can assign them different class then pass arguments through html5 data tag.

<table>  
    <tr>  
         <td><a href="#link1">cell1</a></td>  
         <td class="cell" data-link="#link2">cell2</td>  
    </tr>  
    <tr class="row" data-link="#link3">  
         <td>cell3</td>  
         <td>cell4</td>  
    </tr>  
</table>  

<script>  
$('td.cell').click(function() {  
    console.log('td:' + $(this).data('link'));  
});  

$('tr.row').click(function() {  
    console.log('tr:' + $(this).data('link'));  
});  
</script>  

Then there is no duplicate event binding, click on cell3 and cell4 shows tr:#link3, and click on cell2 shows td:#link2, and nothing for #link1.

Missing ul, ol tag

The same rules exists in <ul>, <ol> and <dl> list tags. Nothing should exists between <ul>, <ol>, <dl> and <li> or <dd>. And never use <li> or <dd> along, every list items should wrap with proper <ul>, <dt> tags.

Common mistakes:

wrong place for link, though it works on major browsers, but please don't do that.  
<ul>  
    <a href="#"><li>list1</li></a>  
</ul>  

missing ul, ol tags  
<li>list1</li>  
<li>list2</li>  
<li>list3</li>  

Avoid complex tables

Depending on the data of the table, you may have a few colspan or rowspan to combine cells, it's okay in this situation, what you should avoid is complex tables for layout purpose.

It's 2015, you should no longer using <table> for layout, but it doesn't mean it is wrong of doing so, it's bad but not wrong. And still lots of web pages are generate by Dreamweaver or Photoshop, it cause this kinda issues. There might be an exception for EDMs, since most of online email clients will remove parts of html and css from your contents, you will need to use <table> for layout.

Slice into different tables

<table>  
<tr>  
    <td colspan="7"></td>  
</tr>  
<tr>  
    <td colspan="7"></td>  
</tr>  
<tr>  
    <td colspan="7"></td>  
</tr>  
<tr>  
    <td colspan="3" rowspan="4"></td>  
    <td colspan="4"></td>  
</tr>  
<tr>  
    <td colspan="4"></td>  
</tr>  
<tr>  
    <td colspan="4"></td>  
</tr>  
<tr>  
    <td colspan="4"></td>  
</tr>  
<tr>  
    <td colspan="7"></td>  
</tr>  
<tr>  
    <td colspan="7"></td>  
</tr>  
<tr>  
    <td>1</td>  
    <td>2</td>  
    <td>3</td>  
    <td>4</td>  
    <td>5</td>  
    <td>6</td>  
</tr>  
</table>  
1 2 3 4 5 6

This is common, it only needs several cells in one row, then use lots of colspan to combine cells for the rest of rows. Someday later the last row changes to 6 cells, but chances are you forgot to update the colspan in other rows or something like that, it won't make any difference if the number of colspan still bigger then maximum cells in each row.

When you feel like to use colspan or rowspan or some rows are very different then others, then you should consider to slice your table into several tables.

<table>  
<tr><td></td></tr>  
<tr><td></td></tr>  
<tr><td></td></tr>  
<tr>  
    <td>  
        <table width="100%">  
        <tr>  
            <td width="50%"></td>  
            <td width="50%">  
                <table width="100%">  
                <tr><td></td></tr>  
                <tr><td></td></tr>  
                <tr><td></td></tr>  
                <tr><td></td></tr>  
                <tr><td></td></tr>  
                <tr><td></td></tr>  
                <tr><td></td></tr>  
                </table>  
            </td>  
        </tr>  
        </table>  
    </td>  
</tr>  
<tr><td></td></tr>  
<tr><td></td></tr>  
<tr>  
    <td>  
        <table width="100%">  
        <tr>  
            <td>1</td>  
            <td>2</td>  
            <td>3</td>  
            <td>4</td>  
            <td>5</td>  
            <td>6</td>  
            <td>7</td>  
            <td>8</td>  
            <td>9</td>  
        </tr>  
        </table>  
    </td>  
</tr>  
</table>  
1 2 3 4 5 6 7 8 9

By slicing parts of cells that cause other cells need to use rowspan or colspan into small tables, make it simple relationship and easy to read html code. The code is a little bit longer but it reduce the dependence of cells.

If you do this, developer will hate you

<table>  
<tr>  
    <td><input type="radio"></td>  
    <td>Lorem ipsum</td>  
</tr>  
<tr>  
    <td colspan="2">Choose one or two options:</td>  
<tr>  
<tr>  
    <td></td>  
    <td>  
        <input type="checkbox"> option1<br>  
    </td>  
</tr>  
<tr>  
    <td></td>  
    <td>  
        <input type="checkbox"> option2<br>  
    </td>  
</tr>  
<tr>  
    <td></td>  
    <td>  
        <input type="checkbox"> option3<br>  
    </td>  
</tr>  
</table>  
Lorem ipsum
Choose one or two options:
option1
option2
option3

This section is mostly related to user interface design and the organization of html, it ain't just happened in table. When using radio or checkbox, normally you should wrap it with <label> to make some text also clickable and select the box just for better user experience. When you separate the <input> and text into different parent then you'll need to specify the id for both elements.

<tr>  
    <td><input type="radio" id="option1"></td>  
    <td><label for="option1">Lorem ipsum</label></td>  
</tr>  

It's legal syntax and usage, but you have to assign id for each <input> elements and its <label>. Once you have a large form, then you have a lot work to do. It's a boring process and makes the html source code hard to read.

<tr>  
    <td></td>  
    <td><label><input type="radio"> Lorem ipsum</label></td>  
</tr>  

Simply put these two element beside each other and wrap with <label> also archive the same effect. And you can save a lot of space of html code and a lot of time for your life.

Let's go deeper, assume we have some rules of minimum and maximum selections for those checkboxes. You'll need some Javascript for validation. Let's add some information to the code for this example.

<tr>  
    <td></td>  
    <td>  
        <label><input type="checkbox" name="question[]" value="1"> option1</label><br>  
    </td>  
</tr>  
<tr>  
    <td></td>  
    <td>  
        <label><input type="checkbox" name="question[]" value="2"> option2</label><br>  
    </td>  
</tr>  
<tr>  
    <td></td>  
    <td>  
        <label><input type="checkbox" name="question[]" value="3"> option3</label><br>  
    </td>  
</tr>  

We wrap each checkbox with <label> and assign the name to "question[]", it make the values of selected checkboxes like an array for both Javascript and PHP.

// find all selected input elements with type of checkbox and name is "question[]"  
var $questionElements = $("input[type=checkbox][name='question[]']:checked");  
if ($questionElements.length < 1 || $questionElements.length > 2 ) {  
    // if the number of elements less then 1 or greater then 2  
    // cancel the submission and perform some feedback  
    return false;  
}  

The html code is okay and the javascript is simple, you said. Imaging we have multiple questions like this, both html and javascript code will be a mess. hmm, Let's make it better, shall we?

<tr>  
    <td>  
        <p>Question 1:</p>  
        <div class="checkbox-group" data-min="1" data-max="2">  
            <label><input type="checkbox" name="question[1][]" value="1"> option1</label><br>  
            <label><input type="checkbox" name="question[1][]" value="2"> option2</label><br>  
            <label><input type="checkbox" name="question[1][]" value="3"> option3</label><br>  
            <label><input type="checkbox" name="question[1][]" value="4"> option4</label><br>  
        </div>  
    </td>  
</tr>  
<tr>  
    <td>  
        <p>Question 2:</p>  
        <div class="checkbox-group" data-min="2" data-max="5">  
            <label><input type="checkbox" name="question[2][]" value="1"> option1</label><br>  
            <label><input type="checkbox" name="question[2][]" value="2"> option2</label><br>  
            <label><input type="checkbox" name="question[2][]" value="3"> option3</label><br>  
            <label><input type="checkbox" name="question[2][]" value="4"> option4</label><br>  
            <label><input type="checkbox" name="question[2][]" value="5"> option5</label><br>  
            <label><input type="checkbox" name="question[2][]" value="6"> option6</label><br>  
        </div>  
    </td>  
</tr>  

By properly grouping elements into several <div> and add some more informations. We use two html5 data attributes for the minimum and maximum selection argument, it was design for validation javascript codes, but it also helps you to know what might happen without reading the javascript codes. .checkbox-group is one way to let javascript make css selection easier. You now have a well organization and self document html codes.

// assume it starts with valid state  
var valid = true;  

$(".checkbox-group").each(function() {  
    var $group = $(this);  
    var min = parseInt($group.data('min'), 10);  
    var max = parseInt($group.data('max'), 10);  

    // find all selected input elements with type of checkbox inside $group  
    var $elements = $group.find("[type=checkbox]:checked");  
    if ($elements.length < min || $elements.length > max) {  
       // once it's invalid, then set the valid flag to false  
        valid = false;  
        // exit the $(".checkbox-group").each() loop without check the rest of elements  
        return false;  
    }  
});  

if (!valid) {  
    // validation failed, cancel this submission, prompt some feedback  
    return false;  
}  

Don't fear for the code just yet, it is pretty much the same as previous javascript. We do the validation for each .checkbox-group, get the min, max variables from data attributes, check if then number of selected elements is valid. During the validation, we don't care about who exactly is, we don't need to hard code the minimum, maximum conditions, and the code is reusable with properly html codes.

Garbage in garbage out

Well organized html codes is not about syntax, it doesn't mean <div> is always right and <table> is always wrong. It's about properly organizing elements by its attributes or meanings, make the html code readable, make it easy to styling and easy to write good javascript programs.

Let's have a more complex and real example that includes html, css and php. Say we have some products, each item has a image, name and description. And you have a terrible table like this.

<table>  
<tr>  
    <td>img1</td>  
    <td>img2</td>  
    <td>img3</td>  
    <td>img4</td>  
</tr>  
<tr>  
    <td>name1</td>  
    <td>name2</td>  
    <td>name3</td>  
    <td>name4</td>  
</tr>  
<tr>  
    <td>text1</td>  
    <td>text2</td>  
    <td>text3</td>  
    <td>text4</td>  
</tr>  
<tr><td colspan="4"></td></tr>  
<tr>  
    <td>img5</td>  
    <td>img6</td>  
    <td>img7</td>  
    <td>img8</td>  
</tr>  
<tr>  
    <td>name5</td>  
    <td>name6</td>  
    <td>name7</td>  
    <td>name8</td>  
</tr>  
<tr>  
    <td>text5</td>  
    <td>text6</td>  
    <td>text7</td>  
    <td>text8</td>  
</tr>  
<tr><td colspan="4"></td></tr>  
<tr>  
    <td>img9</td>  
    <td></td>  
    <td></td>  
    <td></td>  
</tr>  
<tr>  
    <td>name9</td>  
    <td></td>  
    <td></td>  
    <td></td>  
</tr>  
<tr>  
    <td>text9</td>  
    <td></td>  
    <td></td>  
    <td></td>  
</tr>  
</table>  
img1 img2 img3 img4
name1 name2 name3 name4
text1 text2 text3 text4
img5 img6 img7 img8
name5 name6 name7 name8
text5 text6 text7 text8
img9
name9
text9

To generate this table by code, we need to calculate or define the number of rows and columns, follow by the html structure of the table, go through the products data, output the values. Output empty cell when there's no data from the given list. After output every row, check if it need to output an empty row for spacing.

How to output the table as the html above is the most complex part in this case. It needs to output the value of image of 4 items, then output 4 values of name in another row, then 4 values of description in another row, once it's done then go for the next 4 items. It does not process products data one by one.

<table>  
<?php  
// products data  
$items = [  
    ['image' => 'img1', 'name' => 'name1', 'description' => 'text1'],  
    ['image' => 'img2', 'name' => 'name2', 'description' => 'text2'],  
    ['image' => 'img3', 'name' => 'name3', 'description' => 'text3'],  
    ['image' => 'img4', 'name' => 'name4', 'description' => 'text4'],  
    ['image' => 'img5', 'name' => 'name5', 'description' => 'text5'],  
    ['image' => 'img6', 'name' => 'name6', 'description' => 'text6'],  
    ['image' => 'img7', 'name' => 'name7', 'description' => 'text7'],  
    ['image' => 'img8', 'name' => 'name8', 'description' => 'text8'],  
    ['image' => 'img9', 'name' => 'name9', 'description' => 'text9'],  
];  

// define the number of items in a row  
$numberOfItemsPerRow = 4;  

// field names of product item  
$keysForItem = array('image', 'name', 'description');  
// calculate the number of values of an item, it's 3 in this case  
$numberOfValuesPerItem = count($keysForItem);  
// calculate the number of total product items, it's 9  
$totalItems = count($items);  
// calculate the number of rows by round fractions up, ceil(9 / 4) = 3  
$totalRows = ceil($totalItems / $numberOfItemsPerRow);  
// an index for which item to access  
$index = 0;  

// first loop is how many rows to output from top to bottom  
// the reference index of array form programming usually start by 0  
// so here the result of variable $i will be 0, 1, 2  
for ($i = 0; $i < $totalRows; $i++) {  

    // how many values of an item for process  
    // again, start by 0, the values of $i will be 0, 1, 2  
    for ($j=0; $j < $numberOfValuesPerItem; $j++) {  

        // to calculate the index for items, it will run 3 x 3 = 9 times in this loop  
        // it start by the first item those belongs of a row every time,  
        // the result of $index will be  
        // ($i = 0) 0, 0, 0  
        // ($i = 1) 4, 4, 4  
        // ($i = 2) 8, 8, 8  
        $index = $i * $numberOfItemsPerRow;  

        // get the output field name of current row, the result of $key will be  
        // ($i = 0) image, name, description  
        // ($i = 1) image, name, description  
        // ($i = 2) image, name, description  
        $key = $keysForItem[$j];  

        echo "<tr>";  

        // from left to right, prepare the data for output  

        // how many items for a row, the result of $z will be 0, 1, 2, 3  
        // it runs 3 x 3 x 4 = 36 times in this loop  
        for ($z=0; $z < $numberOfItemsPerRow; $z++) {  

            // the result of $index in each loop will be  
            // ($i = 0, $j = 0, $key = image)           0, 1, 2, 3  
            // ($i = 0, $j = 1, $key = name)            0, 1, 2, 3  
            // ($i = 0, $j = 2, $key = description)     0, 1, 2, 3  
            // ($i = 1, $j = 0, $key = image)           4, 5, 6, 7  
            // ($i = 1, $j = 1, $key = name)            4, 5, 6, 7  
            // ($i = 1, $j = 2, $key = description)     4, 5, 6, 7  
            // ($i = 2, $j = 0, $key = image)           8, 9, 10, 11  
            // ($i = 2, $j = 1, $key = name)            8, 9, 10, 11  
            // ($i = 2, $j = 2, $key = description)     8, 9, 10, 11  

            if (isset($items[$index])) {  
                // if item exists, output the value by $key from the $index-th item  
                echo "<td>" . $items[$index][$key] . "</td>";  
            }  
            else {  
                // if no data exists, output an empty cell  
                echo "<td></td>";  
            }  
            // plus 1 for the $index of next item  
            $index++;  
        }  
        echo "</tr>";  
    }  

    // check if we need an empty row after every 4 items  
    // the result of $index here will be 4, 8, 12  
    if ($index < $totalItems) {  
        echo '<tr><td colspan="' . $numberOfItemsPerRow. '"></td></tr>';  
    }  
}  
?>  
</table>  

It may looks more complex if you don't familiar of coding, but it is also not easy for me after couple hours later. Because it is correct logic for the table but not usual logic for data process. It's will take time to understand what the code really did every time. How to write the code is not the subject here, but you can still see there are only 5 lines of code that output the html and it runs 36 times for just 9 records. This table takes a lot of php codes just for properly generate the html tags, it also makes the table almost impossible to change without modify the code.

To do this in the a better way, we should regain its position, let html be the data structure, php for data processing and css for layouts. Since it's all about table in this article, we'll still use <table> but use it in better place.

<div class="items-wrapper">  
    <div class="item-box">  
        <table>  
        <tr><td>image</td></tr>  
        <tr><td>name</td></tr>  
        <tr><td>description</td></tr>  
        </table>  
    </div>  
    <div class="item-box">  
        <table>  
        <tr><td>image</td></tr>  
        <tr><td>name</td></tr>  
        <tr><td>description</td></tr>  
        </table>  
    </div>  
</div>  

Start by setting up the html, use <table> for each product item and wrap with div.item-box, that makes the html clean but still understandable. These two css classes are not just for styling purpose but also like comments for elements, try to make it short and meaningful.

<div class="items-wrapper">  
    <?php foreach($items as $item): ?>  
    <div class="item-box">  
        <table>  
        <tr><td><?php echo $item['image']; ?></td></tr>  
        <tr><td><?php echo $item['name']; ?></td></tr>  
        <tr><td><?php echo $item['description']; ?></td></tr>  
        </table>  
    </div>  
    <?php endforeach; ?>  
</div>  

It's only 5 lines of code and that's all we need. The html stays almost the same because it doesn't need php to handle html tags or layouts. Also in this syntax, it won't break the result when preview without running php codes. Next, we add some css to styling for the layout.

.items-wrapper {  
    width:100%;  
}  
.items-wrapper:after {  
    content:"";  
    display:table;  
    clear:both;  
}  
.item-box {  
    width:25%;  
    float:left;  
    margin-bottom:10px;  
}  

Each item takes 25% width of .items-wrapper and float to left, it works just like 4 <td> in a <tr> in <table>, changes the width to achieve how many number of items in a row, margin-bottom makes the spacing between each row. Not like <table>, you don't need to fill up empty cells if not enough given data for a certain row, but you do need a clearfix effect otherwise the following elements will also float to left then overlap with .items-wrapper and that is what .items-wrapper:after does, it clears the float effects.

<div class="items-wrapper">  
    <div class="item-box">  
        <table>  
        <tr><td>img1</td></tr>  
        <tr><td>name1</td></tr>  
        <tr><td>text1</td></tr>  
        </table>  
    </div>  
    <div class="item-box">  
        <table>  
        <tr><td>img2</td></tr>  
        <tr><td>name2</td></tr>  
        <tr><td>text2</td></tr>  
        </table>  
    </div>  
    <div class="item-box">  
        <table>  
        <tr><td>img3</td></tr>  
        <tr><td>name3</td></tr>  
        <tr><td>text3</td></tr>  
        </table>  
    </div>  
    <div class="item-box">  
        <table>  
        <tr><td>img4</td></tr>  
        <tr><td>name4</td></tr>  
        <tr><td>text4</td></tr>  
        </table>  
    </div>  
    <div class="item-box">  
        <table>  
        <tr><td>img5</td></tr>  
        <tr><td>name5</td></tr>  
        <tr><td>text5</td></tr>  
        </table>  
    </div>  
    <div class="item-box">  
        <table>  
        <tr><td>img6</td></tr>  
        <tr><td>name6</td></tr>  
        <tr><td>text6</td></tr>  
        </table>  
    </div>  
    <div class="item-box">  
        <table>  
        <tr><td>img7</td></tr>  
        <tr><td>name7</td></tr>  
        <tr><td>text7</td></tr>  
        </table>  
    </div>  
    <div class="item-box">  
        <table>  
        <tr><td>img8</td></tr>  
        <tr><td>name8</td></tr>  
        <tr><td>text8</td></tr>  
        </table>  
    </div>  
    <div class="item-box">  
        <table>  
        <tr><td>img9</td></tr>  
        <tr><td>name9</td></tr>  
        <tr><td>text9</td></tr>  
        </table>  
    </div>  
</div>  
<p>end of products</p>  
img1
name1
text1
img2
name2
text2
img3
name3
text3
img4
name4
text4
img5
name5
text5
img6
name6
text6
img7
name7
text7
img8
name8
text8
img9
name9
text9

end of products

Please

Being a web designer you may not need to expert at javascript or php, but still it's your job to make properly html and css contents, otherwise you are just a graphic designer or people who knows photoshop, it's the same as web developers. And most of these problems are not technical but it cause technical issues which should be prevent from the beginning.

0 comment, 0 pingback