Theming 101 – The theme_table function

I come to you as one reformed. I will no longer use foreach loops to build tables. At first, I did not know about theme_table. Then I couldn't be bothered to learn about it. Then, after feedback from people whose opinion I respect, I felt compelled to bite the bullet. I wish I hadn't waited so long.

Overview

As the name suggests, theme_table is a Drupal theming function for creating tables. It takes arrays holding the table data and generates the HTML for displaying the table. At their simplest, the input arrays hold text elements. At their most complex, the arrays hold arrays which hold arrays. The various arrays all hold data appropriate to their location, so this isn't as bad as it sounds.

As with all Drupal functions, there's documentation at api.drupal.org. For the Drupal 5 theme_table function specifically: api.drupal.org/api/5/function/theme_table. There's enough there to get you started, but you still have to think a bit. (Which is to say, I had to. Think a bit, that is.) Thinking should be reserved for the problem at hand, not figuring out Drupal, so here's my "overflowing with examples so you can cut and paste" explanation of theme_table.

The Examples

In each section an example of the PHP code, the HTML output, and the actual table are shown. The HTML output is formatted to fit on the page. The PHP code is deliberately verbose for explanatory purposes. It can be pasted directly into a Drupal node set to the PHP Code input format if you wish to experiment.

The use of inline style is for demonstration only, and not the recommended way to style a table. Use a CSS style sheet and class selectors in the real world.

These examples also show how the Drupal default CSS can impact table styling.

A Simple Table

A very simple table can be defined as follows.

PHP

<?php
// Table Header
$header = array( 'Header 1', 'Header 2', 'Header 3');

// Table Rows
$row[]  = array( 'Row 1 / Column 1', 'Row 1 / Column 2', 'Row 1 / Column 3');
$row[]  = array( 'Row 2 / Column 1', 'Row 2 / Column 2', 'Row 2 / Column 3');

// Render Table
$tableHTML = theme('table', $header, $row);

print
'<pre>' . htmlentities($tableHTML) . '</pre>';
print
$tableHTML;
?>

Resulting Table (Displayed)

Header 1 Header 2 Header 3
Row 1 / Column 1 Row 1 / Column 2 Row 1 / Column 3
Row 2 / Column 1 Row 2 / Column 2 Row 2 / Column 3

Resulting Table (HTML)

<table>
  <thead>
    <tr>
      <th>Header 1</th> <th>Header 2</th> <th>Header 3</th>
    </tr>
  </thead>
  <tbody>
    <tr class="odd">
      <td>Row 1 / Column 1</td> <td>Row 1 / Column 2</td> <td>Row 1 / Column 3</td>
    </tr>
    <tr class="even">
      <td>Row 2 / Column 1</td> <td>Row 2 / Column 2</td> <td>Row 2 / Column 3</td>
    </tr>
  </tbody>
</table>

The cell's position in the array determines its position in the table.

Table (<table>) Attributes

Attributes are added to the table tag by passing an additional associative array to theme_table to the theme function. Each array element is a table tag attribute, with the array key containing the attribute name and the array value containing the attribute value. We'll expand the previous example by adding attributes for border, class, cellspacing and cellpadding.

PHP

<?php
// Table Attributes
$attributes = array(
 
'border'      => 1,
 
'cellspacing' => 0,
 
'cellpadding' => 5,
 
'class'       => 'example'
);

// Table Header
$header = array( 'Header 1', 'Header 2', 'Header 3');

// Table Rows
$row[]  = array( 'Row 1 / Column 1', 'Row 1 / Column 2', 'Row 1 / Column 3');
$row[]  = array( 'Row 2 / Column 1', 'Row 2 / Column 2', 'Row 2 / Column 3');

// Render Table
$tableHTML = theme('table', $header, $row, $attributes);

print
'<pre>' . htmlentities($tableHTML) . '</pre>';
print
$tableHTML;
?>

Resulting Table (Displayed)

Header 1Header 2Header 3
Row 1 / Column 1Row 1 / Column 2Row 1 / Column 3
Row 2 / Column 1Row 2 / Column 2Row 2 / Column 3

Resulting Table (HTML)

<table border="1" cellspacing="0" cellpadding="5" class="example">
 <thead>
  <tr>
   <th>Header 1</th><th>Header 2</th><th>Header 3</th>
  </tr>
 </thead>
 <tbody>
  <tr class="odd">
   <td>Row 1 / Column 1</td><td>Row 1 / Column 2</td><td>Row 1 / Column 3</td>
  </tr>
  <tr class="even">
   <td>Row 2 / Column 1</td><td>Row 2 / Column 2</td><td>Row 2 / Column 3</td>
  </tr>
 </tbody>
</table>

Header Cell (<th>) Attributes

Attributes are added to the header cells by changing the text entry to an associative array. The title text moves into a key/value pair with a key name of 'data'. For example, the following two statements produce the same results:

<?php
    $header
= array(
     
'Title 1',
     
'Title 2'
   
);

   
$header = array(
      array(
'data'=>'Title 1'),
      array(
'data'=>'Title 2')
    );
?>

The tag attributes are defined as additional key/value entries in the array. As with the attribute array of the previous example, the array element key is the attribute name and array element value is the attribute value. In our running example we'll add a CSS style to the column 1 and 2 titles. Note that the array entries can be mixed, they can be either simple text or an array.

PHP

<?php
// Table Attributes
$attributes = array(
 
'border'      => 1,
 
'cellspacing' => 0,
 
'cellpadding' => 5,
 
'class'       => 'example'
);

// Table Header
$header_title_1 = array('data' => 'Header 1', 'style' => 'color: red;');
$header_title_2 = array('data' => 'Header 2', 'style' => 'color: blue;');
$header_title_3 = 'Header 3';
$header = array( $header_title_1, $header_title_2$header_title_3);

// Table Rows
$row[]  = array( 'Row 1 / Column 1', 'Row 1 / Column 2', 'Row 1 / Column 3');
$row[]  = array( 'Row 2 / Column 1', 'Row 2 / Column 2', 'Row 2 / Column 3');

// Render Table
$tableHTML = theme('table', $header, $row, $attributes);

print
'<pre>' . htmlentities($tableHTML) . '</pre>';
print
$tableHTML;
?>

Resulting Table (Displayed)

Header 1 Header 2 Header 3
Row 1 / Column 1Row 1 / Column 2Row 1 / Column 3
Row 2 / Column 1Row 2 / Column 2Row 2 / Column 3

Resulting Table (HTML)

<table border="1" cellspacing="0" cellpadding="5" class="example">
 <thead>
  <tr>
   <th style="color: red;">Header 1</th>
   <th style="color: blue;">Header 2</th>
   <th>Header 3</th>
  </tr>
 </thead>
 <tbody>
  <tr class="odd">
   <td>Row 1 / Column 1</td><td>Row 1 / Column 2</td><td>Row 1 / Column 3</td>
  </tr>
  <tr class="even">
   <td>Row 2 / Column 1</td><td>Row 2 / Column 2</td><td>Row 2 / Column 3</td>
  </tr>
 </tbody>
</table>

Table Row (<tr>) Attributes

The same method is used to add attributes to individual rows. An associative array is created with the row data in an element with the key name 'data'. The attribute information is stored as key/value pairs.

In this example we'll add a style attribute to change the background colour.

There are a number of things to note. theme_table automatically adds class="even" and class="odd" to the row tags. In our example, one of the attributes is class="example". You'll note that theme_table combines the classes resulting in class="example odd". Once again notice how we can mix the simple and more complex arrays in the row.

PHP

<?php
// Table tag attributes
$attributes = array(
 
'border'      => 1,
 
'cellspacing' => 0,
 
'cellpadding' => 5,
 
'class'       => 'example'
);

// Table Header
$header_title_1 = array('data' => 'Header 1', 'style' => 'color: red;');
$header_title_2 = array('data' => 'Header 2', 'style' => 'color: blue;');
$header_title_3 = 'Header 3';
$header = array( $header_title_1, $header_title_2$header_title_3);

// Table Rows
$row1_values = array( 'Row 1 / Column 1', 'Row 1 / Column 2', 'Row 1 / Column 3');
$row[]  = array(
 
'data'  => $row1_values,
 
'style' => 'background-color: LightGrey;',
 
'class' => 'example'
);
$row[]  = array( 'Row 2 / Column 1', 'Row 2 / Column 2', 'Row 2 / Column 3');

// Render Table
$tableHTML = theme('table', $header, $row, $attributes);

print
'<pre>' . htmlentities($tableHTML) . '</pre>';
print
$tableHTML;
?>

Resulting Table (Displayed)

Header 1 Header 2 Header 3
Row 1 / Column 1Row 1 / Column 2Row 1 / Column 3
Row 2 / Column 1Row 2 / Column 2Row 2 / Column 3

Resulting Table (HTML)

<table border="1" cellspacing="0" cellpadding="5" class="example">
 <thead>
  <tr>
   <th style="color: red;">Header 1</th>
   <th style="color: blue;">Header 2</th>
   <th>Header 3</th>
  </tr>
 </thead>
 <tbody>
  <tr style="background-color: LightGrey;" class="example odd">
   <td>Row 1 / Column 1</td><td>Row 1 / Column 2</td><td>Row 1 / Column 3</td>
  </tr>
  <tr class="even">
   <td>Row 2 / Column 1</td><td>Row 2 / Column 2</td><td>Row 2 / Column 3</td>
  </tr>
 </tbody>
</table>

Table Cell (<td>) Attributes

By this time I'm sure you already know how to add table cell attributes. An associative array is created with the cell data in an array element whose key name is 'data' and with the attributes as key/value pairs. In this example, we'll add style attributes to each cell, and a class attribute to the middle cell.

PHP

<?php
// Table tag attributes
$attributes = array(
 
'border'      => 1,
 
'cellspacing' => 0,
 
'cellpadding' => 5,
 
'class'       => 'example'
);

// Table Header
$header_title_1 = array('data' => 'Header 1', 'style' => 'color: red;');
$header_title_2 = array('data' => 'Header 2', 'style' => 'color: blue;');
$header_title_3 = 'Header 3';
$header = array( $header_title_1, $header_title_2$header_title_3);

// Table Rows
$row1_cell1 = array(
 
'data' => 'Row 1 / Column 1',
 
'style' => 'color: Navy;'
);
$row1_cell2 = array(
 
'data' => 'Row 1 / Column 2',
 
'style' => 'color: SeaGreen;',
 
'class' => 'middle-row'
);
$row1_cell3 = 'Row 1 / Column 3';

$row1_values = array($row1_cell1, $row1_cell2, $row1_cell3);

$row[]  = array(
 
'data'  => $row1_values,
 
'style' => 'background-color: LightGrey;'
);
$row[]  = array( 'Row 2 / Column 1', 'Row 2 / Column 2', 'Row 2 / Column 3');

// Render Table
$tableHTML = theme('table', $header, $row, $attributes);

print
$tableHTML;
?>

Resulting Table (Displayed)

Header 1 Header 2 Header 3
Row 1 / Column 1 Row 1 / Column 2 Row 1 / Column 3
Row 2 / Column 1 Row 2 / Column 2 Row 2 / Column 3

Resulting Table (HTML)

<table border="1" cellspacing="0" cellpadding="5" class="example">
 <thead>
  <tr>
   <th style="color: red;">Header 1</th>
   <th style="color: blue;">Header 2</th>
   <th>Header 3</th>
  </tr>
 </thead>
 <tbody>
  <tr style="background-color: LightGrey;" class="odd">
   <td style="color: Navy;">Row 1 / Column 1</td>
   <td style="color: SeaGreen;" class="middle-row">Row 1 / Column 2</td>
   <td>Row 1 / Column 3</td>
  </tr>
  <tr class="even">
   <td>Row 2 / Column 1</td>
   <td>Row 2 / Column 2</td>
   <td>Row 2 / Column 3</td>
  </tr>
 </tbody>
</table>

Conclusion

Although a little involved at first, theme_table quickly becomes straight forward as one recognizes how the data is defined. Using theme_table removes the fiddly and sometimes complex HTML construction loops making code more readable, maintainable and faster to write.

Comments

For the Drupal 5 theme_table function specifically: api.drupal.org/api/5/function/theme_table. There's enough there to get you started, but you still have to think a bit.

Please submit a patch to improve the theme_table documentation for Drupal 6. Thanks! :)

The API docs make my head hurt. Not because they aren't accurate but because they're so concise and sans examples.

Thanks so much for spelling this out for me.

Kevin

Thanks very much for this excellent explanation. It's just what I'd been looking for.
It's great when an explanation starts from the most basic example so that beginners can follow along.

Thank you very much for the detailed explanation of how to handle theme_table function.

When you visit themes page (admin/build/themes) you see a normal table (as in your examples) but when you visit a users page (admin/user/user) the table headers are shown as sortable. How and when drupal applies this sortable feature to the table headers? How can we tell drupal to apply the sortable feature?

Thanks in advance.

Thanks Dale for this, its starting to make sense now after months of searching , would it be possible to explain how I could render elements such as Fieldsets and text boxes in the table, I am trying to work out how I would build a table with something like this

Course Module Date Course Taken Date Course Renewal due

Hope you can help

Thanks
Nick

Thanks a lot for these explanations ! It helped me a lot !

is there any way to define complex header in drupal 5 such like this:

Name
Contact
Action

Phone
Email
Messenger

Since I couldn't find a viable solution for tables with more than one table headers, I wrote the following snippet myself. This solution has worked well for me! Please feel free to improve the code.

Paste the below code into your template.php file. Replace THEME with your theme prefix. Don't forget to flush your cache after saving changes in template.php!

Whenever you'd like to include additional rows, add a value for $rows_multiple other than NULL to your theme() function. (e.g. theme('table', $header, $row, $attributes, $caption, 'multiple')).

Your $header variable will now be able to take multiple arrays (see example below).

This would be part of your module.

$header[] = array('Hello','World');
$header[] = array('Bye', 'World');

Paste the following into your theme's template.php.
<?php
/**
* Modification of theme_table
*/
function THEME_table($header, $rows, $attributes = array(), $caption = NULL, $rows_multiple = NULL) {

// Add sticky headers, if applicable.
if (count($header)) {
drupal_add_js('misc/tableheader.js');
// Add 'sticky-enabled' class to the table to identify it for JS.
// This is needed to target tables constructed by this function.
$attributes['class'] = empty($attributes['class']) ? 'sticky-enabled' : ($attributes['class'] .' sticky-enabled');
}

$output = '\n";

if (isset($caption)) {
$output .= ''. $caption ."\n";
}

// Multiple header rows
if(!$rows_multiple == NULL){
$thead_set = '';
// Format the table header:
if (count($header)) {
foreach($header as $number => $head){
$ts = tablesort_init($head);
// HTML requires that the thead tag has tr tags in it followed by tbody
// tags. Using if clause to check and see if we have any rows and whether
// the thead tag is already open
if(count($rows) && $thead_set != 1){
$output .= ' ';
$thead_set = 1;
}else{
$output .= ' ';
}
//$output .= (count($rows) ? ' ' : ' ');
foreach ($head as $cell) {
$cell = tablesort_header($cell, $head, $ts);
$output .= _theme_table_cell($cell, TRUE);
}
}
// Using ternary operator to close the tags based on whether or not there are rows
$output .= (count($rows) ? " \n" : "\n");
}
else {
$ts = array();
}
// One header row
}else{
// Format the table header:
if (count($header)) {
$ts = tablesort_init($header);
// HTML requires that the thead tag has tr tags in it followed by tbody
// tags. Using ternary operator to check and see if we have any rows.
$output .= (count($rows) ? ' ' : ' ');
foreach ($header as $cell) {
$cell = tablesort_header($cell, $header, $ts);
$output .= _theme_table_cell($cell, TRUE);
}
// Using ternary operator to close the tags based on whether or not there are rows
$output .= (count($rows) ? " \n" : "\n");
}
else {
$ts = array();
}
}

// Format the table rows:
if (count($rows)) {
$output .= "\n";
$flip = array('even' => 'odd', 'odd' => 'even');
$class = 'even';
foreach ($rows as $number => $row) {
$attributes = array();

// Check if we're dealing with a simple or complex row
if (isset($row['data'])) {
foreach ($row as $key => $value) {
if ($key == 'data') {
$cells = $value;
}
else {
$attributes[$key] = $value;
}
}
}
else {
$cells = $row;
}
if (count($cells)) {
// Add odd/even class
$class = $flip[$class];
if (isset($attributes['class'])) {
$attributes['class'] .= ' '. $class;
}
else {
$attributes['class'] = $class;
}

// Build row
$output .= ' ';
$i = 0;
foreach ($cells as $cell) {
$cell = tablesort_cell($cell, $header, $ts, $i++);
$output .= _theme_table_cell($cell);
}
$output .= " \n";
}
}
$output .= "\n";
}

$output .= "\n";
return $output;
}
?>

The example above works fine with Drupal 6. I haven't tested it with Drupal 5.

thanks..

Very nice tutorial !!!
It helped me a lot...

Thanks a lot :)