? 234004-dragdrop.patch
? 234004.dragdop_2.patch
Index: nodequeue.js
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/nodequeue/nodequeue.js,v
retrieving revision 1.5
diff -u -w -r1.5 nodequeue.js
--- nodequeue.js	11 Jan 2009 16:18:31 -0000	1.5
+++ nodequeue.js	28 Apr 2009 22:32:41 -0000
@@ -419,8 +419,25 @@
 }
 
 if (Drupal.jsEnabled) {
-  $(document).ready(Drupal.nodequeue.autoAttach);
+  $(document).ready(function() {
+    Drupal.nodequeue.autoAttach();
+    $('a.nodequeue-remove').css("display", "block");
+    $('a.nodequeue-remove').click(function() {
+      a = $(this).attr('id');
+      a = "#" + a.replace('nodequeue-remove-', 'edit-') + '-position';
+      $(a).val('r');
+      //Hide the current row
+      $(this).parent().parent().addClass('hidden').hide();
+      //Restripe the table
+      // :even and :odd are reversed because jquery counts from 0 and
+      // we count from 1, so we're out of sync.
+      $('table.nodequeue-dragdrop tr:not(:hidden)')
+        .filter(':odd').filter('.odd')
+          .removeClass('odd').addClass('even')
+            .end().end()
+              .filter(':even').filter('.even')
+                .removeClass('even').addClass('odd');
+      return false;
+    });
+  });
 }
-
-
-
Index: nodequeue.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/nodequeue/nodequeue.module,v
retrieving revision 1.74
diff -u -w -r1.74 nodequeue.module
--- nodequeue.module	9 Apr 2009 19:28:03 -0000	1.74
+++ nodequeue.module	28 Apr 2009 22:32:41 -0000
@@ -895,222 +895,6 @@
   return nodequeue_arrange_subqueue($queue, $subqueue);
 }
 
-function nodequeue_arrange_subqueue_entry($queue, $subqueue, $node) {
-  $qid = $queue->qid;
-  $sqid = $subqueue->sqid;
-  $query_string = nodequeue_get_query_string($node->position, TRUE);
-
-  $buttons = l(
-    theme('image', drupal_get_path('module', 'nodequeue') .'/images/go-up.png', t('Move up')),
-    "admin/content/nodequeue/$qid/up/$sqid/$node->position",
-    array('attributes' => array(
-      'title' => t('Move up'),
-      'class' => 'nodequeue-move-up',
-    ), 'query' => $query_string, 'html' => TRUE));
-  $buttons .= l(
-    theme('image', drupal_get_path('module', 'nodequeue') .'/images/go-down.png', t('Move down')),
-    "admin/content/nodequeue/$qid/down/$sqid/$node->position",
-    array('attributes' => array(
-      'title' => t('Move down'),
-      'class' => 'nodequeue-move-down',
-    ), 'query' => $query_string, 'html' => TRUE));
-  $buttons .= l(
-    theme('image', drupal_get_path('module', 'nodequeue') .'/images/go-top.png', t('Move to front')),
-    "admin/content/nodequeue/$qid/front/$sqid/$node->position",
-    array('attributes' => array(
-      'title' => t('Move to front'),
-      'class' => 'nodequeue-move-front',
-    ), 'query' => $query_string, 'html' => TRUE));
-  $buttons .= l(
-    theme('image', drupal_get_path('module', 'nodequeue') .'/images/go-bottom.png', t('Move to back')),
-    "admin/content/nodequeue/$qid/back/$sqid/$node->position",
-    array('attributes' => array(
-      'title' => t('Move to back'),
-      'class' => 'nodequeue-move-back',
-    ), 'query' => $query_string, 'html' => TRUE));
-  $buttons .= l(
-    theme('image', drupal_get_path('module', 'nodequeue') .'/images/delete.png', t('Remove from queue')),
-    "admin/content/nodequeue/$qid/remove/$sqid/$node->position",
-    array('attributes' => array(
-      'title' => t('Remove from queue'),
-      'class' => 'nodequeue-remove',
-    ), 'query' => $query_string, 'html' => TRUE));
-
-  $output = '<tr id="nodequeue-row-'. $node->position .'" class="nodequeue-row '. ($node->position % 2 ? 'odd' : 'even') .'">';
-  $output .= '<td>'. l($node->title, "node/$node->nid") .'</td>';
-  $output .= '<td>'. theme('username', $node) .'</td>';
-  $output .= '<td>'. format_date($node->created) .'</td>';
-  $output .= '<td>'. $buttons .'</td>';
-  $output .= '</tr>';
-  return $output;
-}
-
-/**
- * View the contents of a subqueue, with links to re-order the queue.
- */
-function nodequeue_arrange_subqueue($queue, $subqueue) {
-  $qid = $queue->qid;
-  $sqid = $subqueue->sqid;
-  $output = '';
-
-  $order = $queue->reverse ? 'DESC' : 'ASC';
-  $result = db_query("SELECT DISTINCT(n.nid), n.title, n.uid, u.name, n.created, nq.position FROM {node} n LEFT JOIN {users} u on n.uid = u.uid LEFT JOIN {nodequeue_nodes} nq ON nq.nid = n.nid WHERE nq.sqid = %d ORDER BY nq.position $order", $sqid);
-
-  $body = '';
-
-  $nids = array();
-  while ($node = db_fetch_object($result)) {
-    $nids[$node->position] = $node->nid;
-    $body .= nodequeue_arrange_subqueue_entry($queue, $subqueue, $node);
-  }
-
-  $output = '<p>'. t('Max nodes in queue: @size', array('@size' => $queue->size ? $queue->size : t("Infinite"))) .'</p>';
-
-  $output .= '<p class="nodequeue-hide-if-not-js nodequeue-warning">';
-  $output .= t('Changes made to the queue order and queue removals will not be active until you click Save, below. If you add more nodes than the queue can hold, they will be removed from the @end when you save!', array('@end' => $queue->reverse ? t('bottom') : t('top')));
-  $output .= '</p>';
-
-  $output .= '<table id="nodequeue-table">';
-  $output .= '<thead>';
-  $output .= '<tr>';
-  $output .= '<th class="nodequeue-node">'. t('Node') .'</th>';
-  $output .= '<th class="nodequeue-author">'. t('Author') .'</th>';
-  $output .= '<th class="nodequeue-date">'. t('Date') .'</th>';
-  $output .= '<th class="nodequeue-operation">'. t('Operation') .'</th>';
-  $output .= '</thead>';
-  $output .= '<tbody>'. $body .'</tbody>';
-  $output .= '</table>';
-
-  $output .= drupal_get_form('nodequeue_arrange_subqueue_form', $queue, $sqid, $nids);
-  drupal_add_js(drupal_get_path('module', 'nodequeue') .'/nodequeue.js');
-
-  return $output;
-}
-
-/**
- * Form used for arranging a queue
- */
-//TODO: Form - Revise for D6
-function nodequeue_arrange_subqueue_form($form_state, $queue, $sqid, $nids) {
-  $form['qid'] = array(
-    '#type' => 'value',
-    '#value' => $queue->qid,
-  );
-
-  $form['sqid'] = array(
-    '#type' => 'hidden',
-    '#value' => $sqid,
-  );
-
-  $form['order'] = array(
-    '#type' => 'hidden',
-    '#id' => 'nodequeue-order',
-    '#default_value' => implode(',', array_keys($nids)),
-  );
-
-  $form['add'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Select title to add'),
-    '#autocomplete_path' => "nodequeue/autocomplete/$sqid",
-    '#default_value' => '',
-  );
-
-  // For use by the validate handler
-  $form['nid'] = array(
-    '#type' => 'value',
-    '#value' => 0,
-  );
-
-  $form['add_submit'] = array(
-    '#type' => 'submit',
-    '#attributes' => array('class' => 'nodequeue-add'),
-    '#value' => t('Add'),
-    '#validate' => array('nodequeue_arrange_subqueue_form_add_validate'),
-    '#submit' => array('nodequeue_arrange_subqueue_form_add_submit'),
-  );
-
-  $form['save'] = array(
-    '#type' => 'submit',
-    '#attributes' => array('class' => 'nodequeue-hide-if-not-js-hide nodequeue-save'),
-    '#value' => t('Save'),
-    '#validate' => array('nodequeue_arrange_subqueue_form_save_validate'),
-  );
-
-  $form['clear'] = array(
-    '#type' => 'submit',
-    '#attributes' => array('class' => 'nodequeue-clear'),
-    '#value' => t('Clear'),
-    '#submit' => array('nodequeue_arrange_subqueue_form_clear_submit'),
-  );
-
-  $form['reverse_click'] = array(
-    '#type' => 'submit',
-    '#attributes' => array('class' => 'nodequeue-reverse'),
-    '#value' => t('Reverse'),
-    '#submit' => array('nodequeue_arrange_subqueue_form_submit'),
-  );
-
-  $form['shuffle'] = array(
-    '#type' => 'submit',
-    '#attributes' => array('class' => 'nodequeue-shuffle'),
-    '#value' => t('Shuffle'),
-    '#submit' => array('nodequeue_arrange_subqueue_form_shuffle_submit'),
-  );
-
-  // Store the original order.
-  $form['nids'] = array(
-    '#type' => 'value',
-    '#value' => $nids,
-  );
-
-  $form['added_nids'] = array(
-    '#type' => 'hidden',
-    '#default_value' => '',
-  );
-
-  $settings = array(
-    'nodequeue-table' => array(
-      // The gadget that stores our the order of items.
-      'order' => 'input#nodequeue-order',
-      // The buttons that do stuff.
-      'up' => 'a.nodequeue-move-up',
-      'down' => 'a.nodequeue-move-down',
-      'top' => 'a.nodequeue-move-front',
-      'bottom' => 'a.nodequeue-move-back',
-      'remove' => 'a.nodequeue-remove',
-
-      // The button that adds an item
-      'add' => 'input.nodequeue-add',
-      // The buttom to clear the queue
-      'clear_list' => 'input.nodequeue-clear',
-      // Path for js to shuffle the queue
-      'shuffle' => 'input.nodequeue-shuffle',
-      // Path for js to reverse the queue
-      'reverse' => 'input.nodequeue-reverse',
-      // Path for ajax on adding an item
-      'path' => url('nodequeue/ajax/add', array('absolute' => TRUE)),
-      // Which items to post when adding
-      'post' => array('#edit-sqid', '#edit-add'),
-      // Where to get the id of an item
-      'tr' => 'nodequeue-row-',
-      'row_class' => 'tr.nodequeue-row',
-      // Where to put the extra (we're storing a nid)
-      'extra' => '#edit-added-nids',
-      // What item to focus on ready
-      'focus' => '#edit-add',
-      // What item(s) to clear after add
-      'clear' => array('#edit-add'),
-      // What hidden class to show as soon as anything has changed that needs saving.
-      'hidden' => '.nodequeue-js-hide',
-      // Do we add to the top or the bottom?
-      'add_location' => $queue->reverse ? 'top' : 'bottom',
-    ),
-  );
-  drupal_add_js(array('nodequeue' => $settings), 'setting');
-  drupal_add_css(drupal_get_path('module', 'nodequeue') .'/nodequeue.css');
-
-  return $form;
-}
 
 /**
  * Implements hook_theme.
@@ -1134,130 +918,51 @@
   );
 }
 
-//TODO: Theme function for Form - do we need to revise?
-function theme_nodequeue_arrange_subqueue_form($form) {
-  $header = array(
-    check_plain($form['add']['#title']),
-    '',
-  );
-
-  unset($form['add']['#title']);
-  $rows = array(
-    drupal_render($form['add']),
-    array('data' => drupal_render($form['add_submit']), 'width' => '80%'),
-  );
-
-  $output = theme('table', $header, array($rows));
-  $output .= drupal_render($form);
-  return $output;
-}
-
-/**
- * Validate handler for nodequeue_arrange_subqueue_form
- */
-function nodequeue_arrange_subqueue_form_validate($form, &$form_state) {
-  // Default Validator - does nothing
-}
-
-/**
- * Validate handler for nodequeue_arrange_subqueue_form for 'Add' button.
- *
- * @param unknown_type $form_id
- * @param unknown_type $form_state
- * @param unknown_type $form
- */
-function nodequeue_arrange_subqueue_form_add_validate($form, &$form_state) {
-  $queue = nodequeue_load($form_state['values']['qid']);
-  $subqueue = nodequeue_load($form_state['values']['sqid']);
-  $nodes = nodequeue_api_autocomplete($queue, $subqueue, $form_state['values']['add']);
-  if (empty($nodes) || !is_array($nodes)) {
-    form_error($form['add'], t('Invalid node'));
-    return;
-  }
-  if (count($nodes) > 1) {
-    form_error($form['add'], t('That matches too many nodes'));
-    return;
-  }
-  $keys = array_keys($nodes);
-  $nid = array_pop($keys);
-  form_set_value($form['nid'], $nid, $form_state);
-}
-
-/**
- * Validate handler for nodequeue_arrange_subqueue_form for 'Save' button.
- *
- * @param unknown_type $form_id
- * @param unknown_type $form_state
- * @param unknown_type $form
- */
-function nodequeue_arrange_subqueue_form_save_validate($form, &$form_state) {
-  $nids = $form_state['values']['nids'];
-
-  // We can't use array_merge because it'll reset our keys and we can't
-  // use + because it will overwrite.
-  if ($form_state['values']['added_nids']) {
-    foreach (explode(',', $form_state['values']['added_nids']) as $nid) {
-      if (empty($nids)) {
-        $nids[1] = $nid;
-      }
-      else {
-        $nids[max(array_keys($nids)) + 1] = $nid;
-      }
-    }
-  }
-  form_set_value($form['nids'], $nids, $form_state);
-}
-
 /**
- * Submit function for nodequeue_arrange_subqueue_form on 'Reverse' button.
+ * Validates new subqueue order information and if it passes validation,
+ * deletes the old subqueue data from the database and saves the new data.
  *
- * Yeah, this just calls the below function with a different parameter, but in D6 we're not supposed to use the $form['ops'].
- */
-function nodequeue_arrange_subqueue_form_reverse_submit($form, &$form_state) {
-  nodequeue_arrange_subqueue_form_submit($form, $form_state, TRUE, FALSE);
-}
-
-function nodequeue_arrange_subqueue_form_shuffle_submit($form, &$form_state) {
-  nodequeue_arrange_subqueue_form_submit($form, $form_state, FALSE, TRUE);
-}
-
-/**
- * Submit function for nodequeue_arrange_subqueue_form
+ * @param $nodes:
+ *   an array of nodes, keyed on the subqueue position.
+ * @param $qid
+ *   the queue id
+ * @param unknown_type $sqid
+ *   the subqueue id
+ * @return An array where the first element is a numeric status code
+ *   (0 means successfully saved) and the second element is a status message.
  */
-//TODO: Form Submit - Revise for D6
-function nodequeue_arrange_subqueue_form_submit($form, &$form_state, $reverse=FALSE, $shuffle=FALSE) {
-  // Add a node to the queue if that's the intention.
-  $queue = nodequeue_load($form_state['values']['qid']);
-  $subqueue = nodequeue_load_subqueue($form_state['values']['sqid']);
-
-  db_query("DELETE FROM {nodequeue_nodes} WHERE sqid = %d", $form_state['values']['sqid']);
-  if ($form_state['values']['order']) {
+function nodequeue_save_subqueue_order($nodes = array(), $qid, $sqid) {
+  $positions = array();
     $now = time();
-    $sql = '';
-    $args = array();
-    $nids = $form_state['values']['nids'];
-    $subqueue->count = 0;
-    $order = explode(',', $form_state['values']['order']);
-    if ($queue->reverse xor $reverse) {
-      $order = array_reverse($order);
+  foreach ($nodes as $pos => $nid) {
+    if ($pos != 'r' && (!is_numeric($pos) || $pos < 1)) {
+      return array(0 => 1, 1 => 'Invalid position value.');
     }
-
-    foreach ($order as $new_pos => $old_pos) {
+    if (!is_numeric($nid) || $nid < 1) {
+      return array(0 => 2, 1 => 'Invalid nid value.');
+    }
+    if ($pos != 'r') {
+      $positions[] = $pos;
       if ($sql) {
         $sql .= ', ';
       }
       $sql .= ' (%d, %d, %d, %d, %d)';
-      $args[] = $form_state['values']['sqid'];
-      $args[] = $form_state['values']['qid'];
-      $args[] = $nids[$old_pos];
-
-      // $new_pos starts from 0 but we start from 1.
-      $args[] = $new_pos + 1;
+      $args[] = $sqid;
+      $args[] = $qid;
+      $args[] = $nid;
+      $args[] = $pos;
       $args[] = $now;
       $subqueue->count++;
     }
+  }
+  if (count(array_unique($positions)) < count($positions)) {
+    return array(0 => 3, 1 => 'Duplicate position values are not allowed.');
+  }
+  db_query("DELETE FROM {nodequeue_nodes} WHERE sqid = %d", $sqid);
+  if (!empty($args)) {
     $sql = "INSERT INTO {nodequeue_nodes} (sqid, qid, nid, position, timestamp) VALUES $sql";
     db_query($sql, $args);
+  }
     if ($queue->size) {
         // 0 means infinity so never do this if false
         nodequeue_check_subqueue_size($queue, $subqueue);
@@ -1265,8 +970,7 @@
     if ($shuffle) { //$form_values['op'] == t('Shuffle')) {
       nodequeue_subqueue_shuffle($subqueue);
     }
-  }
-  drupal_set_message(t('The queue has been updated'));
+  return array(0 => 0, 1 => 'The queue has been updated.');
 }
 
 function nodequeue_arrange_subqueue_form_clear_submit($form, &$form_state) {
@@ -2762,3 +2466,253 @@
 function nodequeue_check_token($seed) {
   return drupal_get_token($seed) == $_GET['token'];
 }
+
+
+/**
+ * Implementation of hook_elements().
+ */
+function nodequeue_elements() {
+  $type = array();
+
+  $type['position'] = array(
+    '#input' => TRUE,
+    '#delta' => 10,
+    '#default_value' => 0,
+    '#process' => array('process_position', 'form_expand_ahah'),
+  );
+
+  return $type;
+}
+
+/**
+ * Page callback to view a queue.
+ */
+function nodequeue_arrange_subqueue($queue, $subqueue = NULL) {
+  // set title and load subqueue if it's not provided
+  drupal_set_title(t("Nodequeue '@title'", array('@title' => $queue->title)));
+  if (!$subqueue->sqid) {
+    if ($queue->subqueues == 1) {
+      $subqueues = nodequeue_load_subqueues_by_queue($queue->qid);
+      $subqueue = array_shift($subqueues);
+    }
+    else {
+      return drupal_not_found();
+    }
+  }
+  else if ($subqueue->sqid) {
+   if (!nodequeue_api_subqueue_access($subqueue, NULL, $queue)) {
+      return drupal_not_found();
+    }
+    drupal_set_title(t("Subqueue '@title'", array('@title' => nodequeue_title_substitute($queue->subqueue_title, $queue, $subqueue))));
+  }
+
+  // get nodes from the queue
+  $nodes = _nodequeue_dragdrop_get_nodes($queue, $subqueue);
+
+  return drupal_get_form('nodequeue_arrange_subqueue_form', $queue, $nodes, $subqueue);
+}
+
+/**
+ * Expand position elements into selects. Works like the weight element, except
+ * only positive values are allowed.
+ */
+function process_position($element) {
+  for ($n = 1; $n <= $element['#delta']; $n++) {
+    $positions[$n] = $n;
+  }
+  $element['#options'] = $positions;
+  $element['#options']['r'] = t('Remove');
+  $element['#type'] = 'select';
+  $element += _element_info('select');
+  return $element;
+}
+
+/**
+ * If no default value is set for position select boxes, use 1.
+ */
+function position_value(&$form) {
+  if (isset($form['#default_value'])) {
+    $form['#value'] = $form['#default_value'];
+  }
+  else {
+    $form['#value'] = 1;
+  }
+}
+
+/**
+ * Form definition for nodequeue drag'n'drop form.
+ */
+function nodequeue_arrange_subqueue_form($form_state, $queue, $nodes, $subqueue) {
+  $form = array('#tree' => TRUE);
+
+  // save queue and subqueue objects for later use
+  $form['#queue']    = (array) $queue;
+  $form['#subqueue'] = (array) $subqueue;
+
+  // prepare the main part of the form which will be themed as a table
+  $count = count($nodes);
+  foreach ($nodes as $node) {
+    $form[$node->nid]['#node']    = (array) $node;
+    $form[$node->nid]['title']    = array('#value' => l($node->title, 'node/' . $node->nid));
+    $form[$node->nid]['author']   = array('#value' => theme('username', $node));
+    $form[$node->nid]['date']     = array('#value' => format_date($node->created));
+    $form[$node->nid]['edit']     = array('#value' => l(t('edit'), 'node/' . $node->nid . '/edit'));
+    $query_string = nodequeue_get_query_string($node->position, TRUE);
+    $form[$node->nid]['remove']   = array('#value' => l(
+    theme('image', drupal_get_path('module', 'nodequeue') .'/images/delete.png', t('Remove from queue')),
+    '',
+    array('attributes' => array(
+      'title' => t('Remove from queue'),
+      'class' => 'nodequeue-remove',
+      'style' => 'display:none;',
+      'id' => 'nodequeue-remove-' . $node->nid,
+      ),
+      'html' => TRUE
+    )));
+    
+    $form[$node->nid]['position'] = array(
+      '#type' => 'position',
+      '#delta' => $count,
+      '#default_value' => $node->position,
+      '#attributes' => array(
+        'class' => 'node-position',
+      ),
+    );
+  }
+
+  // add submit, reverse, and shuffle buttons
+  $form['submit'] = array(
+    '#type' => 'submit',
+    '#value' => t('Save')
+  );
+  $form['reverse'] = array(
+    '#type' => 'submit',
+    '#value' => t('Reverse'),
+    '#submit' => array('nodequeue_arrange_subqueue_form_reverse_submit'),
+  );
+  $form['shuffle'] = array(
+    '#type' => 'submit',
+    '#value' => t('Shuffle'),
+    '#submit' => array('nodequeue_arrange_subqueue_form_shuffle_submit'),
+  );
+
+  return $form;
+}
+
+/**
+ * Submit handler for nodequeue drag'n'drop form. Updates node positions in {nodequeue_nodes}.
+ */
+function nodequeue_arrange_subqueue_form_submit($form, &$form_state, $reverse = FALSE, $shuffle = FALSE) {
+  $nodes = array();
+  $positions = array();
+  foreach ($form_state['values'] as $nid => $element) {
+    if (is_numeric($nid)) {
+      $nodes[$form_state['values'][$nid]['position']] = $nid;
+    }
+  }
+  
+  // reverse the list if the queue is set to display in reverse or if the
+  // reverse button was clicked.
+  ksort($nodes);
+  if ($form['#queue']['reverse'] xor $reverse) {
+    krsort($nodes);
+  }
+  // shuffle the list if the shuffle button was pressed.
+  if ($shuffle) {
+    $keys = array_keys($nodes);
+    $values = array_values($nodes);
+    shuffle($values);
+    $nodes = array_combine($keys, $values);
+  }
+  
+  $qid  = $form['#queue']['qid'];
+  $sqid = $form['#subqueue']['sqid'];
+  $return = nodequeue_save_subqueue_order($nodes, $qid, $sqid);
+  drupal_set_message(t($return[1]));
+}
+
+function nodequeue_arrange_subqueue_form_validate($form, &$form_state) {
+  $positions = array();
+    foreach ($form_state['values'] as $nid => $element) {
+      if (is_numeric($nid) && is_numeric($element['position'])) {
+        $positions[] = $element['position'];
+      }
+    }
+    if (count(array_unique($positions)) < count($positions)) {
+      //This is also checked in nodequeue_save_subqueue_order to prevent invalid programmatic submissions;
+      //TODO: We could be more descriptive with the form_set_error.
+      form_set_error('', t('Duplicate position value.'));
+    }
+}
+/**
+ * Submit handler for nodequeue drag'n'drop reverse button.
+ */
+function nodequeue_arrange_subqueue_form_reverse_submit($form, &$form_state) {
+  nodequeue_arrange_subqueue_form_submit($form, $form_state, TRUE);
+}
+
+/**
+ * Submit handler for nodequeue drag'n'drop shuffle button.
+ */
+function nodequeue_arrange_subqueue_form_shuffle_submit($form, &$form_state) {
+  nodequeue_arrange_subqueue_form_submit($form, $form_state, FALSE, TRUE);
+}
+
+/**
+ * Return a list of nodes in a specific subqueue.
+ */
+function _nodequeue_dragdrop_get_nodes($queue, $subqueue) {
+  $order = $queue->reverse ? 'DESC' : 'ASC';
+  $result = db_query("SELECT DISTINCT(n.nid), n.title, n.uid, u.name, n.created, nq.position FROM {node} n LEFT JOIN {users} u on n.uid = u.uid LEFT JOIN {nodequeue_nodes} nq ON nq.nid = n.nid WHERE nq.sqid = %d ORDER BY nq.position $order", $subqueue->sqid);
+
+  $nodes = array();
+  while ($node = db_fetch_object($result)) {
+    $nodes[] = $node;
+  }
+
+  return $nodes;
+}
+
+
+/**
+ * Theme the subqueue overview as a sortable list.
+ */
+function theme_nodequeue_arrange_subqueue_form($form) {
+  $output = '';
+
+  // get css to hide some of the help text if javascript is disabled
+  drupal_add_css(drupal_get_path('module', 'nodequeue') . '/nodequeue.css');
+  $subqueue = $form['#subqueue'];
+  //TODO: Would be nice to expose qid, sqid, reference as classes for more custom theming :).
+  drupal_add_tabledrag('nodequeue-dragdrop-' . $subqueue['qid'] . '-' . $subqueue['sqid'], 'order', 'sibling', 'node-position');
+  drupal_add_js(drupal_get_path('module', 'nodequeue') . '/nodequeue_dragdrop.js');
+
+  // render form as table rows
+  $rows = array();
+  foreach (element_children($form) as $key) {
+    if (isset($form[$key]['title'])) {
+      $row = array();
+
+      $row[] = drupal_render($form[$key]['title']);
+      $row[] = drupal_render($form[$key]['author']);
+      $row[] = drupal_render($form[$key]['date']);
+      $row[] = drupal_render($form[$key]['position']);
+      $row[] = drupal_render($form[$key]['edit']);
+      $row[] = drupal_render($form[$key]['remove']);
+
+      $rows[] = array(
+        'data'  => $row,
+        'class' => 'draggable',
+      );
+    }
+  }
+  if (empty($rows)) {
+    $rows[] = array(array('data' => t('No nodes in this queue.'), 'colspan' => '4'));
+  }
+
+  $header = array(t('Title'), t('Author'), t('Date'), t('Position'), array('data' => t('Operations'), 'colspan' => '2'));
+  $subqueue = $form['#subqueue'];
+  $output .= theme('table', $header, $rows, array('id' => 'nodequeue-dragdrop-' . $subqueue['qid'] . '-' . $subqueue['sqid'], 'class' => 'nodequeue-dragdrop'));
+  $output .= drupal_render($form);
+  return $output;
+}
\ No newline at end of file
