Index: flag.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/flag/Attic/flag.module,v
retrieving revision 1.11.2.43
diff -u -F^[^a-z]*function -r1.11.2.43 flag.module
--- flag.module	23 Sep 2008 13:03:58 -0000	1.11.2.43
+++ flag.module	26 Sep 2008 14:13:54 -0000
@@ -662,7 +662,12 @@ function flag_page($action, $flag_name, 
 
   if ($js) {
     drupal_set_header('Content-Type: text/javascript; charset=utf-8');
-    print drupal_to_js(array('status' => TRUE));
+    $flag = flag_get_flag($flag_name);
+    print drupal_to_js(array(
+      'status' => TRUE,
+      'newFlagLink' => $flag->theme('flag', $content_id, TRUE),
+      'newUnflagLink' => $flag->theme('unflag', $content_id, TRUE),
+    ));
     exit;
   }
   else {
Index: theme/flag.js
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/flag/theme/Attic/flag.js,v
retrieving revision 1.1.2.3
diff -u -F^[^a-z]*function -U999 -r1.1.2.3 flag.js
--- theme/flag.js	19 Aug 2008 08:50:26 -0000	1.1.2.3
+++ theme/flag.js	26 Sep 2008 14:14:01 -0000
@@ -1,119 +1,192 @@
 // $Id: flag.js,v 1.1.2.3 2008/08/19 08:50:26 mooffie Exp $
+
+/**
+ * Terminology:
+ *
+ *   "Link" means "Everything which is in flag.tpl.php" --and this may contain
+ *   much more than the <A> element. On the other hand, when we speak
+ *   specifically of the <A> element, we say "element" or "the <A> element".
+ */
+
 Drupal.behaviors.flag = function() {
   // Note, extra indentation left here to maintain ease of patching for D5 version.
 
     /**
      * Flips a link. 'Flag this!' links turn into 'Unflag this!' and
      * vice versa.
+     *
+     * @param element
+     *   The <A> element.
+     * @return
+     *   The new <A> elemenet.
      */
     function flipLink(element, settings) {
-      // If this is a 'flag this' link...
-      if ($(element).is('.flag-action')) {
-        // ...then turn it into an 'unflag this' link;
-        var newHtml = settings.unflag;
-      }
-      else {
-        // else, turn it into a 'flag this' link.
-        var newHtml = settings.flag;
+      setFlagged(element, !isFlagged(element));
+      return refreshLink(element, settings);
+    }
+
+    /**
+     * Refreshes a link display.
+     *
+     * @param element
+     *   The <A> element.
+     * @return
+     *   The new <A> elemenet.
+     */
+    function refreshLink(element, settings) {
+      if (typeof settings.flag == 'undefined') {
+        // New HTML is not yet available; the 'settings' parcel is empty. We
+        // have to wait for a server response.
+        return element;
       }
-      updateLink(element, newHtml, settings);
+      var newHtml = isFlagged(element) ? settings.unflag : settings.flag;
+      return updateLink(element, newHtml, settings);
+    }
+
+    /**
+     * Returns TRUE if the item is flagged. That is, if this <A> element is of
+     * the 'Unflag this!' kind.
+     */
+    function isFlagged(element) {
+      if (typeof element.isFlagged != 'undefined') {
+        return element.isFlagged;
+      }
+      return $(element).is('.unflag-action');
+    }
+
+    /**
+     * Sets the status of the <A> element. See isFlagged() above.
+     */
+    function setFlagged(element, value) {
+      element.isFlagged = value;
     }
 
     /**
      * Helper function. Updates a link's HTML with a new one.
+     *
+     * @param element
+     *   The <A> element.
+     * @return
+     *   The new <A> elemenet.
      */
     function updateLink(element, newHtml, settings) {
       var $newLink = $(newHtml);
 
       // Initially hide the message so we can fade it in.
       $('.flag-message', $newLink).css('display', 'none');
 
-      // Reattach the behavior to the new link.
-      if ($('a', $newLink).size() > 0) {
-        $('a', $newLink).bind('click', function() { return flagClick(this, settings) });
-      }
-      else {
-        $newLink.bind('click', function() { return flagClick(this, settings) });
-      }
+      // Reattach the behavior to the new <A> element. This element
+      // is either whithin the wrapper or it is the outer element itself.
+      var $nucleus = $newLink.is('a') ? $newLink : $newLink.find('a.flag');
+      $nucleus.addClass('flag-processed').click(function() { return flagClick(this, settings) });
 
       // Find the wrapper of the old link.
       var $wrapper = $(element).parents('.flag-wrapper:first');
       if ($wrapper.length == 0) {
         // If no ancestor wrapper was found, or if the 'flag-wrapper' class is
         // attached to the <a> element itself, then take the element itself.
         $wrapper = $(element);
       }
       // Replace the old link with the new one.
       $wrapper.after($newLink).remove();
 
       $('.flag-message', $newLink).fadeIn();
+
+      // @todo: The Drupl 6 version should do `Drupal.attachBehaviors($newLink)` here.
+
+      return $nucleus.get(0);
     }
     
-    // Click function for each Flag link.
+    /**
+     * A click handler that is attached to all <A class="flag"> elements.
+     */
     function flagClick(element, settings) {
       // Hide any other active messages.
       $('span.flag-message:visible').fadeOut();
 
+      // While waiting for a server response, the wrapper will have a
+      // 'flag-waiting' class. Themers are thus able to style the link
+      // differently, e.g., by displaying a throbber.
+      $(element).parents('.flag-wrapper').addClass('flag-waiting');
+      
       // Send POST request
       $.ajax({
         type: 'POST',
         url: element.href,
         data: { js: true },
         dataType: 'json',
         success: function (data) {
           // Display errors
           if (!data.status) {
             // Change link back
             flipLink(element, settings);
             return;
           }
+          else {
+	    // Success
+	    settings.flag = data.newFlagLink;
+	    settings.unflag = data.newUnflagLink;
+	    refreshLink(element, settings);
+          }
         },
         error: function (xmlhttp) {
           alert('An HTTP error '+ xmlhttp.status +' occurred.\n'+ element.href);
           // Change link back
           flipLink(element, settings);
         }
       });
       // Swap out the links.
-      flipLink(element, settings);
+      //
+      // Note that we save the new link back in the 'element' closure variable,
+      // because the above Ajax handlers ('success' and 'error') use it.
+      element = flipLink(element, settings);
       return false;
     }
 
     /**
      * Like alert(), but displays only once, to keep user from losing sanity.
      */
     var warn = function(message) {
       if (!Drupal.settings.flag.alertShown) {
         alert('Flag module: ' + message);
         Drupal.settings.flag.alertShown = true;
       }
     }
 
     /**
      * Returns the settings for a certain link element.
      */
     function getLinkSettings(element) {
       // The link URL is of the form ?q=flag/unflag/bookmarks/23,
       // so let's parse it to extract the flag name and the content ID.
       var matches = element.href.match(/flag\/(un)?flag\/(\w+)\/(\d+)/);
       if (!matches) {
         warn("Error: Invalid flag URL '" + element.href + "'");
       }
       var flagName  = matches[2];
       var contentId = matches[3];
       var slot = 'cid_' + contentId;
 
+      if (!Drupal.settings.flag) {
+        Drupal.settings.flag = [];
+      }
+      if (!Drupal.settings.flag.flags) {
+        Drupal.settings.flag.flags = [];
+      }
+      if (!Drupal.settings.flag.flags[flagName]) {
+        Drupal.settings.flag.flags[flagName] = [];
+      }
       if (!Drupal.settings.flag.flags[flagName][slot]) {
         // Slot does not exist. Create.
         Drupal.settings.flag.flags[flagName][slot] = {};
       }
       // Return a reference to the settings slot.
       return Drupal.settings.flag.flags[flagName][slot];
     }
 
     // On load, bind the click behavior for all links on the page.
-    $('a.flag').click(function() {
+    $('a.flag:not(.flag-processed)').addClass('flag-processed').click(function() {
       return flagClick(this, getLinkSettings(this));
     });
   // Intentional extra indention.
 }
