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.
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>
|
|||||||||
|
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.