? inner.sql
? pre.sql
? station_next_1.patch
? station_playlist_ajax.patch
? schedule/views/station_schedule_plugin_argument_validate_day.inc
Index: station.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/station/station.module,v
retrieving revision 1.57
diff -u -p -r1.57 station.module
--- station.module	22 Sep 2009 18:46:14 -0000	1.57
+++ station.module	30 Sep 2009 04:57:47 -0000
@@ -32,12 +32,12 @@ function station_menu() {
     'type' => MENU_DEFAULT_LOCAL_TASK,
     'weight' => '-10',
   );
+
   $items['station'] = array(
     'title' => 'Station',
     'page callback' => 'station_page',
     'access arguments' => array('access content'),
   );
-
   return $items;
 }
 
@@ -113,6 +113,36 @@ function station_page() {
 
 
 /**
+ * AJAX form handler.
+ */
+function station_ajax_form_handler() {
+  $form_state = array('storage' => NULL, 'submitted' => FALSE);
+  $form_build_id = $_POST['form_build_id'];
+
+  // Get the form from the cache.
+  $form = form_get_cache($form_build_id, $form_state);
+  $args = $form['#parameters'];
+  $form_id = array_shift($args);
+
+  // We need to process the form, prepare for that by setting a few internals.
+  $form_state['post'] = $form['#post'] = $_POST;
+  $form['#programmed'] = $form['#redirect'] = FALSE;
+
+  // Build, validate and if possible, submit the form.
+  drupal_process_form($form_id, $form, $form_state);
+  // If validation fails, force form submission.
+  if (form_get_errors()) {
+    form_execute_handlers('submit', $form, $form_state);
+  }
+
+  // This call recreates the form relying solely on the form_state that the
+  // drupal_process_form set up.
+  $form = drupal_rebuild_form($form_id, $form_state, $args, $form_build_id);
+
+  return $form;
+}
+
+/**
  * Determine if we have a station archive module running locally or access to a
  * remote one.
  *
Index: playlist/station_playlist.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/station/playlist/station_playlist.module,v
retrieving revision 1.23
diff -u -p -r1.23 station_playlist.module
--- playlist/station_playlist.module	27 Sep 2009 06:12:25 -0000	1.23
+++ playlist/station_playlist.module	30 Sep 2009 04:57:47 -0000
@@ -30,6 +30,13 @@ function station_playlist_menu() {
     'access arguments' => array('access content'),
     'type' => MENU_CALLBACK,
   );
+
+  $items['station/ahah/playlist'] = array(
+    'page callback' => 'station_playlist_ahah',
+    'page arguments' => array(),
+    'access arguments' => array('access content'),
+    'type ' => MENU_CALLBACK,
+  );
   return $items;
 }
 
@@ -151,32 +158,44 @@ function station_playlist_access($op, $n
 }
 
 /**
+ * Helper function to build an empty track row.
+ */
+function station_playlist_get_empty_track() {
+  static $track;
+
+  if (!isset($track)) {
+    $track = array(
+      'artist' => '',
+      'album' => '',
+      'title' => '',
+      'label' => '',
+      'link' => '',
+      'weight' => 9999,
+    );
+    drupal_alter('station_playlist_empty_track', $track);
+  }
+  return $track;
+}
+
+/**
  * Implementation of hook_form().
  *
  * Build a form for playlist nodes.
  */
-function station_playlist_form(&$node) {
+function station_playlist_form(&$node, $form_state) {
   // TODO Port this:
   // if this is a new node (with no nid) and we've got a numeric argument,
   // assume that's the program we should attach to.
-  if (empty($node->nid)) {
-    if (is_numeric(arg(3))) {
-      $node->field_station_program[0]['nid'] = (int) arg(3);
-    }
+  if (empty($node->nid) && is_numeric(arg(3))) {
+    $node->field_station_program[0]['nid'] = (int) arg(3);
   }
   if (!isset($node->tracks)) {
-    $node->tracks = array();
+    $row = station_playlist_get_empty_track();
+    for ($i = 0; $i < 10; $i++) {
+      $node->tracks[] = $row;
+    }
   }
 
-  $form['tracks'] = array(
-    '#type' => 'fieldset', '#title' => t('Tracks'),
-    '#tree' => TRUE,
-    '#collapsible' => FALSE,
-    '#weight' => 1,
-    '#theme' => 'station_playlist_track_form',
-    '#description' => t('Enter the tracks played on the show, the artist and title are required. If you provide a link, it needs to begin with <code>http://</code> or you will get a link to a page on this site.') .'<br />'
-      . t('The weight is used to set the order: lower weights rise, heavier sink. If you want to remove a track just clear the artist and title text boxes. If you need more tracks, just save, more will be added. '),
-  );
   $form['body_filter']['body'] = array(
     '#type' => 'textarea',
     '#title' => t('Description'),
@@ -185,98 +204,169 @@ function station_playlist_form(&$node) {
   );
   $form['body_filter']['format'] = filter_form($node->format);
 
-
-  $blanks = 10;
-  $max = max(20, $blanks + count($node->tracks) + 1);
+  // Wrapper to whole all the tracks elements.
+  $form['tracks_wrapper'] = array(
+    '#value' => '<label>'. t('Tracks') .':</label>',
+    '#prefix' => '<div class="clear-block form-item" id="station-playlist-tracks-wrapper">',
+    '#suffix' => '</div>',
+    '#theme' => 'station_playlist_track_form',
+    '#description' => t('Enter the tracks played on the show, the artist and title are required. If you provide a link, it needs to begin with <code>http://</code> or you will get a link to a page on this site.'),
+    '#tree' => FALSE,
+  );
+  // Container to display existing tracks.
+  $form['tracks_wrapper']['tracks'] = array(
+    '#prefix' => '<div id="station-playlist-tracks">',
+    '#suffix' => '</div>',
+    '#tree' => TRUE,
+  );
 
   // load current tracks
-  $weight = 0;
-  foreach ($node->tracks as $key => $track) {
-    $form['tracks'][$weight]['artist'] = array(
-      '#type' => 'textfield', '#size' => 20, '#maxlength' => 255,
+  foreach ($node->tracks as $weight => $track) {
+    $form['tracks_wrapper']['tracks'][$weight]['artist'] = array(
+      '#type' => 'textfield',
+      '#size' => 20,
+      '#maxlength' => 255,
       '#default_value' => isset($track['artist']) ? $track['artist'] : '',
       '#autocomplete_path' => 'station/autocomplete/playlist/artist',
     );
-    $form['tracks'][$weight]['album'] = array(
-      '#type' => 'textfield', '#size' => 20, '#maxlength' => 255,
+    $form['tracks_wrapper']['tracks'][$weight]['album'] = array(
+      '#type' => 'textfield',
+      '#size' => 20,
+      '#maxlength' => 255,
       '#default_value' => isset($track['album']) ? $track['album'] : '',
       '#autocomplete_path' => 'station/autocomplete/playlist/album',
     );
-    $form['tracks'][$weight]['title'] = array(
-      '#type' => 'textfield', '#size' => 20, '#maxlength' => 255,
+    $form['tracks_wrapper']['tracks'][$weight]['title'] = array(
+      '#type' => 'textfield',
+      '#size' => 20,
+      '#maxlength' => 255,
       '#default_value' => isset($track['title']) ? $track['title'] : '',
     );
-    $form['tracks'][$weight]['label'] = array(
-      '#type' => 'textfield', '#size' => 20, '#maxlength' => 255,
+    $form['tracks_wrapper']['tracks'][$weight]['label'] = array(
+      '#type' => 'textfield',
+      '#size' => 20,
+      '#maxlength' => 255,
       '#default_value' => isset($track['label']) ? $track['label'] : '',
       '#autocomplete_path' => 'station/autocomplete/playlist/label',
     );
-    $form['tracks'][$weight]['link'] = array(
-      '#type' => 'textfield', '#size' => 20, '#maxlength' => 255,
+    $form['tracks_wrapper']['tracks'][$weight]['link'] = array(
+      '#type' => 'textfield',
+      '#size' => 20,
+      '#maxlength' => 255,
       '#default_value' => isset($track['link']) ? $track['link'] : '',
     );
-    $form['tracks'][$weight]['weight'] = array(
-      '#type' => 'weight', '#delta' => $max, '#default_value' => $weight,
+    $form['tracks_wrapper']['tracks'][$weight]['weight'] = array(
+      '#type' => 'weight',
+      '#default_value' => $weight,
+      '#delta' => 100,
+    );
+    // Remove button.
+    $form['tracks_wrapper']['tracks'][$weight]['remove'] = array(
+      '#type' => 'submit',
+      '#name' => 'remove_' . $weight,
+      '#value' => t('Remove'),
+      '#submit' => array('station_playlist_form_ahah_track_submit'),
+      '#ahah' => array(
+        'path' => 'station/ahah/playlist',
+        'wrapper' => 'station-playlist-tracks-wrapper',
+        'method' => 'replace',
+        'effect' => 'fade',
+      ),
     );
-    $weight++;
-  }
-  // add some empty tracks
-  for ($i = 0; $i < $blanks; $i++) {
-    $form['tracks'][$weight]['artist'] = array(
-      '#type' => 'textfield', '#size' => 20, '#maxlength' => 255,
-      '#autocomplete_path' => 'station/autocomplete/playlist/artist',
-    );
-    $form['tracks'][$weight]['album'] = array(
-      '#type' => 'textfield', '#size' => 20, '#maxlength' => 255,
-      '#autocomplete_path' => 'station/autocomplete/playlist/album',
-    );
-    $form['tracks'][$weight]['title'] = array(
-      '#type' => 'textfield', '#size' => 20, '#maxlength' => 255,
-    );
-    $form['tracks'][$weight]['label'] = array(
-      '#type' => 'textfield', '#size' => 20, '#maxlength' => 255,
-      '#autocomplete_path' => 'station/autocomplete/playlist/label',
-    );
-    $form['tracks'][$weight]['link'] = array(
-      '#type' => 'textfield', '#size' => 20, '#maxlength' => 255,
-    );
-    $form['tracks'][$weight]['weight'] = array(
-      '#type' => 'weight', '#delta' => $max, '#default_value' => $weight,
-    );
-    $weight++;
   }
 
+  // We name our button 'add_more' to avoid conflicts with other modules using
+  // AHAH-enabled buttons with the id 'more'.
+  $form['tracks_wrapper']['add_more'] = array(
+    '#type' => 'submit',
+    '#value' => t('Add another'),
+    '#weight' => 1,
+    '#submit' => array('station_playlist_form_ahah_track_submit'),
+    '#ahah' => array(
+      'path' => 'station/ahah/playlist',
+      'wrapper' => 'station-playlist-tracks-wrapper',
+      'method' => 'replace',
+      'effect' => 'fade',
+    ),
+  );
+  $form['#validate'][] = 'station_playlist_node_form_validate';
   $form['#submit'][] = 'station_playlist_node_form_submit';
   return $form;
 }
 
+/**
+ * A do nothing submit function to prevent the form from saving.
+ */
+function station_playlist_form_ahah_track_submit($form, &$form_state) {
+  // Set the form to rebuild and run submit handlers.
+  node_form_submit_build_node($form, $form_state);
+}
+
 function theme_station_playlist_track_form($form) {
   // To have the drag and drop not totally wack out the formatting we need
   // the first column in the table with no form element.
-  $header = array('', t('Artist'), t('Title'), t('Album'), t('Label'), t('Link'), t('Weight'));
+  $header = array('', t('Artist'), t('Title'), t('Album'), t('Label'), t('Link'), t('Weight'), '');
 
   $rows = array();
-  foreach (element_children($form) as $key) {
+  foreach (element_children($form['tracks']) as $key) {
     $row = array();
     $row[] = '';
-    $row[] = drupal_render($form[$key]['artist']);
-    $row[] = drupal_render($form[$key]['title']);
-    $row[] = drupal_render($form[$key]['album']);
-    $row[] = drupal_render($form[$key]['label']);
-    $row[] = drupal_render($form[$key]['link']);
-    $form[$key]['weight']['#attributes']['class'] = 'track-weight';
-    $row[] = drupal_render($form[$key]['weight']);
+    $row[] = drupal_render($form['tracks'][$key]['artist']);
+    $row[] = drupal_render($form['tracks'][$key]['title']);
+    $row[] = drupal_render($form['tracks'][$key]['album']);
+    $row[] = drupal_render($form['tracks'][$key]['label']);
+    $row[] = drupal_render($form['tracks'][$key]['link']);
+    $form['tracks'][$key]['weight']['#attributes']['class'] = 'track-weight';
+    $row[] = drupal_render($form['tracks'][$key]['weight']);
+    $row[] = drupal_render($form['tracks'][$key]['remove']);
     $rows[] = array('data' => $row, 'class' => 'draggable');
   }
 
   $output = '';
   if (count($rows)) {
-    drupal_add_tabledrag('station-playlist-tracks', 'order', 'sibling', 'track-weight');
-    $output .= theme('table', $header, $rows, array('id' => 'station-playlist-tracks'));
+    drupal_add_tabledrag('station-playlist-tracks-table', 'order', 'sibling', 'track-weight');
+    $output .= theme('table', $header, $rows, array('id' => 'station-playlist-tracks-table'));
   }
 
   return $output . drupal_render($form);
+}
+
+function station_playlist_node_form_validate($form, &$form_state) {
+  $tracks = $form_state['values']['tracks'];
+
+  // Check if a track should be removed.
+  if (isset($form_state['clicked_button']['#parents'][2]) && $form_state['clicked_button']['#parents'][2] == 'remove') {
+    $delta = $form_state['clicked_button']['#parents'][1];
+    unset($tracks[$delta]);
+  }
+
+  // Sort the table by weight (which might have changed with a tablesort)
+  // then reset the keys.
+  usort($tracks, '_station_playlist_sort_tracks');
+  $tracks = array_values($tracks);
+  // Now strip the previous weights which may not be adjacent and update the
+  // track's weights.
+  foreach ($tracks as $key => $track) {
+    $track['weight'] = $key;
+  }
+
+  // Check if they've requested a new track.
+  if (isset($form_state['clicked_button']['#parents'][0]) && $form_state['clicked_button']['#parents'][0] == 'add_more') {
+    // Add the new row to the tracks list.
+    $tracks[] = station_playlist_get_empty_track();
+  }
+
+  form_set_value($form['tracks_wrapper']['tracks'], $tracks, $form_state);
+}
 
+/**
+ * Helper function to sort tracks by weight.
+ */
+function _station_playlist_sort_tracks($a, $b) {
+  if ((int) $a['weight'] == (int) $b['weight']) {
+    return 0;
+  }
+  return ((int) $a['weight'] < (int) $b['weight']) ? -1 : 1;
 }
 
 /**
@@ -294,6 +384,33 @@ function station_playlist_validate(&$nod
   }
 }
 
+
+
+function station_playlist_ahah() {
+  // The form is generated in an include file which we need to include manually.
+  include_once 'modules/node/node.pages.inc';
+
+  $form = station_ajax_form_handler();
+
+  // Render the new output.
+  $sub_form = $form['tracks_wrapper'];
+  // Prevent duplicate wrappers.
+  unset($sub_form['#prefix'], $sub_form['#suffix']);
+
+  $output = theme('status_messages') . drupal_render($sub_form);
+
+  // AHAH is not being nice to us and doesn't know about the "Remove" button.
+  // This causes it not to attach AHAH behaviours to it after modifying the form.
+  // So we need to tell it first.
+  $javascript = drupal_add_js(NULL, NULL);
+  if (isset($javascript['setting'])) {
+    $output .= '<script type="text/javascript">jQuery.extend(Drupal.settings, '. drupal_to_js(call_user_func_array('array_merge_recursive', $javascript['setting'])) .');</script>';
+  }
+
+  // Final rendering callback.
+  drupal_json(array('status' => TRUE, 'data' => $output));
+}
+
 /**
  * Form submit handler to set the node's title().
  */
@@ -369,7 +486,7 @@ function station_playlist_view(&$node, $
     drupal_set_breadcrumb($breadcrumb);
   }
 
-  if (!$teaser) {
+  if (!$teaser && isset($node->nid)) {
     if ($view = views_get_view('station_playlist_tracks')) {
       $display_id = 'default';
       if ($view->access($display_id)) {
@@ -389,6 +506,8 @@ function station_playlist_view(&$node, $
 
 /**
  * Implementation of hook_nodeapi().
+ *
+ * We inject our past and future playlists onto program nodes here.
  */
 function station_playlist_nodeapi(&$node, $op, $teaser, $page) {
   if ($node->type == 'station_program' && $op == 'view') {
