Drupal Drag & Drop Sorting


As the desire for neater and sleeker user interface increases, the ability to provide a website administrator with the ability to sort content that will appear on a list page. Granted, there are a number of great modules that allow for the sorting of pre-defined nodes, but sometimes you just have to sort items (whether they are nodes or rows from a custom table) in a completely custom way. This blog post will describe how you can display any item that you retrieve through a query in list form and allow the administrator to sort those items in a drag and drop fashion. This functionality will be build out within a custom module that is attached at the end of the article. Please note that this tutorial does require some knowledge of how to create custom modules and utilize module hooks. Click here for all the resources you need to start building custom modules.

The Weight Field

In order to sort nodes or your custom items, a weight field must be present. This field stores the sort order of these items. For nodes, create an integer CCK weight field. If you are creating a table within a custom module, create an unsigned integer.

Listing Items and the Form API

Use a query to obtain the item title, item ID, item weight, and any other information you want to display for each item. Then, utilize the form API to assign each item to the form array, using the item ID as the unique key for each row as follows:

function customsortmodule_display_items_form($form_state) { 
	$form = array();
	$db_probe = db_query("select id, title, weight from {customsortmodule_list} order by weight);

	$form['#tree'] = TRUE;
	while ($row = db_fetch_array($db_probe)) {
		//Make sure all fields that need to be updated in the database (generally, only the ID and weight fields need this for this type of form) and their corresponding database fields have the same name.
		
		//The next two fields should both contain the applicable item title
		//Note how each field (name, id, and weight) is stored as a child of it's $row['id']. This allows each item to be displayed and acted upon as a row in a table.
		
		$form[$row['id']]['#item'] = $row['title'];
		$form[$row['id']]['name']['#value'] = $row['title'];
		
		//The id of the field must be included so each  
		$form[$row['id']]['id'] = array(
			'#type' => 'hidden',
			'#value' => $row['id']
		);
		
		//This is the weight field that determines where the item is sorted. The delta value is half of the maximum number of items plus 1 that can be sorted in this table. For example, we can sort 41 items in this table (-20 to 20).
		$form[$row['id']]['weight'] = array(
			'#type' => 'weight',
			'#delta' => 20,
			'#default_value' => $row['weight']
		);
	}
	$form['submit'] = array(
		'#type' => 'submit',
		'#value' => t('Save configuration'),
	);
	return $form;

}

Add sorting capability

Since we will be utilizing a custom theming function, make sure to register the theming function with hook_theme in the .module like so:

function customsortmodule_theme() {
  return array(
    'customsortmodule_display_items_table_form' => array(
      'file' => 'custommodule.admin.inc',
      'arguments' => array('form' => NULL),
    )
	);
}

In this theming function, utilize a function called drupal_add_tabledrag to add sorting capabilities to your form table. This will provide sorting for a particular table id ("thetable") and a particular weight class ("theweight").

function theme_customsortmodule_display_items_table_form($form) {
  //This function is the instantiator of the sorter. Make sure the 0th paramater is the id of your table, and the 3rd paramater is the class of your weight variable
	drupal_add_tabledrag('thetable', 'order', 'sibling', 'theweight');
	
	//Define your table headers
	$header = array(
    t('Name'),
    t('Weight'),
  );
  $rows = array();

	//Loop through each item to display in the sortable table
  foreach (element_children($form) as $key) {
    if (isset($form[$key]['name'])) {
			
			//Make this variable the weight class defined in the drupal_add_tabledrag function.
			$form[$key]['weight']['#attributes']['class'] = 'theweight';
	
			$row = array();
			//Define columns
			$row = array(
				drupal_render($form[$key]['name']), 
				drupal_render($form[$key]['weight']), 
			//Add to the $rows varaiable, which will be used to generate the sortable rows
			$rows[] = array(
				'data' => $row,
				'class' => 'draggable'
			);
		}
	}
	
	//Finally, output the sortable table. Make sure the id variable is the same as the table id in drupal_add_tabledrag
	$output = theme('table', $header, $rows, array('id' => 'thetable'), "My Sorted Items");
	$output .= drupal_render($form);
	return $output;

}

Saving item weights

At this point, this custom module provides the administrator with the ability to sort any number of items. However, just as in the menu system, a save button needs to be pressed before the database is updated. An example submit function is as follows:

function customsortmodule_display_items_form_submit($form, &$form_state) {
	$values = $form_state['values']; //All form values
  foreach ($values as $arrkey => $valuearr) { //Cycle through each row of values
		if (is_array($valuearr)) {

		$updateWeight = array(
				"id"=>$valuearr['id'],
				"weight"=>$valuearr['weight'],
			);

			drupal_write_record('customsortmodule_list',$updateWeight,'id');
		}
	}
}

Conclusion

This ability to provide user-friendly custom sort functionality allows you to easily sort any number of items within your website. For more reference on this kind of sorting within Drupal, please check out the drupal_add_tabledrag entry in the Drupal API.

Next time: Sorting with parent items like the menu system

The next step in custom sorting is the ability to provide parent-child relationships for sortable items. My next blog post will detail the best way to approach this functionality on a reusable scale.