Index: misc/ajax.js
===================================================================
RCS file: /cvs/drupal/drupal/misc/ajax.js,v
retrieving revision 1.33
diff -u -p -r1.33 ajax.js
--- misc/ajax.js	17 Dec 2010 01:03:58 -0000	1.33
+++ misc/ajax.js	19 Dec 2010 18:45:50 -0000
@@ -20,41 +20,44 @@ Drupal.ajax = Drupal.ajax || {};
  */
 Drupal.behaviors.AJAX = {
   attach: function (context, settings) {
-    // Load all AJAX behaviors specified in the settings.
+    // Bind AJAX behaviors to all elements specified in settings.
     for (var base in settings.ajax) {
-      if (!$('#' + base + '.ajax-processed').length) {
-        var element_settings = settings.ajax[base];
+      var element_settings = settings.ajax[base];
 
-        if (typeof element_settings.selector == 'undefined') {
-          element_settings.selector = '#' + base;
-        }
-        $(element_settings.selector).each(function () {
-          element_settings.element = this;
-          Drupal.ajax[base] = new Drupal.ajax(base, this, element_settings);
-        });
-
-        $('#' + base).addClass('ajax-processed');
+      // If no selector has been defined, default to using base as HTML ID.
+      if (typeof element_settings.selector == 'undefined') {
+        element_settings.selector = '#' + base;
       }
+      // Use jQuery.once() for consistency.
+      var $elements = $(element_settings.selector, context);
+      $elements.once('ajax', function () {
+        Drupal.ajax[base] = new Drupal.ajax(base, $elements, element_settings);
+        // Only process all matched $elements once; returning false cancels the
+        // $.each() loop.
+        return false;
+      });
     }
 
     // Bind AJAX behaviors to all items showing the class.
-    $('.use-ajax:not(.ajax-processed)').addClass('ajax-processed').each(function () {
+    $('.use-ajax', context).once('ajax', function () {
+      var $elements = $(this);
       var element_settings = {};
       // Clicked links look better with the throbber than the progress bar.
-      element_settings.progress = { 'type': 'throbber' };
+      element_settings.progress = { type: 'throbber' };
 
       // 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');
+      if ($elements.attr('href')) {
+        element_settings.url = $elements.attr('href');
         element_settings.event = 'click';
       }
-      var base = $(this).attr('id');
-      Drupal.ajax[base] = new Drupal.ajax(base, this, element_settings);
+      var base = $elements.attr('id');
+      Drupal.ajax[base] = new Drupal.ajax(base, $elements, 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 () {
+    $('.use-ajax-submit', context).once('ajax', function () {
+      var $elements = $(this);
       var element_settings = {};
 
       // AJAX submits specified in this manner automatically submit to the
@@ -65,11 +68,12 @@ Drupal.behaviors.AJAX = {
       element_settings.setClick = true;
       // Form buttons use the 'click' event rather than mousedown.
       element_settings.event = 'click';
-      // Clicked form buttons look better with the throbber than the progress bar.
-      element_settings.progress = { 'type': 'throbber' };
+      // Clicked form buttons look better with the throbber than the progress
+      // bar.
+      element_settings.progress = { type: 'throbber' };
 
-      var base = $(this).attr('id');
-      Drupal.ajax[base] = new Drupal.ajax(base, this, element_settings);
+      var base = $elements.attr('id');
+      Drupal.ajax[base] = new Drupal.ajax(base, $elements, element_settings);
     });
   }
 };
@@ -96,7 +100,12 @@ Drupal.behaviors.AJAX = {
  *    };
  * @endcode
  */
-Drupal.ajax = function (base, element, element_settings) {
+Drupal.ajax = function (base, $elements, element_settings) {
+  // Backwards-compatibility: Ensure that $elements is a jQuery object.
+  if (typeof $elements.nodeType != 'undefined') {
+    $elements = $($elements);
+  }
+
   var defaults = {
     url: 'system/ajax',
     event: 'mousedown',
@@ -116,7 +125,8 @@ Drupal.ajax = function (base, element, e
 
   $.extend(this, defaults, element_settings);
 
-  this.element = element;
+  this.$elements = $elements;
+  this.element = $elements.get(0);
   this.element_settings = element_settings;
 
   // Replacing 'nojs' with 'ajax' in the URL allows for an easy method to let
@@ -174,8 +184,9 @@ Drupal.ajax = function (base, element, e
     type: 'POST'
   };
 
-  // Bind the ajaxSubmit function to the element event.
-  $(ajax.element).bind(element_settings.event, function (event) {
+  // Bind the ajaxSubmit function to the specified event.
+  ajax.$elements.bind(element_settings.event, function (event) {
+    ajax.element = this;
     return ajax.eventResponse(this, event);
   });
 
@@ -183,7 +194,8 @@ Drupal.ajax = function (base, element, e
   // can be triggered through keyboard input as well as e.g. a mousedown
   // action.
   if (element_settings.keypress) {
-    $(element_settings.element).keypress(function (event) {
+    ajax.$elements.keypress(function (event) {
+      ajax.element = this;
       return ajax.keypressResponse(this, event);
     });
   }
@@ -197,12 +209,11 @@ Drupal.ajax = function (base, element, e
  * if it is, trigger it for us and prevent other keypresses from triggering.
  */
 Drupal.ajax.prototype.keypressResponse = function (element, event) {
-  // Create a synonym for this to reduce code confusion.
   var ajax = this;
 
   // Detect enter key and space bar.
   if (event.which == 13 || event.which == 32) {
-    $(ajax.element_settings.element).trigger(ajax.element_settings.event);
+    $(ajax.element).trigger(ajax.element_settings.event);
     return false;
   }
 };
@@ -216,7 +227,6 @@ Drupal.ajax.prototype.keypressResponse =
  * ajax object.
  */
 Drupal.ajax.prototype.eventResponse = function (element, event) {
-  // Create a synonym for this to reduce code confusion.
   var ajax = this;
 
   // Do not perform another ajax command if one is already in progress.
@@ -312,28 +322,30 @@ Drupal.ajax.prototype.beforeSubmit = fun
  * Prepare the AJAX request before it is sent.
  */
 Drupal.ajax.prototype.beforeSend = function (xmlhttprequest, options) {
+  var ajax = this;
+
   // Disable the element that received the change.
-  $(this.element).addClass('progress-disabled').attr('disabled', true);
+  $(ajax.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">&nbsp;</div></div>');
-    if (this.progress.message) {
-      $('.throbber', this.progress.element).after('<div class="message">' + this.progress.message + '</div>');
+  if (ajax.progress.type == 'bar') {
+    var progressBar = new Drupal.progressBar('ajax-progress-' + ajax.element.id, eval(ajax.progress.update_callback), ajax.progress.method, eval(ajax.progress.error_callback));
+    if (ajax.progress.message) {
+      progressBar.setProgress(-1, ajax.progress.message);
+    }
+    if (ajax.progress.url) {
+      progressBar.startMonitoring(ajax.progress.url, ajax.progress.interval || 1500);
+    }
+    ajax.progress.element = $(progressBar.element).addClass('ajax-progress ajax-progress-bar');
+    ajax.progress.object = progressBar;
+    $(ajax.element).after(ajax.progress.element);
+  }
+  else if (ajax.progress.type == 'throbber') {
+    ajax.progress.element = $('<div class="ajax-progress ajax-progress-throbber"><div class="throbber">&nbsp;</div></div>');
+    if (ajax.progress.message) {
+      $('.throbber', ajax.progress.element).after('<div class="message">' + ajax.progress.message + '</div>');
     }
-    $(this.element).after(this.progress.element);
+    $(ajax.element).after(ajax.progress.element);
   }
 };
 
@@ -341,20 +353,22 @@ Drupal.ajax.prototype.beforeSend = funct
  * Handler for the form redirection completion.
  */
 Drupal.ajax.prototype.success = function (response, status) {
+  var ajax = this;
+
   // Remove the progress element.
-  if (this.progress.element) {
-    $(this.progress.element).remove();
+  if (ajax.progress.element) {
+    $(ajax.progress.element).remove();
   }
-  if (this.progress.object) {
-    this.progress.object.stopMonitoring();
+  if (ajax.progress.object) {
+    ajax.progress.object.stopMonitoring();
   }
-  $(this.element).removeClass('progress-disabled').removeAttr('disabled');
+  $(ajax.element).removeClass('progress-disabled').removeAttr('disabled');
 
   Drupal.freezeHeight();
 
   for (var i in response) {
-    if (response[i]['command'] && this.commands[response[i]['command']]) {
-      this.commands[response[i]['command']](this, response[i], status);
+    if (response[i]['command'] && ajax.commands[response[i]['command']]) {
+      ajax.commands[response[i]['command']](ajax, response[i], status);
     }
   }
 
@@ -362,24 +376,26 @@ Drupal.ajax.prototype.success = function
   // attachBehaviors() called on the new content from processing the response
   // commands is not sufficient, because behaviors from the entire form need
   // to be reattached.
-  if (this.form) {
-    var settings = this.settings || Drupal.settings;
-    Drupal.attachBehaviors(this.form, settings);
+  if (ajax.form) {
+    var settings = ajax.settings || Drupal.settings;
+    Drupal.attachBehaviors(ajax.form, settings);
   }
 
   Drupal.unfreezeHeight();
 
   // Remove any response-specific settings so they don't get used on the next
   // call by mistake.
-  this.settings = null;
+  ajax.settings = null;
 };
 
 /**
  * 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 ajax = this;
+
+  var type = response.effect || ajax.effect;
+  var speed = response.speed || ajax.speed;
 
   var effect = {};
   if (type == 'none') {
@@ -405,22 +421,24 @@ Drupal.ajax.prototype.getEffect = functi
  * Handler for the form redirection error.
  */
 Drupal.ajax.prototype.error = function (response, uri) {
+  var ajax = this;
+
   alert(Drupal.ajaxError(response, uri));
   // Remove the progress element.
-  if (this.progress.element) {
-    $(this.progress.element).remove();
+  if (ajax.progress.element) {
+    $(ajax.progress.element).remove();
   }
-  if (this.progress.object) {
-    this.progress.object.stopMonitoring();
+  if (ajax.progress.object) {
+    ajax.progress.object.stopMonitoring();
   }
   // Undo hide.
-  $(this.wrapper).show();
+  $(ajax.wrapper).show();
   // Re-enable the element.
-  $(this.element).removeClass('progress-disabled').removeAttr('disabled');
+  $(ajax.element).removeClass('progress-disabled').removeAttr('disabled');
   // Reattach behaviors, if they were detached in beforeSerialize().
-  if (this.form) {
-    var settings = response.settings || this.settings || Drupal.settings;
-    Drupal.attachBehaviors(this.form, settings);
+  if (ajax.form) {
+    var settings = response.settings || ajax.settings || Drupal.settings;
+    Drupal.attachBehaviors(ajax.form, settings);
   }
 };
 
