? drupal_ahah.patch
Index: includes/form.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/form.inc,v
retrieving revision 1.209
diff -u -p -r1.209 form.inc
--- includes/form.inc	1 Jul 2007 17:41:14 -0000	1.209
+++ includes/form.inc	1 Jul 2007 20:25:32 -0000
@@ -1381,6 +1381,48 @@ function expand_radios($element) {
 }
 
 /**
+ * Add AHAH information about a form element to the page to communicate with
+ * javascript. If #ahah_path is set on an element, this additional javascript is
+ * added to the page header to attach the AHAH behaviors. See ahah.js for more
+ * information.
+ * 
+ * @param $element
+ *   An associative array containing the properties of the element.
+ *   Properties used: ahah_event, ahah_path, ahah_wrapper, ahah_parameters,
+ *   ahah_effect.
+ * @return
+ *   None. Additional code is added to the header of the page using
+ *   drupal_add_js.
+ */
+function form_expand_ahah($element) {
+  static $js_added = array();
+
+  // Adding the same javascript settings twice will cause a recursion error,
+  // we avoid the problem by checking if the javascript has already been added.
+  if (!isset($js_added[$element['#id']]) && isset($element['#ahah_event']) && isset($element['#ahah_path'])) {
+    drupal_add_js('misc/ahah.js');
+    drupal_add_js('misc/progress.js');
+
+    $ahah_binding = array(
+      'id' => $element['#id'],
+      'uri' => url($element['#ahah_path']),
+      'event' => $element['#ahah_event'],
+      'effect' => empty($element['#ahah_effect']) ? 'none' : $element['#ahah_effect'],
+      'method' => empty($element['#ahah_method']) ? 'replace' : $element['#ahah_method'],
+    );
+
+    if (!empty($element['#ahah_wrapper'])) {
+      $ahah_binding['wrapper'] = $element['#ahah_wrapper'];
+    }
+
+    drupal_add_js(array('ahah' => array($element['#id'] => $ahah_binding)), 'setting');
+
+    $js_added[$element['#id']] = TRUE;
+  }
+  return $element;
+}
+
+/**
  * Format a form item.
  *
  * @param $element
Index: misc/ahah.js
===================================================================
RCS file: misc/ahah.js
diff -N misc/ahah.js
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ misc/ahah.js	1 Jul 2007 20:25:32 -0000
@@ -0,0 +1,118 @@
+// $Id: $
+
+/**
+ * Provides AJAX-like page updating via AHAH (Asynchronous HTML and HTTP).
+ *
+ * AHAH is a method of making a request via Javascript while viewing an HTML
+ * page. The request returns a small chunk of HTML, which is then directly
+ * injected into the page.
+ *
+ * Drupal uses this file to enhance form elements with #ahah_path and
+ * #ahah_wrapper properties. If set, this file will automatically be included
+ * to provide AHAH capabilities.
+ */
+
+/**
+ * Attaches the ahah behaviour to each ahah form element.
+ */
+Drupal.behaviors.ahah = function(context) {
+  for (var base in Drupal.settings.ahah) {
+    if (!$('#'+ base + '.ahah-processed').size()) {
+      var element = Drupal.settings.ahah[base];
+      var ahah = new Drupal.ahah(base, element);
+      $('#'+ base).addClass('ahah-processed');
+    }
+  }
+};
+
+/**
+ * AHAH object.
+ */
+Drupal.ahah = function(base, element) {
+  // Set the properties for this object.
+  this.id = '#' + base;
+  this.event = element.event;
+  this.uri = element.uri;
+  this.wrapper = '#'+ element.wrapper;
+  this.effect = element.effect;
+  this.method = element.method;
+  if (this.effect == 'none') {
+    this.showEffect = 'show';
+    this.hideEffect = 'hide';
+  }
+  else if (this.effect == 'fade') {
+    this.showEffect = 'fadeIn';
+    this.hideEffect = 'fadeOut';
+  }
+  else {
+    this.showEffect = this.effect + 'Toggle';
+    this.hideEffect = this.effect + 'Toggle';
+  }
+  Drupal.redirectFormButton(this.uri, $(this.id).get(0), this);
+};
+
+/**
+ * Handler for the form redirection submission.
+ */
+Drupal.ahah.prototype.onsubmit = function () {
+  // Insert progressbar and stretch to take the same space.
+  this.progress = new Drupal.progressBar('ahah_progress');
+  this.progress.setProgress(-1, Drupal.t('Please wait...'));
+
+  var wrapper = $(this.wrapper);
+  var button = $(this.id);
+  var progress_element = $(this.progress.element);
+
+  progress_element.css('float', 'left').css({
+    display: 'none',
+    width: '10em',
+    margin: '0 0 0 20px'
+  });
+  button.css('float', 'left').attr('disabled', true).after(progress_element);
+  eval('progress_element.' + this.showEffect + '()');
+};
+
+/**
+ * Handler for the form redirection completion.
+ */
+Drupal.ahah.prototype.oncomplete = function (data) {
+  var wrapper = $(this.wrapper);
+  var button = $(this.id);
+  var progress_element = $(this.progress.element);
+  var new_content = $('<div>' + data + '</div>');
+
+  Drupal.freezeHeight();
+
+  // Remove the progress element.
+  progress_element.remove();
+
+  // Hide the new content before adding to page.
+  new_content.hide();
+
+  // Add the form and re-attach behavior.
+  if (this.method == 'replace') {
+    wrapper.empty().append(new_content);
+  }
+  else {
+    eval('wrapper.' + this.method + '(new_content)');
+  }
+  eval('new_content.' + this.showEffect + '()');
+  button.css('float', 'none').attr('disabled', false);
+
+  Drupal.attachBehaviors(new_content);
+  Drupal.unfreezeHeight();
+};
+
+/**
+ * Handler for the form redirection error.
+ */
+Drupal.ahah.prototype.onerror = function (error) {
+  alert(Drupal.t('An error occurred:\n\n@error', { '@error': error }));
+  // Remove progressbar.
+  $(this.progress.element).remove();
+  this.progress = null;
+  // Undo hide.
+  $(this.wrapper).show();
+  // Re-enable the element.
+  $(this.id).css('float', 'none').attr('disabled', false);
+};
Index: misc/upload.js
===================================================================
RCS file: misc/upload.js
diff -N misc/upload.js
--- misc/upload.js	1 Jul 2007 15:37:08 -0000	1.14
+++ /dev/null	1 Jan 1970 00:00:00 -0000
@@ -1,110 +0,0 @@
-// $Id: upload.js,v 1.14 2007/07/01 15:37:08 dries Exp $
-
-/**
- * Attaches the upload behaviour to the upload form.
- */
-Drupal.behaviors.upload = function(context) {
-  $('input.upload:not(.upload-processed)', context).addClass('upload-processed').each(function () {
-    var uri = this.value;
-    // Extract the base name from the id (edit-attach-url -> attach).
-    var base = this.id.substring(5, this.id.length - 4);
-    var button = base + '-button';
-    var wrapper = base + '-wrapper';
-    var hide = base + '-hide';
-    var upload = new Drupal.jsUpload(uri, button, wrapper, hide);
-    $(this).addClass('upload-processed');
-  });
-};
-
-/**
- * JS upload object.
- */
-Drupal.jsUpload = function(uri, button, wrapper, hide) {
-  // Note: these elements are replaced after an upload, so we re-select them
-  // everytime they are needed.
-  this.button = '#'+ button;
-  this.wrapper = '#'+ wrapper;
-  this.hide = '#'+ hide;
-  Drupal.redirectFormButton(uri, $(this.button).get(0), this);
-};
-
-/**
- * Handler for the form redirection submission.
- */
-Drupal.jsUpload.prototype.onsubmit = function () {
-  // Insert progressbar and stretch to take the same space.
-  this.progress = new Drupal.progressBar('uploadprogress');
-  this.progress.setProgress(-1, Drupal.t('Uploading file'));
-
-  var hide = this.hide;
-  var el = this.progress.element;
-  var offset = $(hide).get(0).offsetHeight;
-  $(el).css({
-    width: '28em',
-    height: offset +'px',
-    paddingTop: '10px',
-    display: 'none'
-  });
-  $(hide).css('position', 'absolute');
-
-  $(hide).after(el);
-  $(el).fadeIn('slow');
-  $(hide).fadeOut('slow');
-};
-
-/**
- * Handler for the form redirection completion.
- */
-Drupal.jsUpload.prototype.oncomplete = function (data) {
-  // Remove old form
-  Drupal.freezeHeight(); // Avoid unnecessary scrolling
-  $(this.wrapper).html('');
-
-  // Place HTML into temporary div
-  var div = document.createElement('div');
-  $(div).html(data);
-
-  // If uploading the first attachment fade in everything
-  if ($('tr', div).size() == 2) {
-    // Replace form and re-attach behaviours
-    $(div).hide();
-    $(this.wrapper).append(div);
-    $(div).fadeIn('slow');
-  }
-  // Else fade in only the last table row
-  else {
-    // Hide form and last table row
-    $('table tr:last-of-type td', div).hide();
-
-    // Note: workaround because jQuery's #id selector does not work outside of 'document'
-    // Should be: $(this.hide, div).hide();
-    var hide = this.hide;
-    $('div', div).each(function() {
-      if (('#'+ this.id) == hide) {
-        this.style.display = 'none';
-      }
-    });
-
-    // Replace form, fade in items and re-attach behaviour
-    $(this.wrapper).append(div);
-    $('table tr:last-of-type td', div).fadeIn('slow');
-    $(this.hide, div).fadeIn('slow');
-  }
-  Drupal.attachBehaviors(div);
-  Drupal.unfreezeHeight();
-};
-
-/**
- * Handler for the form redirection error.
- */
-Drupal.jsUpload.prototype.onerror = function (error) {
-  alert(Drupal.t('An error occurred:\n\n@error', { '@error': error }));
-  // Remove progressbar
-  $(this.progress.element).remove();
-  this.progress = null;
-  // Undo hide
-  $(this.hide).css({
-    position: 'static',
-    left: '0px'
-  });
-};
Index: modules/system/system.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.module,v
retrieving revision 1.504
diff -u -p -r1.504 system.module
--- modules/system/system.module	1 Jul 2007 17:41:16 -0000	1.504
+++ modules/system/system.module	1 Jul 2007 20:25:34 -0000
@@ -98,9 +98,8 @@ function system_elements() {
   $type['form'] = array('#method' => 'post', '#action' => request_uri());
 
   // Inputs
-  $type['checkbox'] = array('#input' => TRUE, '#return_value' => 1);
-  $type['submit'] = array('#input' => TRUE, '#name' => 'op', '#button_type' => 'submit', '#executes_submit_callback' => TRUE);
-  $type['button'] = array('#input' => TRUE, '#name' => 'op', '#button_type' => 'submit', '#executes_submit_callback' => FALSE);
+  $type['submit'] = array('#input' => TRUE, '#name' => 'op', '#button_type' => 'submit', '#executes_submit_callback' => TRUE, '#ahah_event' => 'submit', '#process' => array('form_expand_ahah'));
+  $type['button'] = array('#input' => TRUE, '#name' => 'op', '#button_type' => 'submit', '#executes_submit_callback' => FALSE, '#ahah_event' => 'submit', '#process' => array('form_expand_ahah'));
   $type['textfield'] = array('#input' => TRUE, '#size' => 60, '#maxlength' => 128, '#autocomplete_path' => FALSE);
   $type['password'] = array('#input' => TRUE, '#size' => 60, '#maxlength' => 128);
   $type['password_confirm'] = array('#input' => TRUE, '#process' => array('expand_password_confirm'));
@@ -108,6 +107,7 @@ function system_elements() {
   $type['radios'] = array('#input' => TRUE, '#process' => array('expand_radios'));
   $type['radio'] = array('#input' => TRUE, '#default_value' => NULL);
   $type['checkboxes'] = array('#input' => TRUE, '#process' => array('expand_checkboxes'), '#tree' => TRUE);
+  $type['checkbox'] = array('#input' => TRUE, '#return_value' => 1);
   $type['select'] = array('#input' => TRUE, '#size' => 0, '#multiple' => FALSE);
   $type['weight'] = array('#input' => TRUE, '#delta' => 10, '#default_value' => 0, '#process' => array('process_weight'));
   $type['date'] = array('#input' => TRUE, '#process' => array('expand_date' => array()), '#element_validate' => array('date_validate'));
Index: modules/upload/upload.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/upload/upload.module,v
retrieving revision 1.170
diff -u -p -r1.170 upload.module
--- modules/upload/upload.module	1 Jul 2007 17:41:16 -0000	1.170
+++ modules/upload/upload.module	1 Jul 2007 20:25:34 -0000
@@ -368,9 +368,6 @@ function upload_form_alter(&$form, $form
   if (isset($form['type']) && isset($form['#node'])) {
     $node = $form['#node'];
     if ($form['type']['#value'] .'_node_form' == $form_id && variable_get("upload_$node->type", TRUE)) {
-      drupal_add_js('misc/progress.js');
-      drupal_add_js('misc/upload.js');
-
       // Attachments fieldset
       $form['attachments'] = array(
         '#type' => 'fieldset',
@@ -384,7 +381,7 @@ function upload_form_alter(&$form, $form
         '#weight' => 30,
       );
 
-      // Wrapper for fieldset contents (used by upload JS).
+      // Wrapper for fieldset contents (used by ahah.js).
       $form['attachments']['wrapper'] = array(
         '#prefix' => '<div id="attach-wrapper">',
         '#suffix' => '</div>',
@@ -633,12 +630,6 @@ function _upload_form($node) {
 
   if (user_access('upload files')) {
     $limits = _upload_file_limits($user);
-
-    // This div is hidden when the user uploads through JS.
-    $form['new'] = array(
-      '#prefix' => '<div id="attach-hide">',
-      '#suffix' => '</div>',
-    );
     $form['new']['upload'] = array(
       '#type' => 'file',
       '#title' => t('Attach new file'),
@@ -649,14 +640,13 @@ function _upload_form($node) {
       '#type' => 'submit',
       '#value' => t('Attach'),
       '#name' => 'attach',
-      '#id' => 'attach-button',
+      '#ahah_path' => 'upload/js',
+      '#ahah_wrapper' => 'attach-wrapper',
       '#submit' => array(),
     );
-    // The class triggers the js upload behaviour.
-    $form['attach-url'] = array('#type' => 'hidden', '#value' => url('upload/js', array('absolute' => TRUE)), '#attributes' => array('class' => 'upload'));
   }
 
-  // Needed for JS.
+  // This value is used in upload_js().
   $form['current']['vid'] = array('#type' => 'hidden', '#value' => isset($node->vid) ? $node->vid : 0);
   return $form;
 }
@@ -708,6 +698,7 @@ function upload_load($node) {
 function upload_js() {
   // We only do the upload.module part of the node validation process.
   $node = (object)$_POST;
+  $files = isset($_POST['files']) ? $_POST['files'] : array();
 
   // Load existing node files.
   $node->files = upload_load($node);
@@ -725,8 +716,13 @@ function upload_js() {
   drupal_alter('form', $form, array(), 'upload_js');
   $form_state = array('submitted' => FALSE);
   $form = form_builder('upload_js', $form, $form_state);
-  // @todo: Put status messages inside wrapper, instead of above so they do not
-  // persist across ajax reloads.
+
+  // Maintain the list and delete checkboxes values.
+  foreach ($files as $fid => $file) {
+    $form['files'][$fid]['list']['#value'] = isset($file['list']) ? 1 : 0;
+    $form['files'][$fid]['remove']['#value'] = isset($file['remove']) ? 1 : 0;
+  }
+
   $output = theme('status_messages') . drupal_render($form);
 
   // We send the updated file attachments form.
