Using a Loading Overlay with Visualforce's ActionStatus tag for Form Refreshes

Wednesday, July 6, 2011

I'm not sure how other Visualforce developers uses the actionStatus tag, but I've discovered a different way of using it that gives visual feedback to the user that the part of the page he is working with is under an update operation.

Until now, I've used the actionStatus tag like this, relying on the start and stop facets of the element to display simple "Loading..." text:

<apex:actionStatus id="entryStatus" >
   <apex:facet name="start">
      <apex:outputText style="font-weight:bold;font-size:16px;" value="Loading..."/>
   </apex:facet>
</apex:actionStatus>

The Loading text appears, but the fields are still editable, which shouldn't be true.


This is fine for a lot of cases, but when the form has multiple input fields, I don't want the user to think that they can change the field values while they wait for an AJAX update, because their updates will be clobbered. Rather, it would be intuitive to have a slightly transparent screen, like a lightbox, appear over the form fields to prevent changes and provide a visual cue that an update is occurring on the section that the user is working with. After trying a few different methods, I refactored the solution to use the actionStatus tag, which has onstart and onstop attributes. These attributes can be used to call a Javascript function that will drop a screen down at a higher z-index/precedence than the fields, capturing subsequent clicks.

<apex:actionStatus id="entryStatus" onstart="showLoadingDiv();" onstop="hideLoadingDiv();">
   <apex:facet name="start">
      <apex:outputText style="font-weight:bold;font-size:16px;" value="Loading..."/>
   </apex:facet>
</apex:actionStatus>

<div id="loadingDiv"></div> <-- The screen element that will cover the form. Javascript will change the height of this to drop it down. -->
   <apex:pageblock id="hardCosts" title="Line Items - Hard Cost Estimate"> <-- The form element to screen. -->
      <script>
      //This Javascript needs to be inside the element to hide to get its correct Id value {!$Component.task}.
      function showLoadingDiv() {
         //Find the sizes of the div to screen.
 var blockToLoad = document.getElementById('{!$Component.hardCosts}');
 var loadWidth = window.getComputedStyle(blockToLoad,"").getPropertyValue("width");
 var loadHeight = window.getComputedStyle(blockToLoad,"").getPropertyValue("height");
 //Set the loadingDiv to screen the element at the correct size.
 var loadingDiv = document.getElementById('loadingDiv');
 loadingDiv.setAttribute('style','background-color:black; opacity:0.35; height:' + parseFloat(loadHeight) + 'px; width:' + loadWidth + ';');
      }
      function hideLoadingDiv() {
 //Find the loadingDiv to hide.
 var loadingDiv = document.getElementById('loadingDiv');
 var blockToLoad = document.getElementById('{!$Component.hardCosts}');
 var loadWidth = window.getComputedStyle(blockToLoad,"").getPropertyValue("width");
 //Set its height to 0 to hide it.
 loadingDiv.setAttribute('style','height:0px; width: ' + loadWidth + '; background-color:black; opacity:0.35;');
      }
      
      </script>

And some styles to set the initial size of the loadingDiv element and add some attractive transitions.

#loadingDiv {
   height:0px;
   width:100%;
   position:absolute;
   -webkit-transition: all 0.10s ease-out;
   -moz-transition: all 0.10s ease-out;
}

It looks better with animations, but here's the result of a rough draft of the idea:
An overlay shows that it is loading.

This is just a rough draft, so there are some features that should be added before deploying this into production. For example, adding "Loading..." text next to an animated gif on the overlay would make it even more clear that the user must wait before accessing the data again, but this is a good start.

Can you make it better? Message me on Twitter (@alex_berg) so we can improve the idea.

2 comments:

  1. I know this is old, but just found it and used the idea. However, I just put the div inside of the actionstatus start facet and did not need any javascript.

    ReplyDelete
    Replies
    1. Nice! Just this week I was again looking at this technique. I know this method is crude, so your suggestion is well-timed. Thanks for the tip, Paul.

      Delete