Last updated March 28, 2012. Created by NancyDru on July 4, 2007.
Edited by drupalshrek, Senpai, dmitrig01, add1sun. Log in to edit this page.

Introduction

This is a brief tutorial on how to use built-in functions to make a table in Drupal. Some of the code in this tutorial is not properly presented in the Drupal Way, but it should give an idea of what to do, and it works as written unless you're using the weight.module on your site.

Creating The First Table

The Task

We want to create a page that shows some information about all of our content (okay, nodes) in a tabular format. We need the stuff that is promoted to the front page and sticky listed first.

Now we all know that the boss who requested this didn't specify a lot of things he really wants. So we'll start with what he asked for, then embellish it when he starts saying, "But I need this column done this way..." We've all been there, even if we are that boss.

<?php
  $header
= array('Node ID', 'Title', 'Type', 'Terms', 'Created', 'Published', 'Sticky', 'Promoted');
 
$rows = array();
 
$no_yes = array('No', 'Yes');
 
$results = db_query("SELECT * FROM {node} ORDER BY promote DESC, sticky DESC, created DESC");
  while (
$node = db_fetch_object($results)) {
   
$termlist = taxonomy_node_get_terms($node->nid);
   
$terms = array();
    foreach (
$termlist as $key => $value) { $terms[] = $value->name; }
   
$rows[] = array($node->nid,
                   
l($node->title, 'node/'. $node->nid .'/edit'),
                   
$node->type,
                   
implode(' | ', $terms),
                   
format_date($node->created),
                   
$no_yes[$node->status],
                   
$no_yes[$node->sticky],
                   
$no_yes[$node->promote],
                );
   }
  return
theme('table', $header, $rows);
?>


The Basics

Okay that produces the table he asked for, so let's look at what we did.

  • theme('table', $header, $rows) - This invokes the Drupal function to create a table; it's pretty simple to build nice looking tables with it.
  • We used the form "theme('table'..." (as opposed to "theme_table(...") so that our site's theme can be used beyond what we've done here.

  • $header is an array ($header = array('Node ID', 'Title', ...)) that we built above to contain the titles for the columns.
  • $rows is an array ($rows[] = array($node->nid,...)) whose elements we built for each row that was returned from the query.

Uh-oh, here comes the boss! "Nancy, this looks sort of like what I wanted, but can you center the node ID, published, sticky, and promoted columns, please?"

Additional Row Attributes

"Sure, boss, no problem." I know how to do this, but I won't let him know how simple it is, so I'll go get a cup of coffee and visit with my friend Sheila down the hall first.

All we have to do is add an extra attribute to each of those cells. Here are the lines we've changed.

<?php
    $rows
[] = array(array('data' => $node->nid, 'align' => 'center'),
                    array(
'data' => $no_yes[$node->status], 'align' => 'center'),
                    array(
'data' => $no_yes[$node->sticky], 'align' => 'center'),
                    array(
'data' => $no_yes[$node->promote], 'align' => 'center'),
?>

Notice that we can do this by changing each of them to an array and adding an "align" attribute. The actual data is identified with the key "data" and the value is assigned with the array assignment operator "=>". Then we specify each HTML table attribute we want to add, in this case, the alignment element.

[Warning: Some themes will not honor the alignment attribute, but that's easy to fix by using a "class" attribute.]

Separating Rows

An hour later, my boss finds me conferring with Sheila. "Hey, Nancy, this is getting pretty close to what I want, but can you do something to separate the rows? You know like Barney did on that other one where he used a different color. Maybe he can tell you how he did it because it took a lot of time."

After he walks away, I snicker to Sheila, "He doesn't know that table rows are automatically given alternating classes so all I have to do is change the style sheet."

Sheila reminds me, "Yeah, but make sure you don't mess up all the other tables on the site."

That's not a problem, we'll just give this table a different name before we change the CSS. We'll name the table for my boss, Ralph. Here's the first step, where we add an attribute to the whole table.

<?php
  $table_attributes
= array('id' => 'ralphs-node-table');
  return
theme('table', $header, $rows, $table_attributes);
?>

As you can see, there is a another parameter that we've added to the theme function. It tells Theme to add this attribute to the "table" tag.

As I said to Sheila, the theme_table function automatically adds 'class="odd"' and 'class="even"' to the rows on an alternating basis. So now we can go to the end of our style sheet and specify:

#ralphs-node-table .odd {background: #e0f0e0;}
#ralphs-node-table .even {background: #e0e0f0;}


Adding Graphics

The next morning Ralph came and announced, "I had a brilliant thought last night! Instead of yes and no for these columns, let's make it stand out better. How about a nice big check mark for the ones that are yes and just leave it blank for no. Oh, except for the ones that aren't published - let's make them a big X or something." With that and my make-believe grimace of immense pain, he walked.

I laughed because I knew how to do this in just a few minutes, but he thinks it will take me the rest of the week.

Remember, we can specify pretty much any attribute for a cell with the theme_table function. And, if you've ever looked at your site logs, you'll know that such graphics are already available on all Drupal sites. We'll just set a class name for those cells and then specify those graphics to be used. I'm going to do it so his next changes are even easier. So here's the changed code, which uses a little php magic:

<?php
 
array('data' => ' ', 'class' => $node->status ? 'published' : 'unpublished'),
  array(
'data' => ' ', 'class' => $node->sticky ? 'sticky' : 'not-sticky'),
  array(
'data' => ' ', 'class' => $node->promote ? 'promoted' : 'not-promoted')
?>

Note that you add just about any attribute to any cell. I generally try to use classes for everything so that it can be easily changed in the CSS.

So now we just have to set up the style sheet:

#ralphs-node-table .published {background-color: inherit; background-image: url(/misc/watchdog-ok.png);
   background-repeat: no-repeat; background-position: center;}
#ralphs-node-table .unpublished {background-color: inherit; background-image: url(/misc/watchdog-error.png);
   background-repeat: no-repeat; background-position: center;}
#ralphs-node-table .sticky {background-color: inherit; background-image: url(/misc/forum-sticky.png);
   background-repeat: no-repeat; background-position: center;}
#ralphs-node-table .not-sticky {}
#ralphs-node-table .promoted {background-color: inherit; background-image: url(/misc/watchdog-warning.png);
   background-repeat: no-repeat; background-position: center;}
#ralphs-node-table .not-promoted {}

Okay, I cheated. While I was looking up the exact names of those images, I found a "forum-sticky" image. So shoot me!

Final Result

Well, here's how it looks now:

<?php
  $header
= array('Node ID', 'Title', 'Type', 'Terms', 'Created', 'Published', 'Sticky', 'Promoted');
 
$rows = array();
 
$no_yes = array('No', 'Yes');
 
$results = db_query("SELECT * FROM {node} ORDER BY promote DESC, sticky DESC, created DESC");
  while (
$node = db_fetch_object($results)) {
   
$termlist = taxonomy_node_get_terms($node->nid);
   
$terms = array();
    foreach (
$termlist as $key => $value) { $terms[] = $value->name; }
   
$rows[] = array(array('data' => $node->nid, 'align' => 'center'),
                   
l($node->title, 'node/'. $node->nid .'/edit'),
                   
$node->type,
                   
implode(' | ', $terms),
                   
format_date($node->created),
                    array(
'data' => ' ', 'class' => $node->status ? 'published' : 'unpublished'),
                    array(
'data' => ' ', 'class' => $node->sticky ? 'sticky' : 'not-sticky'),
                    array(
'data' => ' ', 'class' => $node->promote ? 'promoted' : 'not-promoted'),
                );
   }
 
$table_attributes = array('id' => 'ralphs-node-table', 'align' => 'center');
  return
theme('table', $header, $rows, $table_attributes);
?>

I'm just waiting for Ralph to come in and ask me to add a pager function to this code. I wonder if I can make him think it will take me a couple of days when it will really only take a couple of minutes.

 

Looking for support? Visit the Drupal.org forums, or join #drupal-support in IRC.

Comments

I needed to create a Drupal style table complete with sticky headers, sorting, pging etc. This page helped me get started with the process. However, it did not cover everything I wanted to do with my table. After spending sometime, I was able to achieve all of the above on my Drupal table. I have discussed the entire process I followed here:
http://www.rahulsingla.com/blog/2010/05/creating-drupal-style-tables

--
I always think tomorrow will have more time than today.
And every today seems to pass-by faster than yesterday.
http://www.rahulsingla.com

Drupal version: 6.20

A sincere thank you for providing this write up. It pointed me in the right direction. Unfortunately the example in the write-up didn't give any output. After a bit of fiddling I got the following, based in part on the code above, to work with a pager.

Code subject to review.

<?php
$sql
= "SELECT nid, title, type from {node} where type = '%s'";
$count_query = "select count(*) from {node} where type = '%s'";
$result = pager_query($sql, 10, 0, $count_query, 'your_node_type_here');
$headers  = array();
$rows = array();
while (
$row = db_fetch_array($result)) {
$rows[] = array(
array(
'data' => ('<h4>' . $row['title'] . '</h4>'), 'class' => 'title'),
array(
'data' => $row['type'], 'class' => 'type')
);
}
$table_attributes = array('id' => 'table-id');
$headers = array("Title","Type");
$output = theme('table', $headers, $rows, $table_attributes);
$output .= theme('pager', NULL, 10);
print
$output;
?>

At 'your_node_type_here' add the node type you are looking for.

For example:

<?php
$result
= pager_query($sql, 10, 0, $count_query, 'story');
?>

The original code for the pager:
http://drupal.org/node/357749

This was supposed to be simple. I'm not sure where "the next day" went.

<?php
  $header
= array(
    array(
'data' => t('Node ID'), 'field' => 'nid'),
    array(
'data' => t('Title'), 'field' => 'title'),
    array(
'data' => t('Type'), 'field' => 'type'),
    array(
'data' => t('Terms'), 'field' => ''),     /* Not sortable */
   
'default' => array('data' => t('Created'), 'field' => 'created', 'sort' => 'desc'),
    array(
'data' => t('Published'), 'field' => 'status'),
    array(
'data' => t('Sticky'), 'field' => 'sticky'),
    array(
'data' => t('Promoted'), 'field' => 'promote'),
    );
 
$rows = array();
 
$noyes = array(t('No'), t('Yes'));
 
$show_count = 10;
//  $query = "SELECT * FROM {node} ORDER BY promote DESC, sticky DESC, created DESC";
 
$query = "SELECT * FROM {node}";
 
// See if the user has requested a sort order other than the normal.
 
if (isset($_GET['order']) && $_GET['order'] != $header['default']['data']) {
   
// Yes, so we won't "fix" it.
   
$order = '';
  }
  else {
   
// No, so we'll add promot and sticky to the default, created.
   
$order = 'promote DESC, sticky DESC,';
  }
 
$query .= tablesort_sql($header, $order);
 
$results = pager_query($query, $show_count);
  while (
$node = db_fetch_object($results)) {
   
$termlist = taxonomy_node_get_terms($node->nid);
   
$terms = array();
    foreach (
$termlist as $key => $value) { $terms[] = $value->name; }
   
$rows[] = array(
      array(
'data' => $node->nid, 'align' => 'center'),
     
l($node->title, 'node/'. $node->nid .'/edit'),
     
$node->type,
     
implode(' | ', $terms),
     
format_date($node->created),
      array(
'data' => ' ', 'class' => $node->status ? 'published' : 'unpublished'),
      array(
'data' => ' ', 'class' => $node->sticky ? 'sticky' : 'not-sticky'),
      array(
'data' => ' ', 'class' => $node->promote ? 'promoted' : 'not-promoted'),
      );
   }
 
$table_attributes = array('id' => 'ralphs-node-table', 'align' => 'center');
  return
theme('table', $header, $rows, $table_attributes) . theme('pager', array(), $show_count);
?>

Just in case anyone wants to use this in Drupal 7, everything's the same except for the last line where it says
return theme('table', $header, $rows, $table_attributes);
change it to
return theme('table', array('header' => $header, 'rows' => $rows));

Phew

I have been trying to do this in D7 and all I'm getting is problems. After Googling it I find that db_fetch_object and taxonomy_node_get_terms are no longer in Drupal. Did you do anything else to get this to work or is the change above all it takes? If that is the case what am I doing that is breaking it?

Thanks

subscribing

"class" attribute must be an array. Some like this

<?php
  $row
[] = array('data' => $node->nid, 'align' => 'left', 'style' => 'padding-right: 20px');
 
$rows[] = array('data' => $row, 'class' => array('my-style-row'));
?>

In D6 they were not, but in D7, yes, they must be.

Nancy, Thanks so much for providing this!

just to roll it up:
in 7. the prototype for theme is table($hook, $variables)
the supported keys for the $variable array are

header = array of column names
rows = array of row data (each row is an array of row items)
attributes = various drupal attributes
caption = the table caption
colgroups = Got me, I did'nt use this
sticky = why is this not part of 'attributes'?
empty = again, I am clueless.

I do not know what all of the passed arguments do, but there is the list of them.

IT specialist using drupal for an intranet portal for employees

This document does for D7. My requirement was little different. I have to make it sort. The values not coming from the Database. for that i used simple jquery
http://tablesorter.com/docs/

Rakesh James

<?php
$install
= db_query("SELECT DATE(FROM_UNIXTIME(sfo.first_access)) as dt,sfo.first_access, count(device_id) as number_of_install FROM sf_analytics_device_info sfo WHERE sfo.device_id IN (SELECT sf.device_id FROM sf_analytics_app_access_data sf WHERE sf.app_id = 387 GROUP BY sf.device_id) GROUP BY dt ORDER BY first_access DESC")->FetchALL();
$header = array('Date', 'Number of installation');
$rows = array();
foreach(
$install as $key) {
 
$rows[] = array($key->dt,$key->number_of_install);
}
return
theme('table', array('header' => $header, 'rows' => $rows));
?>

Rakesh James