From 2ceae6ad581701a0ee72eede787583e9140a5eda Mon Sep 17 00:00:00 2001 From: Dries Buytaert Date: Wed, 4 Jul 2007 15:42:38 +0000 Subject: [PATCH] - Patch #154398 by quicksketch et al: add dynamic AHAH submission properties to Forms API. This patch was ready for a long time and is somewhat of a usability improvement (enabler). --- includes/form.inc | 42 +++++++++++++ misc/ahah.js | 118 +++++++++++++++++++++++++++++++++++ modules/system/system.module | 6 +- modules/upload/upload.module | 28 ++++----- 4 files changed, 175 insertions(+), 19 deletions(-) create mode 100644 misc/ahah.js diff --git a/includes/form.inc b/includes/form.inc index 608fe755e57..f135e685b3a 100644 --- a/includes/form.inc +++ b/includes/form.inc @@ -1382,6 +1382,48 @@ function expand_radios($element) { return $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. * diff --git a/misc/ahah.js b/misc/ahah.js new file mode 100644 index 00000000000..7f48bb91de4 --- /dev/null +++ b/misc/ahah.js @@ -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); +}; diff --git a/modules/system/system.module b/modules/system/system.module index 710f5894c62..7008353f248 100644 --- a/modules/system/system.module +++ b/modules/system/system.module @@ -103,9 +103,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')); @@ -113,6 +112,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')); diff --git a/modules/upload/upload.module b/modules/upload/upload.module index 434c87c5967..22419573c10 100644 --- a/modules/upload/upload.module +++ b/modules/upload/upload.module @@ -368,9 +368,6 @@ function upload_form_alter(&$form, $form_state, $form_id) { 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_state, $form_id) { '#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.