? drupal_ahah2.patch ? misc/jquery113.js Index: includes/form.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/form.inc,v retrieving revision 1.214 diff -u -p -r1.214 form.inc --- includes/form.inc 4 Jul 2007 15:45:37 -0000 1.214 +++ includes/form.inc 8 Jul 2007 20:57:41 -0000 @@ -1469,15 +1469,16 @@ function form_expand_ahah($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($js_added[$element['#id']]) && isset($element['#ahah_event']) && isset($element['#ahah_path'])) { + drupal_add_js('misc/jquery.form.js'); drupal_add_js('misc/ahah.js'); drupal_add_js('misc/progress.js'); $ahah_binding = array( - 'id' => $element['#id'], - 'uri' => url($element['#ahah_path']), + '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'], + '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'], ); if (!empty($element['#ahah_wrapper'])) { Index: misc/ahah.js =================================================================== RCS file: /cvs/drupal/drupal/misc/ahah.js,v retrieving revision 1.1 diff -u -p -r1.1 ahah.js --- misc/ahah.js 4 Jul 2007 15:42:38 -0000 1.1 +++ misc/ahah.js 8 Jul 2007 20:57:41 -0000 @@ -31,6 +31,7 @@ Drupal.behaviors.ahah = function(context Drupal.ahah = function(base, element) { // Set the properties for this object. this.id = '#' + base; + this.selector = element.selector; this.event = element.event; this.uri = element.uri; this.wrapper = '#'+ element.wrapper; @@ -39,47 +40,73 @@ Drupal.ahah = function(base, element) { 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'; } - Drupal.redirectFormButton(this.uri, $(this.id).get(0), this); + + // 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.uri, + beforeSubmit: function(form_values, element, options) { + return ahah.onsubmit(form_values, element, options); + }, + success: function(response, status) { + return ahah.oncomplete(response, status); + }, + complete: function(response, status) { + if (status == 'error') { + return ahah.onerror(response.responseText); + } + }, + dataType: 'json', + type: 'POST' + }; + + // Bind the ajaxSubmit function to the element event. + $(ahah.selector).bind(ahah.event, function() { + options.element = this; + $(ahah.id).parents('form').ajaxSubmit(options); + return false; + }); + }; /** * Handler for the form redirection submission. */ -Drupal.ahah.prototype.onsubmit = function () { +Drupal.ahah.prototype.onsubmit = function (form_values, element, options) { // 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 element = $(options.element); 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 + '()'); + progress_element.css('float', 'left').css('display', 'none'); + element.css('float', 'left').attr('disabled', true).after(progress_element); + progress_element.show(); }; /** * Handler for the form redirection completion. */ -Drupal.ahah.prototype.oncomplete = function (data) { +Drupal.ahah.prototype.oncomplete = function (response, status) { var wrapper = $(this.wrapper); var button = $(this.id); var progress_element = $(this.progress.element); - var new_content = $('
' + data + '
'); + var new_content = $('
' + response.data + '
'); Drupal.freezeHeight(); @@ -89,14 +116,24 @@ Drupal.ahah.prototype.oncomplete = funct // Hide the new content before adding to page. new_content.hide(); - // Add the form and re-attach behavior. + // Add the new content to the page. if (this.method == 'replace') { wrapper.empty().append(new_content); } else { eval('wrapper.' + this.method + '(new_content)'); } - eval('new_content.' + this.showEffect + '()'); + + // 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(); + eval('$(".ahah-new-content", new_content).' + this.showEffect + '("' + this.showSpeed +'")'); + } + else { + eval('new_content.' + this.showEffect + '("' + this.showSpeed +'")'); + } button.css('float', 'none').attr('disabled', false); Drupal.attachBehaviors(new_content); Index: misc/drupal.js =================================================================== RCS file: /cvs/drupal/drupal/misc/drupal.js,v retrieving revision 1.35 diff -u -p -r1.35 drupal.js --- misc/drupal.js 1 Jul 2007 15:37:08 -0000 1.35 +++ misc/drupal.js 8 Jul 2007 20:57:42 -0000 @@ -195,69 +195,6 @@ Drupal.theme = function(func) { }; /** - * Redirects a button's form submission to a hidden iframe and displays the result - * in a given wrapper. The iframe should contain a call to - * window.parent.iframeHandler() after submission. - */ -Drupal.redirectFormButton = function (uri, button, handler) { - // Trap the button - button.onmouseover = button.onfocus = function() { - button.onclick = function() { - // Create target iframe - Drupal.createIframe(); - - // Prepare variables for use in anonymous function. - var button = this; - var action = button.form.action; - var target = button.form.target; - - // Redirect form submission to iframe - this.form.action = uri; - this.form.target = 'redirect-target'; - - handler.onsubmit(); - - // Set iframe handler for later - window.iframeHandler = function () { - var iframe = $('#redirect-target').get(0); - // Restore form submission - button.form.action = action; - button.form.target = target; - - // Get response from iframe body - try { - response = (iframe.contentWindow || iframe.contentDocument || iframe).document.body.innerHTML; - // Firefox 1.0.x hack: Remove (corrupted) control characters - response = response.replace(/[\f\n\r\t]/g, ' '); - if (window.opera) { - // Opera-hack: it returns innerHTML sanitized. - response = response.replace(/"/g, '"'); - } - } - catch (e) { - response = null; - } - - response = Drupal.parseJson(response); - // Check response code - if (response.status == 0) { - handler.onerror(response.data); - return; - } - handler.oncomplete(response.data); - - return true; - }; - - return true; - }; - }; - button.onmouseout = button.onblur = function() { - button.onclick = null; - }; -}; - -/** * Retrieves the absolute position of an element on the screen */ Drupal.absolutePosition = function (el) { @@ -305,41 +242,6 @@ Drupal.parseJson = function (data) { }; /** - * Create an invisible iframe for form submissions. - */ -Drupal.createIframe = function () { - if ($('#redirect-holder').size()) { - return; - } - // Note: some browsers require the literal name/id attributes on the tag, - // some want them set through JS. We do both. - window.iframeHandler = function () {}; - var div = document.createElement('div'); - div.id = 'redirect-holder'; - $(div).html(''); - var iframe = div.firstChild; - $(iframe) - .attr({ - name: 'redirect-target', - id: 'redirect-target' - }) - .css({ - position: 'absolute', - height: '1px', - width: '1px', - visibility: 'hidden' - }); - $('body').append(div); -}; - -/** - * Delete the invisible iframe - */ -Drupal.deleteIframe = function () { - $('#redirect-holder').remove(); -}; - -/** * Freeze the current body height (as minimum height). Used to prevent * unnecessary upwards scrolling when doing DOM manipulations. */ Index: misc/jquery.form.js =================================================================== RCS file: misc/jquery.form.js diff -N misc/jquery.form.js --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ misc/jquery.form.js 8 Jul 2007 20:57:42 -0000 @@ -0,0 +1,804 @@ +/* + * jQuery form plugin + * @requires jQuery v1.1 or later + * + * Examples at: http://malsup.com/jquery/form/ + * Dual licensed under the MIT and GPL licenses: + * http://www.opensource.org/licenses/mit-license.php + * http://www.gnu.org/licenses/gpl.html + * + * Revision: $Id$ + * Version: 1.0 Jul-04-2007 + */ + (function($) { +/** + * ajaxSubmit() provides a mechanism for submitting an HTML form using AJAX. + * + * ajaxSubmit accepts a single argument which can be either a success callback function + * or an options Object. If a function is provided it will be invoked upon successful + * completion of the submit and will be passed the response from the server. + * If an options Object is provided, the following attributes are supported: + * + * target: Identifies the element(s) in the page to be updated with the server response. + * This value may be specified as a jQuery selection string, a jQuery object, + * or a DOM element. + * default value: null + * + * url: URL to which the form data will be submitted. + * default value: value of form's 'action' attribute + * + * type: The method in which the form data should be submitted, 'GET' or 'POST'. + * default value: value of form's 'method' attribute (or 'GET' if none found) + * + * beforeSubmit: Callback method to be invoked before the form is submitted. + * default value: null + * + * success: Callback method to be invoked after the form has been successfully submitted + * and the response has been returned from the server + * default value: null + * + * dataType: Expected dataType of the response. One of: null, 'xml', 'script', or 'json' + * default value: null + * + * semantic: Boolean flag indicating whether data must be submitted in semantic order (slower). + * default value: false + * + * resetForm: Boolean flag indicating whether the form should be reset if the submit is successful + * + * clearForm: Boolean flag indicating whether the form should be cleared if the submit is successful + * + * + * The 'beforeSubmit' callback can be provided as a hook for running pre-submit logic or for + * validating the form data. If the 'beforeSubmit' callback returns false then the form will + * not be submitted. The 'beforeSubmit' callback is invoked with three arguments: the form data + * in array format, the jQuery object, and the options object passed into ajaxSubmit. + * The form data array takes the following form: + * + * [ { name: 'username', value: 'jresig' }, { name: 'password', value: 'secret' } ] + * + * If a 'success' callback method is provided it is invoked after the response has been returned + * from the server. It is passed the responseText or responseXML value (depending on dataType). + * See jQuery.ajax for further details. + * + * + * The dataType option provides a means for specifying how the server response should be handled. + * This maps directly to the jQuery.httpData method. The following values are supported: + * + * 'xml': if dataType == 'xml' the server response is treated as XML and the 'after' + * callback method, if specified, will be passed the responseXML value + * 'json': if dataType == 'json' the server response will be evaluted and passed to + * the 'after' callback, if specified + * 'script': if dataType == 'script' the server response is evaluated in the global context + * + * + * Note that it does not make sense to use both the 'target' and 'dataType' options. If both + * are provided the target will be ignored. + * + * The semantic argument can be used to force form serialization in semantic order. + * This is normally true anyway, unless the form contains input elements of type='image'. + * If your form must be submitted with name/value pairs in semantic order and your form + * contains an input of type='image" then pass true for this arg, otherwise pass false + * (or nothing) to avoid the overhead for this logic. + * + * + * When used on its own, ajaxSubmit() is typically bound to a form's submit event like this: + * + * $("#form-id").submit(function() { + * $(this).ajaxSubmit(options); + * return false; // cancel conventional submit + * }); + * + * When using ajaxForm(), however, this is done for you. + * + * @example + * $('#myForm').ajaxSubmit(function(data) { + * alert('Form submit succeeded! Server returned: ' + data); + * }); + * @desc Submit form and alert server response + * + * + * @example + * var options = { + * target: '#myTargetDiv' + * }; + * $('#myForm').ajaxSubmit(options); + * @desc Submit form and update page element with server response + * + * + * @example + * var options = { + * success: function(responseText) { + * alert(responseText); + * } + * }; + * $('#myForm').ajaxSubmit(options); + * @desc Submit form and alert the server response + * + * + * @example + * var options = { + * beforeSubmit: function(formArray, jqForm) { + * if (formArray.length == 0) { + * alert('Please enter data.'); + * return false; + * } + * } + * }; + * $('#myForm').ajaxSubmit(options); + * @desc Pre-submit validation which aborts the submit operation if form data is empty + * + * + * @example + * var options = { + * url: myJsonUrl.php, + * dataType: 'json', + * success: function(data) { + * // 'data' is an object representing the the evaluated json data + * } + * }; + * $('#myForm').ajaxSubmit(options); + * @desc json data returned and evaluated + * + * + * @example + * var options = { + * url: myXmlUrl.php, + * dataType: 'xml', + * success: function(responseXML) { + * // responseXML is XML document object + * var data = $('myElement', responseXML).text(); + * } + * }; + * $('#myForm').ajaxSubmit(options); + * @desc XML data returned from server + * + * + * @example + * var options = { + * resetForm: true + * }; + * $('#myForm').ajaxSubmit(options); + * @desc submit form and reset it if successful + * + * @example + * $('#myForm).submit(function() { + * $(this).ajaxSubmit(); + * return false; + * }); + * @desc Bind form's submit event to use ajaxSubmit + * + * + * @name ajaxSubmit + * @type jQuery + * @param options object literal containing options which control the form submission process + * @cat Plugins/Form + * @return jQuery + */ +$.fn.ajaxSubmit = function(options) { + if (typeof options == 'function') + options = { success: options }; + + options = $.extend({ + url: this.attr('action') || window.location, + type: this.attr('method') || 'GET' + }, options || {}); + + var a = this.formToArray(options.semantic); + + // give pre-submit callback an opportunity to abort the submit + if (options.beforeSubmit && options.beforeSubmit(a, this, options) === false) return this; + + // fire vetoable 'validate' event + var veto = {}; + $.event.trigger('form.submit.validate', [a, this, options, veto]); + if (veto.veto) + return this; + + var q = $.param(a);//.replace(/%20/g,'+'); + + if (options.type.toUpperCase() == 'GET') { + options.url += (options.url.indexOf('?') >= 0 ? '&' : '?') + q; + options.data = null; // data is null for 'get' + } + else + options.data = q; // data is the query string for 'post' + + var $form = this, callbacks = []; + if (options.resetForm) callbacks.push(function() { $form.resetForm(); }); + if (options.clearForm) callbacks.push(function() { $form.clearForm(); }); + + // perform a load on the target only if dataType is not provided + if (!options.dataType && options.target) { + var oldSuccess = options.success;// || function(){}; + callbacks.push(function(data, status) { + $(options.target).attr("innerHTML", data).evalScripts().each(oldSuccess, [data, status]); + }); + } + else if (options.success) + callbacks.push(options.success); + + options.success = function(data, status) { + for (var i=0, max=callbacks.length; i < max; i++) + callbacks[i](data, status); + }; + + // are there files to upload? + var files = $('input:file', this).fieldValue(); + var found = false; + for (var j=0; j < files.length; j++) + if (files[j]) + found = true; + + if (options.iframe || found) // options.iframe allows user to force iframe mode + fileUpload(); + else + $.ajax(options); + + // fire 'notify' event + $.event.trigger('form.submit.notify', [this, options]); + return this; + + + // private function for handling file uploads (hat tip to YAHOO!) + function fileUpload() { + var form = $form[0]; + var opts = $.extend({}, $.ajaxSettings, options); + + var id = 'jqFormIO' + $.fn.ajaxSubmit.counter++; + var $io = $('