Technology

Drupal 7 Global ajax progress throbber using jQuery Ajax Events

In my opinion Drupal 7 AJAX user experience has one major flaw out-of-the box. If a user does a form action while an AJAX request is still processing, Drupal will give a very user-unfriendly pop-up message, saying something in the lines of:

An AJAX HTTP request terminated abnormally.
Debugging information follows.
Path: /system/ajax
StatusText:
ResponseText:
ReadyState: 4

This is because Drupal uses JavaScript alerts (yikes!) to track AJAX callback errors. There is a plan on redesigning the AJAX error system (maybe for Drupal 9?), but I would like to prevent these horrible errors shown for the end user NOW.

I came up with a simple solution to the problem by using jQuery AJAX Events. Basically we will create an overlay to prevent any client side actions to the site for the duration of an AJAX callback, by using two AJAX events: AjaxSend and AjaxComplete. More information can be found commented in the code.

You can implement this JS Drupal behavior snippet in a js file attached to a custom module or your theme. I also included some CSS code for basic theming (no throbber image included, but there is commented out CSS code for adding one).

Anyways, here is the actual JS snippet, with comments:

PS. This is a fairly simple implementation, and doesn’t take into account edge cases. The JavaScript code provides some examples on how to handle some of them.

/** This behavior will create an overlay throbber for the
 * duration of ajax requests
 * This is an simplified example snippet of project code,
 * and will not take into account all edge cases
 */
Drupal.behaviors.exoveGlobalThrobber = {
  attach:function(context) {
    // Overlay global throbber HTML,
    // this is what we actually show for the end user
    var div = '<div style="display: none;" id="global-throbber">
      . <div class="ajax-progress-throbber"><div class="throbber"></div>
      . </div></div>';

    // jQuery Ajax Events need to be triggered on the document element
    // This is triggered on the beginning of an jQuery AJAX request
    $(document).ajaxSend(function( event, request, settings ) {

      // Add throbber on system or views ajax calls
      // Most core and contrib module callbacks will use these urls.
      if (settings.url == "/system/ajax" || settings.url == "/views/ajax") {
        // If not yet added, if multiple AJAX requests are fired
        if ($('#global-throbber').length == 0) {
          // Add the overlay div to the beginning of the page,
          // and fade it in halfway
          $('body').append(div);
          $('#global-throbber').fadeTo("fast" , 0.5);
        }
      }

      // EXAMPLES: =============
      // Additional logic can be added here, and by returning false,
      // input can be left enabled on some edge cases.
      // A couple of examples below.

      /*
      // Example: Do not use throbber, if specific input element value
      // (change YOURVALUE to actual for element value)
      if (typeof settings.extraData !== 'undefined') {
        if (typeof settings.extraData['_triggering_element_value'] !== 'undefined') {
          if (settings.extraData['_triggering_element_value'] == YOURVALUE) {
            return;
          }
        }
      }
      */
      /*
      // Example: Do not use throbber, if external bootstrapped file.
      // callback from a custom file
      if (settings.url == '/CUSTOMFILE.php') {
        return;
      }
      */
      // END EXAMPLES: =============

      // Disable visible form elements, we can leave hidden
      // elements alone, because the user cannot click them
      // Also, don't touch the already disabled elements,
      // so we can know what to re-enable on the AJAX completion
      $("input[disabled!='disabled']").each(function() {
        if ($(this).css("display") !== "none" && $(this).attr("type") == 'submit') {
          // Disable the elements, and add a class (global-throbber-disabled)
          // The class is for enabling the elements on completion.
          // Also add a disabled class for theming
          $(this).attr("disabled", true).addClass("global-throbber-disabled").addClass('disabled');
        }
      });
    });

    // Again, needs to be triggered on the document element
    // This is triggered on the completion of an jQuery AJAX request
    $(document).ajaxComplete(function( event, request, settings ) {
      // Enable all disabled elements, by using the class
      // that we added on the ajaxSend Event.
      // Then we can remove the class that we added for this procedure
      // We added a disabled class for theming, also remove that.
      $("input.global-throbber-disabled").removeAttr("disabled","disabled").removeClass("global-throbber-disabled").removeClass('disabled');
      // Remove throbber container added to the beginning of the body
      $('#global-throbber').remove();
    });
  }
};

CSS for basic theming of the throbber

UX tip: I would suggest on using a sufficiently big throbber image, because the throbber container will overlay the whole page, and a big throbber (at least bigger than Drupal’s default ajax throbber) is a good way of letting the user know that the whole site is currently processing something.

#global-throbber {
    display :block;
    width: 100%;
    height: 100%;
    background: black;
    opacity: 0.6;
    filter: alpha(opacity=60);
    position: fixed;
    top: 0;
    left: 0;
    z-index: 10000;
}
#global-throbber .ajax-progress .message,
#global-throbber .ajax-progress-throbber .message {
    display :block;
    color: black;
    font-size: 20px;
    padding: 30px;
}
#global-throbber .ajax-progress .throbber,
#global-throbber .ajax-progress-throbber .throbber {
    display :block;
    float: none;
    margin: 250px auto 0 auto;
    width: 128px;
    height: 128px;
    // Uncomment to add a throbber image of your choice
    // background: url("../images/progress_bar.gif") no-repeat center center;
}
#global-throbber .ajax-progress,
#global-throbber .ajax-progress-throbber {
    vertical-align: middle;
    text-align: center;
}

Further reading:

Categories: Technology

Latest blogs