Hi, I am using Forms API to build a page which displays a table of rows from a DB, with a checkbox on each row. At the bottom is a Submit button. I can render it but the submitted value is always that of my last checkbox. This tells me that I am probably overwriting part of my forms array, or the HTML is not submitting properly. It's probably fixable, but my larger question is: what is the easiest and best way to do this? Eventually I will want to page the output, create filters, sorts, etc. so I'd like to be "smart".

I've read a lot of ways to create pages like this:

* iterate over the rows and add to form[box] with each row
* create separate forms for each row
* use Javascript
* use CCK

I would prefer to not load tons of modules. Thanks for any input.

Comments

jeebsuk’s picture

Definitely do not create a form for each row, that sounds a terrible waste. I think you may need to use the form tree attribute I believe I had this problem when creating something similar in the past that should stop all your stuff from getting overwritten, that hopefully gives you a little something to go on to start with.

trying_thanks’s picture

Thanks for the quick response. Here's the code I'm using so far:


// while loop with DB query, which gets qids 
// and calls drupal_get_form('queue_checkbox_form', $qid)
// on each row

function queue_checkbox_form( &$form_state, $qid ) {
    $form['boxes'] = array(
        '#type'     => 'checkbox',
        '#default_value'    => 0,
        '#return_value' => $qid,
        '#submit'   => array('delete_submit'),
        '#tree'     => TRUE ,
    );
    $form['delete'] = array(
        '#type'     => 'submit',
        '#default_value'    => t('Delete'),
        '#submit'   => array('delete_submit'),
        '#tree'     => TRUE ,
    );
    return $form;
}

// .............

function delete_submit($form_values, &$form_state) {
    $qid = $form_values['boxes']['#post']['boxes'];
    drupal_set_message('deleted: ' . $qid);
}

It's working OK, but I'm creating submit buttons for every checkbox - not optimal, I'm trying to do multiple deletes based on checked boxes. If I try to create the submit button with another constructor (since I am creating the checkboxes in a while loop), I can't get the selected checkboxes from the form_values array. I also tried adding $qid as an argument to the #submit value callback for the Submit button, no luck there. Finally, I tried conditional logic in the queue_checkbox constructor, also no luck. So really what I'm missing is how to have one submit button send the values of all checked boxes on the page (is this possible?). Thanks.

jaypan’s picture

<?php
function mymodule_checkbox_form($form_state)
{
  $checkboxes = array(1, 2, 3);
  $form['checkboxes'] = array
  (
    '#type' => 'checkboxes',
    '#options' => $checkboxes,
  );
  $form['submit'] = array
  (
    '#type' => 'submit',
    '#value' => t('Submit'),
  );
  return $form;
}

function mymodule_checkbox_form_submit($form_state)
{
  $checked_boxes = array(); //this will hold the value for each of the checkboxes that has been checked
  foreach($form_state['values']['checkboxes'] as $checkbox) // $form_state['values'] holds the all the submited form values
  {
    if($checkbox > 0) // non-checked checkboxes have a value of zero. Checked checkboxes hold the value of the checkbox. In this case, I gave all the checkboxes a numerical value, so I only need to check if the value is greater than zero to see if the box was checked. If a text value is given, you will need to check to see if the value is equal to the text value for the checkbox
    {
      $checked_boxes[] = $checkbox;
    }
  }
  foreach($checked_boxes as $checked_box)
  {
    //here you can do whatever it is you need to do with each of the submitted values.
  }
}
?>

Contact me to contract me for D7 -> D10/11 migrations.

trying_thanks’s picture

Excellent, thanks. Creating "n" checkboxes is accomplished by passing a row count to the checkbox_form constructor. Validation works great. The only problem is how to render the individual checkboxes in a table, with one checkbox next to each DB row. Looks like there are some CSS hacks that might work. I can't seem to render the boxes one at a time since they are all one form. Also the forms API for Drupal 6.14 seems a bit buggy, no?

jaypan’s picture

The forms API is great - it just takes a bit of getting used to. But I've not yet found anything I couldn't do with it.

As for how to put your checkboxes in a table, you have to run it through a theming function:

<?php
function mymodule_form($form_state)
{
  $rows = db_query("SELECT nid, title, created {node}"); // this is a typical query from the node table to get some info about nodes
  while($row = $rows) // we loop through the results
  {
    $checkboxes[$row['nid']] = ''; // a blank value is given so that the checkboxes have no title and are just rendered as a blank checkbox
    $form['data_table']['title'][$row['nid']] = array("#value" => $row['title']); // we key each of the rows by the checkbox NID. If you aren't using NIDs, make sure that the checkbox values are unique. If they aren't, then you need to use some other key to make sure that each row is unique
    $form['data_table']['created'][$row['nid']] = array("#value" => $row['created']);
  }
  $form['data_table']['nids'] = array // now we put the checkboxes into the form
  (
    '#type' => 'checkboxes',
    '#options' => $checkboxes,
  );
  $form['data_table']['#theme'] = 'mytheme'; // here we call the 'data_table' section of the form to themed using the theme 'my_theme'
  // do the rest of your form definition here
}

function mymodule_theme() // here we register our theme functions (or templates)
{
  return array
  (
    'mytheme' => array // this is the name of the theme we want to register
    (
      'arguments' => array('form' => NULL), // we pass it one value - form. This will be the 'data_table' section of $form from the form API function
    );
  );
}

function theme_mytheme(&$form) // now we declare our theme function
{
  $has_posts = isset($form['title']) && is_array($form['title']); // first we check to see if any values (rows) were actually passed to the function to be themed.
  $select_header = $has_posts ? theme('table_select_header_cell') : ''; // if there are posts, we add a checkbox to the header. This checkbox will be a 'check all' checkbox - when a check is entered into it, all checkboxes get checked. If there are no posts, then we enter nothing
  $header = array($select_header, t('Node Title'), t('Created On')); // this is the header for our table. It's got three columns - one for the check all box, one for the node title and one for the creation date
  $output = ''; // here we initialize a blank row

  if ($has_posts) // if we have posts, we start to build our table. We need to build an array called $rows. Each element of this array will be one row of the table (see http://api.drupal.org/api/function/theme_table/6 for more information on building tables in Drupal)
  {
    foreach (element_children($form['last_name']) as $key) // this gives us the unique index that we keyed each of our rows by in the while loop in the form definition
    {
      $row = array(); // we initialize a blank row. All the row data will go in here
      $row[] = drupal_render($form['nids'][$key]); // drupal_render renders the API definition, which is an array, into a proper form element. This will be the checkbox
      $row[] = drupal_render($form['title'][$key]); // this is the node title
      $row[] = drupal_render($form['created'][$key]); // this is the node creation date
      $rows[] = $row; // here we add the array that is that row to the $rows array
    }
  }
  else
  {
    $row = array('data' => t('No rows were found'), 'colspan' => 3) // this is a default message when no rows are found
    $rows[] = row;
  }
  $output .= theme('table', $header, $rows); // this builds our table
  $output .= drupal_render($form); //this calls some last rendering functions on the 'data_table' section of the $form from the form definition in the original function
  return $output; // and now we return our themed section of the form. This will be rendered in a table.
}
?>

There you go. It's not the easiest thing to do, but it works really well and you end up with a nicely formed table that will have a sticky table header if the form is longer than can fit into one screen. See theme_table() at api.drupal.org for more info on building HTML tables in Drupal.

Contact me to contract me for D7 -> D10/11 migrations.

trying_thanks’s picture

I never would've figured this out... thanks. I couldn't quite get the theming to work, mostly WSOD even with PHP errors turned on. We must be using different drupal versions since the DB calls have changed a bit. I ended up working around the problem by using some global variables - accessing the $form array all over the place became problematic, so I hacked around it. When I get my code all cleaned up, I will post here.

jaypan’s picture

The method I posted is for all of Drupal 6.x. The database call was just a typo on my part - I typed it off the top of my head and missed 'FROM'. But it wasn't meant to be a real case, just an example. If you were getting WSOD, it was probably a problem with not finding theming functions. Clear your cache, and also visit admin->build->themes when that happens. This will rebuild your theme registry.

You really shouldn't need to be using global variables at all. This method is the method they use in the core (admin->manage contents->content and admin->manage users -> user), and I use it regularly in modules. Go back and compare what you've done with what I showed you, and you will probably just find you made a typo somewhere along the way.

Contact me to contract me for D7 -> D10/11 migrations.