From 25b9f686a626fc5424c36555fe4757cb8114d4ea Mon Sep 17 00:00:00 2001 From: Angie Byron Date: Mon, 17 Aug 2009 07:12:16 +0000 Subject: #544418 by merlinofchaos, sun, drewish, quicksketch, et al: Integrate CTools AJAX framework with Drupal to extend (and replace) existing ahah framework. Everything about AJAX/AHAH is more betterer now. --- misc/ajax.js | 383 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 383 insertions(+) create mode 100644 misc/ajax.js (limited to 'misc/ajax.js') 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 = $('
 
'); + if (this.progress.message) { + $('.throbber', this.progress.element).after('
' + this.progress.message + '
'); + } + $(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 = $('
').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(' * '); + } + } + }, + + /** + * 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); -- cgit v1.2.3