diff options
-rw-r--r-- | includes/common.inc | 1 | ||||
-rw-r--r-- | includes/form.inc | 135 | ||||
-rw-r--r-- | misc/ahah.js | 259 | ||||
-rw-r--r-- | misc/ajax.js | 383 | ||||
-rw-r--r-- | misc/autocomplete.js | 2 | ||||
-rw-r--r-- | misc/drupal.js | 4 | ||||
-rw-r--r-- | misc/progress.js | 2 | ||||
-rw-r--r-- | modules/book/book.css | 2 | ||||
-rw-r--r-- | modules/book/book.module | 5 | ||||
-rw-r--r-- | modules/book/book.pages.inc | 42 | ||||
-rw-r--r-- | modules/field/field.form.inc | 26 | ||||
-rw-r--r-- | modules/field/field.test | 15 | ||||
-rw-r--r-- | modules/poll/poll.module | 10 | ||||
-rw-r--r-- | modules/poll/poll.test | 19 | ||||
-rw-r--r-- | modules/simpletest/tests/form_test.module | 2 | ||||
-rw-r--r-- | modules/system/system-rtl.css | 4 | ||||
-rw-r--r-- | modules/system/system.css | 8 | ||||
-rw-r--r-- | modules/system/system.module | 28 | ||||
-rw-r--r-- | modules/upload/upload.module | 18 |
19 files changed, 485 insertions, 480 deletions
diff --git a/includes/common.inc b/includes/common.inc index d356a6165..a3579c1f8 100644 --- a/includes/common.inc +++ b/includes/common.inc @@ -3549,6 +3549,7 @@ function _drupal_bootstrap_full() { require_once DRUPAL_ROOT . '/includes/form.inc'; require_once DRUPAL_ROOT . '/includes/mail.inc'; require_once DRUPAL_ROOT . '/includes/actions.inc'; + require_once DRUPAL_ROOT . '/includes/ajax.inc'; // Set the Drupal custom error handler. set_error_handler('_drupal_error_handler'); set_exception_handler('_drupal_exception_handler'); diff --git a/includes/form.inc b/includes/form.inc index 6f7d96305..d6552b043 100644 --- a/includes/form.inc +++ b/includes/form.inc @@ -1801,48 +1801,6 @@ function weight_value(&$form) { } /** - * Menu callback for AHAH callbacks through the #ahah['callback'] FAPI property. - */ -function form_ahah_callback() { - $form_state = form_state_defaults(); - - $form_build_id = $_POST['form_build_id']; - - // Get the form from the cache. - $form = form_get_cache($form_build_id, $form_state); - if (!$form) { - // If $form cannot be loaded from the cache, the form_build_id in $_POST must - // be invalid, which means that someone performed a POST request onto - // system/ahah without actually viewing the concerned form in the browser. - // This is likely a hacking attempt as it never happens under normal - // circumstances, so we just do nothing. - exit; - } - - // We will run some of the submit handlers so we need to disable redirecting. - $form['#redirect'] = FALSE; - - // We need to process the form, prepare for that by setting a few internals - // variables. - $form_state['input'] = $_POST; - $form_state['args'] = $form['#args']; - $form_id = $form['#form_id']; - - // Build, validate and if possible, submit the form. - drupal_process_form($form_id, $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, $form_build_id); - - // Get the callback function from the clicked button. - $callback = $form_state['clicked_button']['#ahah']['callback']; - if (drupal_function_exists($callback)) { - $callback($form, $form_state); - } -} - -/** * Roll out a single radios element to a list of radios, * using the options array as index. */ @@ -1861,7 +1819,7 @@ function form_process_radios($element) { '#attributes' => $element['#attributes'], '#parents' => $element['#parents'], '#id' => form_clean_id('edit-' . implode('-', $parents_for_id)), - '#ahah' => isset($element['#ahah']) ? $element['#ahah'] : NULL, + '#ajax' => isset($element['#ajax']) ? $element['#ajax'] : NULL, ); } } @@ -1971,91 +1929,6 @@ function theme_text_format_wrapper($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_process_ahah($element) { - $js_added = &drupal_static(__FUNCTION__, array()); - // Add a reasonable default event handler if none specified. - if (isset($element['#ahah']) && !isset($element['#ahah']['event'])) { - switch ($element['#type']) { - case 'submit': - case 'button': - case 'image_button': - // Use the mousedown instead of the click event because form - // submission via pressing the enter key triggers a click event on - // submit inputs, inappropriately triggering AHAH behaviors. - $element['#ahah']['event'] = 'mousedown'; - // Attach an additional event handler so that AHAH behaviors - // can be triggered still via keyboard input. - $element['#ahah']['keypress'] = TRUE; - break; - case 'password': - case 'textfield': - case 'textarea': - $element['#ahah']['event'] = 'blur'; - break; - case 'radio': - case 'checkbox': - case 'select': - $element['#ahah']['event'] = 'change'; - break; - default: - return $element; - } - } - - // 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($element['#ahah']['callback']) || isset($element['#ahah']['path'])) && isset($element['#ahah']['event']) && !isset($js_added[$element['#id']])) { - drupal_add_library('system', 'form'); - drupal_add_js('misc/ahah.js'); - - $ahah_binding = array( - 'url' => isset($element['#ahah']['callback']) ? url('system/ahah') : url($element['#ahah']['path']), - 'event' => $element['#ahah']['event'], - 'keypress' => empty($element['#ahah']['keypress']) ? NULL : $element['#ahah']['keypress'], - 'wrapper' => empty($element['#ahah']['wrapper']) ? NULL : $element['#ahah']['wrapper'], - 'selector' => empty($element['#ahah']['selector']) ? '#' . $element['#id'] : $element['#ahah']['selector'], - 'effect' => empty($element['#ahah']['effect']) ? 'none' : $element['#ahah']['effect'], - 'method' => empty($element['#ahah']['method']) ? 'replace' : $element['#ahah']['method'], - 'progress' => empty($element['#ahah']['progress']) ? array('type' => 'throbber') : $element['#ahah']['progress'], - 'button' => isset($element['#executes_submit_callback']) ? array($element['#name'] => $element['#value']) : FALSE, - ); - - // Convert a simple #ahah[progress] type string into an array. - if (is_string($ahah_binding['progress'])) { - $ahah_binding['progress'] = array('type' => $ahah_binding['progress']); - } - // Change progress path to a full url. - if (isset($ahah_binding['progress']['path'])) { - $ahah_binding['progress']['url'] = url($ahah_binding['progress']['path']); - } - - // Add progress.js if we're doing a bar display. - if ($ahah_binding['progress']['type'] == 'bar') { - drupal_add_js('misc/progress.js', array('cache' => FALSE)); - } - - drupal_add_js(array('ahah' => array($element['#id'] => $ahah_binding)), 'setting'); - - $js_added[$element['#id']] = TRUE; - $element['#cache'] = TRUE; - } - return $element; -} - -/** * Format a checkbox. * * @param $element @@ -2132,7 +2005,7 @@ function form_process_checkboxes($element) { '#return_value' => $key, '#default_value' => isset($value[$key]), '#attributes' => $element['#attributes'], - '#ahah' => isset($element['#ahah']) ? $element['#ahah'] : NULL, + '#ajax' => isset($element['#ajax']) ? $element['#ajax'] : NULL, ); } } @@ -2245,7 +2118,7 @@ function form_process_tableselect($element) { '#return_value' => $key, '#default_value' => isset($value[$key]), '#attributes' => $element['#attributes'], - '#ahah' => isset($element['#ahah']) ? $element['#ahah'] : NULL, + '#ajax' => isset($element['#ajax']) ? $element['#ajax'] : NULL, ); } else { @@ -2260,7 +2133,7 @@ function form_process_tableselect($element) { '#attributes' => $element['#attributes'], '#parents' => $element['#parents'], '#id' => form_clean_id('edit-' . implode('-', $parents_for_id)), - '#ahah' => isset($element['#ahah']) ? $element['#ahah'] : NULL, + '#ajax' => isset($element['#ajax']) ? $element['#ajax'] : NULL, ); } } diff --git a/misc/ahah.js b/misc/ahah.js deleted file mode 100644 index 84ff07082..000000000 --- a/misc/ahah.js +++ /dev/null @@ -1,259 +0,0 @@ -// $Id$ -(function ($) { - -/** - * 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. - */ - -Drupal.ahah = Drupal.ahah || {}; - -/** - * Attaches the ahah behavior to each ahah form element. - */ -Drupal.behaviors.ahah = { - attach: function (context, settings) { - for (var base in settings.ahah) { - if (!$('#' + base + '.ahah-processed').size()) { - var element_settings = settings.ahah[base]; - - $(element_settings.selector).each(function () { - element_settings.element = this; - Drupal.ahah[base] = new Drupal.ahah(base, element_settings); - }); - - $('#' + base).addClass('ahah-processed'); - } - } - } -}; - -/** - * AHAH object. - * - * All AHAH objects on a page are accessible through the global Drupal.ahah object - * and are keyed by the submit button's ID. You can access them from your module's - * JavaScript file to override properties or functions. - * For example, if your AHAH enabled button has the ID 'edit-submit', you can - * redefine the function that is called to insert the new content like this - * (inside a Drupal.behaviors attach block): - * @code - * Drupal.behaviors.myCustomAhahStuff = { - * attach: function(context, settings) { - * Drupal.ahah['edit-submit'].insertNewContent = function(response, status) { - * new_content = $(response.data); - * $('#my-wrapper').append(new_content); - * alert('New content was appended to #my-wrapper'); - * } - * } - * }; - * @endcode - */ -Drupal.ahah = function (base, element_settings) { - // Set the properties for this object. - this.element = element_settings.element; - this.selector = element_settings.selector; - this.event = element_settings.event; - this.keypress = element_settings.keypress; - this.url = element_settings.url; - this.wrapper = '#' + element_settings.wrapper; - this.effect = element_settings.effect; - this.method = element_settings.method; - this.progress = element_settings.progress; - this.button = element_settings.button || { }; - - if (this.effect == 'none') { - this.showEffect = 'show'; - this.hideEffect = 'hide'; - this.showSpeed = ''; - } - else if (this.effect == 'fade') { - this.showEffect = 'fadeIn'; - this.hideEffect = 'fadeOut'; - this.showSpeed = 'slow'; - } - else { - this.showEffect = this.effect + 'Toggle'; - this.hideEffect = this.effect + 'Toggle'; - this.showSpeed = 'slow'; - } - - // Record the form action and target, needed for iFrame file uploads. - var form = $(this.element).parents('form'); - this.form_action = form.attr('action'); - this.form_target = form.attr('target'); - this.form_encattr = form.attr('encattr'); - - // Set the options for the ajaxSubmit function. - // The 'this' variable will not persist inside of the options object. - var ahah = this; - var options = { - url: ahah.url, - data: ahah.button, - beforeSubmit: function (form_values, element_settings, options) { - return ahah.beforeSubmit(form_values, element_settings, options); - }, - success: function (response, status) { - // Sanity check for browser support (object expected). - // When using iFrame uploads, responses must be returned as a string. - if (typeof response == 'string') { - response = Drupal.parseJson(response); - } - return ahah.success(response, status); - }, - complete: function (response, status) { - if (status == 'error' || status == 'parsererror') { - return ahah.error(response, ahah.url); - } - }, - dataType: 'json', - type: 'POST' - }; - - // Bind the ajaxSubmit function to the element event. - $(element_settings.element).bind(element_settings.event, function () { - $(element_settings.element).parents('form').ajaxSubmit(options); - return false; - }); - // If necessary, enable keyboard submission so that AHAH behaviors - // can be triggered through keyboard input as well as e.g. a mousedown - // action. - if (element_settings.keypress) { - $(element_settings.element).keypress(function (event) { - // Detect enter key. - if (event.keyCode == 13) { - $(element_settings.element).trigger(element_settings.event); - return false; - } - }); - } -}; - -/** - * Handler for the form redirection submission. - */ -Drupal.ahah.prototype.beforeSubmit = function (form_values, element, options) { - // Disable the element that received the change. - $(this.element).addClass('progress-disabled').attr('disabled', true); - - // Insert progressbar or throbber. - if (this.progress.type == 'bar') { - var progressBar = new Drupal.progressBar('ahah-progress-' + this.element.id, eval(this.progress.update_callback), this.progress.method, eval(this.progress.error_callback)); - if (this.progress.message) { - progressBar.setProgress(-1, this.progress.message); - } - if (this.progress.url) { - progressBar.startMonitoring(this.progress.url, this.progress.interval || 1500); - } - this.progress.element = $(progressBar.element).addClass('ahah-progress ahah-progress-bar'); - this.progress.object = progressBar; - $(this.element).after(this.progress.element); - } - else if (this.progress.type == 'throbber') { - this.progress.element = $('<div class="ahah-progress ahah-progress-throbber"><div class="throbber"> </div></div>'); - if (this.progress.message) { - $('.throbber', this.progress.element).after('<div class="message">' + this.progress.message + '</div>'); - } - $(this.element).after(this.progress.element); - } -}; - -/** - * Handler for the form redirection completion. - */ -Drupal.ahah.prototype.success = function (response, status) { - var form = $(this.element).parents('form'); - - // Restore the previous action and target to the form. - form.attr('action', this.form_action); - this.form_target ? form.attr('target', this.form_target) : form.removeAttr('target'); - this.form_encattr ? form.attr('target', this.form_encattr) : form.removeAttr('encattr'); - - // Remove the progress element. - if (this.progress.element) { - $(this.progress.element).remove(); - } - if (this.progress.object) { - this.progress.object.stopMonitoring(); - } - $(this.element).removeClass('progress-disabled').attr('disabled', false); - - Drupal.freezeHeight(); - - // Call the insertNewContent handler to insert the new content into the page. - this.insertNewContent(response, status); - - Drupal.unfreezeHeight(); -}; - -/** - * Handler to insert the new content into the page. - */ -Drupal.ahah.prototype.insertNewContent = function (response, status) { - var wrapper = $(this.wrapper); - - // Manually insert HTML into the jQuery object, using $() directly crashes - // Safari with long string lengths. http://dev.jquery.com/ticket/1152 - var new_content = $('<div></div>').html(response.data); - - // Add the new content to the page. - if (this.method == 'replace') { - wrapper.empty().append(new_content); - } - else { - wrapper[this.method](new_content); - } - - // Immediately hide the new content if we're using any effects. - if (this.showEffect != 'show') { - new_content.hide(); - } - - // Determine what effect use and what content will receive the effect, then - // show the new content. - if ($('.ahah-new-content', new_content).size() > 0) { - $('.ahah-new-content', new_content).hide(); - new_content.show(); - $('.ahah-new-content', new_content)[this.showEffect](this.showSpeed); - } - else if (this.showEffect != 'show') { - new_content[this.showEffect](this.showSpeed); - } - - // Attach all javascript behaviors to the new content, if it was successfully - // added to the page, this if statement allows #ahah[wrapper] to be optional. - if (new_content.parents('html').length > 0) { - // Apply any settings from the returned JSON if available. - var settings = response.settings || Drupal.settings; - Drupal.attachBehaviors(new_content, settings); - } -}; - -/** - * Handler for the form redirection error. - */ -Drupal.ahah.prototype.error = function (response, uri) { - alert(Drupal.ahahError(response, uri)); - // Resore the previous action and target to the form. - $(this.element).parent('form').attr({ action: this.form_action, target: this.form_target }); - // Remove the progress element. - if (this.progress.element) { - $(this.progress.element).remove(); - } - if (this.progress.object) { - this.progress.object.stopMonitoring(); - } - // Undo hide. - $(this.wrapper).show(); - // Re-enable the element. - $(this.element).removeClass('progress-disabled').attr('disabled', false); -}; - -})(jQuery); diff --git a/misc/ajax.js b/misc/ajax.js new file mode 100644 index 000000000..953827f1d --- /dev/null +++ b/misc/ajax.js @@ -0,0 +1,383 @@ +// $Id$ +(function ($) { + +/** + * Provides AJAX page updating via jQuery $.ajax (Asynchronous JavaScript and XML). + * + * AJAX is a method of making a request via Javascript while viewing an HTML + * page. The request returns an array of commands encoded in JSON, which is + * then executed to make any changes that are necessary to the page. + * + * Drupal uses this file to enhance form elements with #ajax['path'] and + * #ajax['wrapper'] properties. If set, this file will automatically be included + * to provide AJAX capabilities. + */ + +Drupal.ajax = Drupal.ajax || {}; + +/** + * Attaches the AJAX behavior to each AJAX form element. + */ +Drupal.behaviors.AJAX = { + attach: function (context, settings) { + // Load all AJAX behaviors specified in the settings. + for (var base in settings.ajax) { + if (!$('#' + base + '.ajax-processed').length) { + var element_settings = settings.ajax[base]; + + $(element_settings.selector).each(function () { + Drupal.ajax[base] = new Drupal.ajax(base, this, element_settings); + }); + + $('#' + base).addClass('ajax-processed'); + } + } + + // Bind AJAX behaviors to all items showing the class. + $('.use-ajax:not(.ajax-processed)').addClass('ajax-processed').each(function () { + var element_settings = {}; + + // For anchor tags, these will go to the target of the anchor rather + // than the usual location. + if ($(this).attr('href')) { + element_settings.url = $(this).attr('href'); + } + var base = $(this).attr('id'); + Drupal.ajax[base] = new Drupal.ajax(base, this, element_settings); + }); + + // This class means to submit the form to the action using AJAX. + $('.use-ajax-submit:not(.ajax-processed)').addClass('ajax-processed').each(function () { + var element_settings = {}; + + // AJAX submits specified in this manner automatically submit to the + // normal form action. + element_settings.url = $(this.form).attr('action'); + element_settings.set_click = TRUE; + + var base = $(this).attr('id'); + Drupal.ajax[base] = new Drupal.ajax(base, this, element_settings); + }); + } +}; + +/** + * AJAX object. + * + * All AJAX objects on a page are accessible through the global Drupal.ajax + * object and are keyed by the submit button's ID. You can access them from + * your module's JavaScript file to override properties or functions. + * + * For example, if your AJAX enabled button has the ID 'edit-submit', you can + * redefine the function that is called to insert the new content like this + * (inside a Drupal.behaviors attach block): + * @code + * Drupal.behaviors.myCustomAJAXStuff = { + * attach: function (context, settings) { + * Drupal.ajax['edit-submit'].commands.insert = function (ajax, response, status) { + * new_content = $(response.data); + * $('#my-wrapper').append(new_content); + * alert('New content was appended to #my-wrapper'); + * } + * } + * }; + * @endcode + */ +Drupal.ajax = function (base, element, element_settings) { + var defaults = { + url: 'system/ajax', + event: 'mousedown', + keypress: true, + selector: '#' + base, + effect: 'none', + speed: 'slow', + method: 'replace', + progress: { + type: 'bar', + message: 'Please wait...' + }, + button: {}, + }; + + $.extend(this, defaults, element_settings); + + this.element = element; + + // Replacing 'nojs' with 'ajax' in the URL allows for an easy method to let + // the server detect when it needs to degrade gracefully. + this.url = element_settings.url.replace('/nojs/', '/ajax/'); + this.wrapper = '#' + element_settings.wrapper; + + // If there isn't a form, jQuery.ajax() will be used instead, allowing us to + // bind AJAX to links as well. + if (this.element.form) { + this.form = $(this.element.form); + } + + // Set the options for the ajaxSubmit function. + // The 'this' variable will not persist inside of the options object. + var ajax = this; + var options = { + url: ajax.url, + data: ajax.button, + beforeSubmit: function (form_values, element_settings, options) { + return ajax.beforeSubmit(form_values, element_settings, options); + }, + success: function (response, status) { + // Sanity check for browser support (object expected). + // When using iFrame uploads, responses must be returned as a string. + if (typeof response == 'string') { + response = Drupal.parseJson(response); + } + return ajax.success(response, status); + }, + complete: function (response, status) { + if (status == 'error' || status == 'parsererror') { + return ajax.error(response, ajax.url); + } + }, + dataType: 'json', + type: 'POST' + }; + + // Bind the ajaxSubmit function to the element event. + $(this.element).bind(element_settings.event, function () { + if (ajax.form) { + // If setClick is set, we must set this to ensure that the button's + // value is passed. + if (ajax.setClick) { + // Mark the clicked button. 'form.clk' is a special variable for + // ajaxSubmit that tells the system which element got clicked to + // trigger the submit. Without it there would be no 'op' or + // equivalent. + ajax.form.clk = this.element; + } + + ajax.form.ajaxSubmit(options); + } + else { + $.ajax(options); + } + + return false; + }); + + // If necessary, enable keyboard submission so that AJAX behaviors + // can be triggered through keyboard input as well as e.g. a mousedown + // action. + if (element_settings.keypress) { + $(element_settings.element).keypress(function (event) { + // Detect enter key. + if (event.keyCode == 13) { + $(element_settings.element).trigger(element_settings.event); + return false; + } + }); + } +}; + +/** + * Handler for the form redirection submission. + */ +Drupal.ajax.prototype.beforeSubmit = function (form_values, element, options) { + // Disable the element that received the change. + $(this.element).addClass('progress-disabled').attr('disabled', true); + + // Insert progressbar or throbber. + if (this.progress.type == 'bar') { + var progressBar = new Drupal.progressBar('ajax-progress-' + this.element.id, eval(this.progress.update_callback), this.progress.method, eval(this.progress.error_callback)); + if (this.progress.message) { + progressBar.setProgress(-1, this.progress.message); + } + if (this.progress.url) { + progressBar.startMonitoring(this.progress.url, this.progress.interval || 1500); + } + this.progress.element = $(progressBar.element).addClass('ajax-progress ajax-progress-bar'); + this.progress.object = progressBar; + $(this.element).after(this.progress.element); + } + else if (this.progress.type == 'throbber') { + this.progress.element = $('<div class="ajax-progress ajax-progress-throbber"><div class="throbber"> </div></div>'); + if (this.progress.message) { + $('.throbber', this.progress.element).after('<div class="message">' + this.progress.message + '</div>'); + } + $(this.element).after(this.progress.element); + } +}; + +/** + * Handler for the form redirection completion. + */ +Drupal.ajax.prototype.success = function (response, status) { + // Remove the progress element. + if (this.progress.element) { + $(this.progress.element).remove(); + } + if (this.progress.object) { + this.progress.object.stopMonitoring(); + } + $(this.element).removeClass('progress-disabled').attr('disabled', false); + + Drupal.freezeHeight(); + + for (i in response) { + if (response[i]['command'] && this.commands[response[i]['command']]) { + this.commands[response[i]['command']](this, response[i], status); + } + } + + Drupal.unfreezeHeight(); + + // Remove any response-specific settings so they don't get used on the next + // call by mistake. + this.settings = {}; +}; + +/** + * Build an effect object which tells us how to apply the effect when adding new HTML. + */ +Drupal.ajax.prototype.getEffect = function (response) { + var type = response.effect || this.effect; + var speed = response.speed || this.speed; + + var effect = {}; + if (type == 'none') { + effect.showEffect = 'show'; + effect.hideEffect = 'hide'; + effect.showSpeed = ''; + } + else if (type == 'fade') { + effect.showEffect = 'fadeIn'; + effect.hideEffect = 'fadeOut'; + effect.showSpeed = speed; + } + else { + effect.showEffect = type + 'Toggle'; + effect.hideEffect = type + 'Toggle'; + effect.showSpeed = speed; + } + + return effect; +} + +/** + * Handler for the form redirection error. + */ +Drupal.ajax.prototype.error = function (response, uri) { + alert(Drupal.ajaxError(response, uri)); + // Remove the progress element. + if (this.progress.element) { + $(this.progress.element).remove(); + } + if (this.progress.object) { + this.progress.object.stopMonitoring(); + } + // Undo hide. + $(this.wrapper).show(); + // Re-enable the element. + $(this.element).removeClass('progress-disabled').attr('disabled', false); +}; + +/** + * Provide a series of commands that the server can request the client perform. + */ +Drupal.ajax.prototype.commands = { + /** + * Command to insert new content into the DOM. + */ + insert: function (ajax, response, status) { + // Get information from the response. If it is not there, default to + // our presets. + var wrapper = response.selector ? $(response.selector) : $(ajax.wrapper); + var method = response.method || ajax.method; + var effect = ajax.getEffect(response); + + // Manually insert HTML into the jQuery object, using $() directly crashes + // Safari with long string lengths. http://dev.jquery.com/ticket/3178 + var new_content = $('<div></div>').html(response.data); + + // Add the new content to the page. + wrapper[method](new_content); + + // Immediately hide the new content if we're using any effects. + if (effect.showEffect != 'show') { + new_content.hide(); + } + + // Determine which effect to use and what content will receive the + // effect, then show the new content. + if ($('.ajax-new-content', new_content).length > 0) { + $('.ajax-new-content', new_content).hide(); + new_content.show(); + $('.ajax-new-content', new_content)[effect.showEffect](effect.showSpeed); + } + else if (effect.showEffect != 'show') { + new_content[effect.showEffect](effect.showSpeed); + } + + // Attach all JavaScript behaviors to the new content, if it was successfully + // added to the page, this if statement allows #ajax['wrapper'] to be + // optional. + if (new_content.parents('html').length > 0) { + // Apply any settings from the returned JSON if available. + var settings = response.settings || ajax.settings || Drupal.settings; + Drupal.attachBehaviors(new_content, settings); + } + }, + + /** + * Command to remove a chunk from the page. + */ + remove: function (ajax, response, status) { + $(response.selector).remove(); + }, + + /** + * Command to mark a chunk changed. + */ + changed: function (ajax, response, status) { + if (!$(response.selector).hasClass('ajax-changed')) { + $(response.selector).addClass('ajax-changed'); + if (response.asterisk) { + $(response.selector).find(response.asterisk).append(' <span class="ajax-changed">*</span> '); + } + } + }, + + /** + * Command to provide an alert. + */ + alert: function (ajax, response, status) { + alert(response.text, response.title); + }, + + /** + * Command to set the settings that will be used for other commands in this response. + */ + settings: function (ajax, response, status) { + ajax.settings = response.settings; + }, + + /** + * Command to attach data using jQuery's data API. + */ + data: function (ajax, response, status) { + $(response.selector).data(response.name, response.value); + }, + + /** + * Command to restripe a table. + */ + restripe: function (ajax, response, status) { + // :even and :odd are reversed because jQuery counts from 0 and + // we count from 1, so we're out of sync. + $('tbody tr:not(:hidden)', $(response.selector)) + .removeClass('even').removeClass('odd') + .filter(':even') + .addClass('odd').end() + .filter(':odd') + .addClass('even'); + } +}; + +})(jQuery); diff --git a/misc/autocomplete.js b/misc/autocomplete.js index bed9b65ae..87e724d46 100644 --- a/misc/autocomplete.js +++ b/misc/autocomplete.js @@ -289,7 +289,7 @@ Drupal.ACDB.prototype.search = function (searchString) { } }, error: function (xmlhttp) { - alert(Drupal.ahahError(xmlhttp, db.uri)); + alert(Drupal.ajaxError(xmlhttp, db.uri)); } }); }, this.delay); diff --git a/misc/drupal.js b/misc/drupal.js index 40a21b0f1..dbd057bb9 100644 --- a/misc/drupal.js +++ b/misc/drupal.js @@ -294,9 +294,9 @@ Drupal.getSelection = function (element) { }; /** - * Build an error message from ahah response. + * Build an error message from an AJAX response. */ -Drupal.ahahError = function (xmlhttp, uri) { +Drupal.ajaxError = function (xmlhttp, uri) { if (xmlhttp.status == 200 || (xmlhttp.status == 500 && xmlhttp.statusText == 'Service unavailable (with message)')) { if ($.trim(xmlhttp.responseText)) { var message = Drupal.t("An error occurred. \nPath: @uri\nMessage: !text", { '@uri': uri, '!text': xmlhttp.responseText }); diff --git a/misc/progress.js b/misc/progress.js index 2d95573c2..36b297fad 100644 --- a/misc/progress.js +++ b/misc/progress.js @@ -84,7 +84,7 @@ Drupal.progressBar.prototype.sendPing = function () { pb.timer = setTimeout(function () { pb.sendPing(); }, pb.delay); }, error: function (xmlhttp) { - pb.displayError(Drupal.ahahError(xmlhttp, pb.uri)); + pb.displayError(Drupal.ajaxError(xmlhttp, pb.uri)); } }); } diff --git a/modules/book/book.css b/modules/book/book.css index 8c62e4ae9..757b4bdd2 100644 --- a/modules/book/book.css +++ b/modules/book/book.css @@ -47,7 +47,7 @@ html.js #edit-book-pick-book { #book-admin-edit select.progress-disabled { margin-right: 0; } -#book-admin-edit tr.ahah-new-content { +#book-admin-edit tr.ajax-new-content { background-color: #ffd; } #book-admin-edit .form-item { diff --git a/modules/book/book.module b/modules/book/book.module index 9f2e6f241..3b5e88868 100644 --- a/modules/book/book.module +++ b/modules/book/book.module @@ -490,10 +490,11 @@ function _book_add_form_elements(&$form, $node) { '#description' => t('Your page will be a part of the selected book.'), '#weight' => -5, '#attributes' => array('class' => 'book-title-select'), - '#ahah' => array( + '#ajax' => array( 'path' => 'book/js/form', 'wrapper' => 'edit-book-plid-wrapper', - 'effect' => 'slide', + 'effect' => 'fade', + 'speed' => 'fast', ), ); } diff --git a/modules/book/book.pages.inc b/modules/book/book.pages.inc index cc61821ad..3e139cd81 100644 --- a/modules/book/book.pages.inc +++ b/modules/book/book.pages.inc @@ -234,28 +234,26 @@ function book_remove_form_submit($form, &$form_state) { * Prints the replacement HTML in JSON format. */ function book_form_update() { - $cached_form_state = array(); + // Load the form based upon the $_POST data sent via the ajax call. + list($form, $form_state) = ajax_get_form(); + + $commands = array(); $bid = $_POST['book']['bid']; - if ($form = form_get_cache($_POST['form_build_id'], $cached_form_state)) { - // Validate the bid. - if (isset($form['book']['bid']['#options'][$bid])) { - $book_link = $form['#node']->book; - $book_link['bid'] = $bid; - // Get the new options and update the cache. - $form['book']['plid'] = _book_parent_select($book_link); - form_set_cache($_POST['form_build_id'], $form, $cached_form_state); - // Build and render the new select element, then return it in JSON format. - $form_state = array(); - $form = form_builder($form['form_id']['#value'] , $form, $form_state); - $output = drupal_render($form['book']['plid']); - drupal_json(array('status' => TRUE, 'data' => $output)); - } - else { - drupal_json(array('status' => FALSE, 'data' => '')); - } - } - else { - drupal_json(array('status' => FALSE, 'data' => '')); + + // Validate the bid. + if (isset($form['book']['bid']['#options'][$bid])) { + $book_link = $form['#node']->book; + $book_link['bid'] = $bid; + // Get the new options and update the cache. + $form['book']['plid'] = _book_parent_select($book_link); + form_set_cache($form['values']['form_build_id'], $form, $form_state); + + // Build and render the new select element, then return it in JSON format. + $form_state = array(); + $form = form_builder($form['form_id']['#value'], $form, $form_state); + + $commands[] = ajax_command_replace(NULL, drupal_render($form['book']['plid'])); } - exit(); + + ajax_render($commands); } diff --git a/modules/field/field.form.inc b/modules/field/field.form.inc index 20998c349..10f687a56 100644 --- a/modules/field/field.form.inc +++ b/modules/field/field.form.inc @@ -190,7 +190,7 @@ function field_multiple_value_form($field, $instance, $items, &$form, &$form_sta '#value' => t('Add another item'), // Submit callback for disabled JavaScript. '#submit' => array('field_add_more_submit'), - '#ahah' => array( + '#ajax' => array( 'path' => 'field/js_add_more/' . $bundle_name_url_css . '/' . $field_name_url_css, 'wrapper' => $field_name_url_css . '-wrapper', 'method' => 'replace', @@ -362,13 +362,13 @@ function field_add_more_js($bundle_name, $field_name) { } if ($invalid) { - drupal_json(array('data' => '')); - exit; + ajax_render(array()); } // We don't simply return a new empty widget row to append to existing ones, // because: // - ahah.js won't simply let us add a new row to a table + // @todo ajax.js lets you. :) // - attaching the 'draggable' behavior won't be easy // So we resort to rebuilding the whole table of widgets including the // existing ones, which makes us jump through a few hoops. @@ -428,21 +428,15 @@ function field_add_more_js($bundle_name, $field_name) { foreach ($form_path as $key) { $field_form = $field_form[$key]; } - // Add a div around the new field to receive the ahah effect. - $field_form[$delta]['#prefix'] = '<div class="ahah-new-content">' . (isset($field_form[$delta]['#prefix']) ? $field_form[$delta]['#prefix'] : ''); + // Add a DIV around the new field to receive the AJAX effect. + $field_form[$delta]['#prefix'] = '<div class="ajax-new-content">' . (isset($field_form[$delta]['#prefix']) ? $field_form[$delta]['#prefix'] : ''); $field_form[$delta]['#suffix'] = (isset($field_form[$delta]['#suffix']) ? $field_form[$delta]['#suffix'] : '') . '</div>'; // Prevent duplicate wrapper. unset($field_form['#prefix'], $field_form['#suffix']); - // If a newly inserted widget contains AHAH behaviors, they normally won't - // work because AHAH doesn't know about those - it just attaches to the exact - // form elements that were initially specified in the Drupal.settings object. - // The new ones didn't exist then, so we need to update Drupal.settings - // by ourselves in order to let AHAH know about those new form elements. - $javascript = drupal_add_js(NULL, NULL); - $output_js = isset($javascript['setting']) ? '<script type="text/javascript">jQuery.extend(Drupal.settings, ' . drupal_to_js(call_user_func_array('array_merge_recursive', $javascript['setting'])) . ');</script>' : ''; - - $output = theme('status_messages') . drupal_render($field_form) . $output_js; - drupal_json(array('status' => TRUE, 'data' => $output)); - exit; + $output = theme('status_messages') . drupal_render($field_form); + + $commands = array(); + $commands[] = ajax_command_replace(NULL, $output); + ajax_render($commands); } diff --git a/modules/field/field.test b/modules/field/field.test index fce0fd8c2..49a1694bf 100644 --- a/modules/field/field.test +++ b/modules/field/field.test @@ -1336,12 +1336,19 @@ class FieldFormTestCase extends DrupalWebTestCase { unset($this->additionalCurlOptions[CURLOPT_URL]); // The response is drupal_json, so we need to undo some escaping. - $response = json_decode(str_replace(array('\x3c', '\x3e', '\x26'), array("<", ">", "&"), $this->drupalGetContent())); - $this->assertTrue(is_object($response), t('The response is an object')); - $this->assertIdentical($response->status, TRUE, t('Response status is true')); + $commands = json_decode(str_replace(array('\x3c', '\x3e', '\x26'), array("<", ">", "&"), $this->drupalGetContent())); + + // The JSON response will be two AJAX commands. The first is a settings + // command and the second is the replace command. + $settings = reset($commands); + $replace = next($commands); + + $this->assertTrue(is_object($settings), t('The response settings command is an object')); + $this->assertTrue(is_object($replace), t('The response replace command is an object')); + // This response data is valid HTML so we will can reuse everything we have // for HTML pages. - $this->content = $response->data; + $this->content = $replace->data; // Needs to be emptied out so the new content will be parsed. $this->elements = ''; diff --git a/modules/poll/poll.module b/modules/poll/poll.module index 26236fd7d..b8493a397 100644 --- a/modules/poll/poll.module +++ b/modules/poll/poll.module @@ -263,14 +263,14 @@ function poll_form($node, $form_state) { } // We name our button 'poll_more' to avoid conflicts with other modules using - // AHAH-enabled buttons with the id 'more'. + // AJAX-enabled buttons with the id 'more'. $form['choice_wrapper']['poll_more'] = array( '#type' => 'submit', '#value' => t('More choices'), '#description' => t("If the amount of boxes above isn't enough, click here to add more choices."), '#weight' => 1, '#submit' => array('poll_more_choices_submit'), // If no javascript action. - '#ahah' => array( + '#ajax' => array( 'callback' => 'poll_choice_js', 'wrapper' => 'poll-choices', 'method' => 'replace', @@ -320,7 +320,7 @@ function poll_more_choices_submit($form, &$form_state) { // Make the changes we want to the form state. if ($form_state['values']['poll_more']) { - $n = $_GET['q'] == 'system/ahah' ? 1 : 5; + $n = $_GET['q'] == 'system/ajax' ? 1 : 5; $form_state['choice_count'] = count($form_state['values']['choice']) + $n; } } @@ -373,9 +373,7 @@ function poll_choice_js($form, $form_state) { // Prevent duplicate wrappers. unset($choice_form['#prefix'], $choice_form['#suffix']); - $output = theme('status_messages') . drupal_render($choice_form); - - drupal_json(array('status' => TRUE, 'data' => $output)); + return theme('status_messages') . drupal_render($choice_form); } /** diff --git a/modules/poll/poll.test b/modules/poll/poll.test index f5b018095..a5f668efa 100644 --- a/modules/poll/poll.test +++ b/modules/poll/poll.test @@ -340,17 +340,24 @@ class PollJSAddChoice extends DrupalWebTestCase { // @TODO: the framework should make it possible to submit a form to a // different URL than its action or the current. For now, we can just force // it. - $this->additionalCurlOptions[CURLOPT_URL] = url('system/ahah', array('absolute' => TRUE)); + $this->additionalCurlOptions[CURLOPT_URL] = url('system/ajax', array('absolute' => TRUE)); $this->drupalPost(NULL, $edit, t('More choices')); unset($this->additionalCurlOptions[CURLOPT_URL]); // The response is drupal_json, so we need to undo some escaping. - $response = json_decode(str_replace(array('\x3c', '\x3e', '\x26'), array("<", ">", "&"), $this->drupalGetContent())); - $this->assertTrue(is_object($response), t('The response is an object')); - $this->assertIdentical($response->status, TRUE, t('Response status is true')); - // This response data is valid HTML so we will can reuse everything we have + $commands = json_decode(str_replace(array('\x3c', '\x3e', '\x26'), array("<", ">", "&"), $this->drupalGetContent())); + + // The JSON response will be two AJAX commands. The first is a settings + // command and the second is the replace command. + $settings = reset($commands); + $replace = next($commands); + + $this->assertTrue(is_object($settings), t('The response settings command is an object')); + $this->assertTrue(is_object($replace), t('The response replace command is an object')); + + // This replace data is valid HTML so we will can reuse everything we have // for HTML pages. - $this->content = $response->data; + $this->content = $replace->data; // Needs to be emptied out so the new content will be parsed. $this->elements = ''; diff --git a/modules/simpletest/tests/form_test.module b/modules/simpletest/tests/form_test.module index 34b495f5d..6646e85a8 100644 --- a/modules/simpletest/tests/form_test.module +++ b/modules/simpletest/tests/form_test.module @@ -294,7 +294,7 @@ function form_test_mock_form_submit($form, &$form_state) { * It uses two steps for editing a virtual "thing". Any changes to it are saved * in the form storage and have to be present during any step. By setting the * request parameter "cache" the form can be tested with caching enabled, as - * it would be the case, if the form would contain some #ahah callbacks. + * it would be the case, if the form would contain some #ajax callbacks. * * @see form_storage_test_form_submit(). */ diff --git a/modules/system/system-rtl.css b/modules/system/system-rtl.css index 0983da8dc..511143a04 100644 --- a/modules/system/system-rtl.css +++ b/modules/system/system-rtl.css @@ -81,10 +81,10 @@ div.teaser-button-wrapper { .progress-disabled { float: right; } -.ahah-progress { +.ajax-progress { float: right; } -.ahah-progress .throbber { +.ajax-progress .throbber { float: right; } input.password-field { diff --git a/modules/system/system.css b/modules/system/system.css index 8e020a33e..531ae65ed 100644 --- a/modules/system/system.css +++ b/modules/system/system.css @@ -450,20 +450,20 @@ html.js .no-js { .progress-disabled { float: left; /* LTR */ } -.ahah-progress { +.ajax-progress { float: left; /* LTR */ } -.ahah-progress .throbber { +.ajax-progress .throbber { width: 15px; height: 15px; margin: 2px; background: transparent url(../../misc/throbber.gif) no-repeat 0px -18px; float: left; /* LTR */ } -tr .ahah-progress .throbber { +tr .ajax-progress .throbber { margin: 0 2px; } -.ahah-progress-bar { +.ajax-progress-bar { width: 16em; } diff --git a/modules/system/system.module b/modules/system/system.module index d6a4ab3ee..001949c04 100644 --- a/modules/system/system.module +++ b/modules/system/system.module @@ -286,7 +286,7 @@ function system_elements() { '#name' => 'op', '#button_type' => 'submit', '#executes_submit_callback' => TRUE, - '#process' => array('form_process_ahah'), + '#process' => array('ajax_process_form'), '#theme_wrappers' => array('button'), ); @@ -295,7 +295,7 @@ function system_elements() { '#name' => 'op', '#button_type' => 'submit', '#executes_submit_callback' => FALSE, - '#process' => array('form_process_ahah'), + '#process' => array('ajax_process_form'), '#theme_wrappers' => array('button'), ); @@ -303,7 +303,7 @@ function system_elements() { '#input' => TRUE, '#button_type' => 'submit', '#executes_submit_callback' => TRUE, - '#process' => array('form_process_ahah'), + '#process' => array('ajax_process_form'), '#return_value' => TRUE, '#has_garbage_value' => TRUE, '#src' => NULL, @@ -315,7 +315,7 @@ function system_elements() { '#size' => 60, '#maxlength' => 128, '#autocomplete_path' => FALSE, - '#process' => array('form_process_text_format', 'form_process_ahah'), + '#process' => array('form_process_text_format', 'ajax_process_form'), '#theme' => 'textfield', '#theme_wrappers' => array('form_element'), ); @@ -324,7 +324,7 @@ function system_elements() { '#input' => TRUE, '#size' => 60, '#maxlength' => 128, - '#process' => array('form_process_ahah'), + '#process' => array('ajax_process_form'), '#theme' => 'password', '#theme_wrappers' => array('form_element'), ); @@ -340,7 +340,7 @@ function system_elements() { '#cols' => 60, '#rows' => 5, '#resizable' => TRUE, - '#process' => array('form_process_text_format', 'form_process_ahah'), + '#process' => array('form_process_text_format', 'ajax_process_form'), '#theme' => 'textarea', '#theme_wrappers' => array('form_element'), ); @@ -355,7 +355,7 @@ function system_elements() { $type['radio'] = array( '#input' => TRUE, '#default_value' => NULL, - '#process' => array('form_process_ahah'), + '#process' => array('ajax_process_form'), '#theme' => 'radio', '#theme_wrappers' => array('form_element'), '#form_element_skip_title' => TRUE, @@ -372,7 +372,7 @@ function system_elements() { $type['checkbox'] = array( '#input' => TRUE, '#return_value' => 1, - '#process' => array('form_process_ahah'), + '#process' => array('ajax_process_form'), '#theme' => 'checkbox', '#theme_wrappers' => array('form_element'), '#form_element_skip_title' => TRUE, @@ -382,7 +382,7 @@ function system_elements() { '#input' => TRUE, '#size' => 0, '#multiple' => FALSE, - '#process' => array('form_process_ahah'), + '#process' => array('ajax_process_form'), '#theme' => 'select', '#theme_wrappers' => array('form_element'), ); @@ -391,7 +391,7 @@ function system_elements() { '#input' => TRUE, '#delta' => 10, '#default_value' => 0, - '#process' => array('form_process_weight', 'form_process_ahah'), + '#process' => array('form_process_weight', 'ajax_process_form'), ); $type['date'] = array( @@ -430,7 +430,7 @@ function system_elements() { $type['hidden'] = array( '#input' => TRUE, - '#process' => array('form_process_ahah'), + '#process' => array('ajax_process_form'), '#theme' => 'hidden', ); @@ -447,7 +447,7 @@ function system_elements() { '#collapsible' => FALSE, '#collapsed' => FALSE, '#value' => NULL, - '#process' => array('form_process_fieldset', 'form_process_ahah'), + '#process' => array('form_process_fieldset', 'ajax_process_form'), '#pre_render' => array('form_pre_render_fieldset'), '#theme_wrappers' => array('fieldset'), ); @@ -476,9 +476,9 @@ function system_menu() { 'access callback' => TRUE, 'type' => MENU_CALLBACK, ); - $items['system/ahah'] = array( + $items['system/ajax'] = array( 'title' => 'AHAH callback', - 'page callback' => 'form_ahah_callback', + 'page callback' => 'ajax_form_callback', 'access callback' => TRUE, 'type' => MENU_CALLBACK, ); diff --git a/modules/upload/upload.module b/modules/upload/upload.module index b04b92894..772e329ed 100644 --- a/modules/upload/upload.module +++ b/modules/upload/upload.module @@ -60,7 +60,7 @@ function upload_permission() { */ function upload_node_links($node, $build_mode) { $links = array(); - + // Display a link with the number of attachments $num_files = 0; foreach ($node->files as $file) { @@ -236,7 +236,7 @@ function upload_form_alter(&$form, $form_state, $form_id) { '#weight' => 30, ); - // Wrapper for fieldset contents (used by ahah.js). + // Wrapper for fieldset contents (used by ajax.js). $form['attachments']['wrapper'] = array( '#prefix' => '<div id="attach-wrapper">', '#suffix' => '</div>', @@ -582,7 +582,7 @@ function _upload_form($node) { '#type' => 'submit', '#value' => t('Attach'), '#name' => 'attach', - '#ahah' => array( + '#ajax' => array( 'path' => 'upload/js', 'wrapper' => 'attach-wrapper', 'progress' => array('type' => 'bar', 'message' => t('Please wait...')), @@ -692,9 +692,11 @@ function upload_js() { $form = form_builder('upload_js', $form, $form_state); $output = theme('status_messages') . drupal_render($form); - // We send the updated file attachments form. - // Don't call drupal_json(). ahah.js uses an iframe and - // the header output by drupal_json() causes problems in some browsers. - print drupal_to_js(array('status' => TRUE, 'data' => $output)); - exit; + $commands = array(); + $commands[] = ajax_command_replace(NULL, $output); + + // AJAX uploads use an <iframe> and some browsers have problems with the + // 'text/javascript' Content-Type header with iframes. Passing FALSE to + // ajax_render() prevents the header from being sent. + ajax_render($commands, FALSE); } |