Author image
Senior Mind

Quick Guide to using drupal_add_tabledrag and enjoying jquery drag and drop loveliness

We are finding that the feature exciting most end users in Drupal 6 is the lovely new jquery based drag and drop, as seen on the blocks and menu edit pages - we will be quite happy never have to explain the concept of "weights" again. The best news is that you can add this functionality to your own forms for free - and here is how.

Build and theme the form

We are assuming your vaguely familiar with the form API - you can brush up here http://api.drupal.org/api/file/developer/topics/forms_api.html - so we won't go into too much detail here. Essentially we need to create a form that will be themed into a table, each row in the table will need a form element capable of holding the "weight" or "order" value.

  function example_form(&$form_state){

    //fetch the data from the DB
    $result = db_query("SELECT id, value1, value2, weight
                        FROM {foo}
                        ORDER BY weight ASC");

    while ($row = db_fetch_object($result)){
      //create a partial table row containing the data from the table
      $data = array(
        $row->value1,
        $row->value2
      );

      //add our static "row" data into a form value
      $form['rows'][$row->id]['data']=array(
                                   '#type' => 'value',
                                   '#value' => $data
                                 );

      //now create the weight form element. 
      //NOTE how we add the id into the element key
      $form['rows'][$row->id]['weight-'.$row->id]=array(
        '#type'=>'textfield',
        '#size'=>5,
        '#default_value'=>$weight,
        //add a specific class in here - we need this later
        '#attributes' => array('class'=>'weight'),
      );
    }   
   
    //Don't forget the submit button
    $form['submit']=array(
      '#type'=>'submit',
      '#value'=>t('Save changes'),
    );

    return $form;
  }

That should create a pretty basic and ugly form - we now need to theme this as a table

  function theme_example_form($form){
    //loop through each "row" in the table array
    foreach($form['rows'] as $id => $row){
      //we are only interested in numeric keys
      if (intval($id)){ 
        $this_row = $row['data']['#value'];

        //Add the weight field to the row
        $this_row[] = drupal_render($form['rows'][$id]['weight-'.$sid]);
  
        //Add the row to the array of rows
        $table_rows[] = array('data'=>$this_row, 'class'=>'draggable');
      }
    }
   
    //Make sure the header count matches the column count
    $header=array(
      "Value1","Value2","Order"
    );

    $output = theme('table',$header,$table_rows,array('id'=>'example-table'));
    $output .= drupal_render($form);

    // Call add_tabledrag to add and setup the JS for us
    // The key thing here is the first param - the table ID
    // and the 4th param, the class of the form item which holds the weight
    drupal_add_tabledrag('example-table', 'order', 'sibling', 'weight');     

    return $output;
  }

Don't forget - we are in Drupal 6 land now so we need to register our theme function before it will take affect (how many times have I forgotten this already!)

function example_module_theme() {
  return array(
    'example_form' => array(
      'arguments' => array('form' => NULL),
    ),
  );
}

Handle the submit

We should now have a working drag and drop enabled form themed as a table, we now need to handle the submit and store our new weight values.

function example_form_submit($form, &$form_state) {
  foreach($form_state['values'] as $key=>$data){
    //we are only interested in weight elements
    if (substr($key,0,6)=='weight'){
      //cunningly we have the DB id of the row in the element name
      $id = str_replace('weight-','',$key);
      db_query("UPDATE {foo} SET weight=%d WHERE id=%d",$data,$id);
    }
  }
  //optionally set the redirect value in form_submit ...
}

Comments

There is a specific type 'weight' in the Drupal API for cases like this - I suggest you use it rather than 'textfield'.

So the form field should be:

$form['rows'][$row->id]['weight-'.$row->id]=array(
'#type'=>'weight',
'#default_value'=>$weight,
'#attributes' => array('class'=>'weight'),
);

The advantage of using 'weight' is that when JS is turned off, the user gets a nice dropdown box with weights to choose.

@Shaun Ressler
It performs the write to the database with the new weights in the function: example_form_submit

If it's not working for you, start your debugging there.

How to use this function to make a page the same: admin/build/menu-customize/navigation:
*root
*parent1
*parent2
*children1
*leaf
*parent3
......
Thank you for advance

Thanks for this posting and I used this function into my websites and now problems solved!

There are a few errors in this code -- double-check it before using it on your own site.

  • The theme function needs to return its output
  • The form function needs to return the $form array
  • $sid should be $id in the theme function

--Cliff Smith

Great article! I found a minor error in the theme_example_form() function. The $sid variable should be $id in line 9. Also, a follow up article on how to create a table with children would be nice.

Newbie here . Very helpful. One thing not real clear is where code for form submit function gets put, how it gets called , and where the the function is called that actually triggers rendering the form .

IE, i think you call in a template or module menu callback or somewhere that is page is generated. :

 
print theme('example_form');

Thanks

Great intro. I am missing something, though. You use the variable $weight but I do not see where it gets its value.

Great article! Very helpful... The tables are now appearing correctly for me, but the weight values are not being updated/saved at all... Nothing in the $form_state array reflects the appropriate weights on submit. Is there something missing somewhere?

Thanks,
Shaun

Thanks for this posting and I used this function into my websites and now problems solved!

Regards,
Sharon

Careful not to give your table the same id as you're form name (e.g. 'test-table' for table id, 'test_table' for form name) like I did. You'll end up wondering why you get obscure "this.table.tBodies is undefined" javascript errors.

Cheers got this to work with the new jQuery

Want to explain the concept of weights for drupal once more? only kidding :)

Comments on this article are now closed, if you want to give us feeback you can use our contact form instead.