? 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 = $('
' + data + '
'); + + 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' => '
', '#suffix' => '
', @@ -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' => '
', - '#suffix' => '
', - ); $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.