Alternatives to the Average AJAX ActionStatus

Visualforce offers some great shortcuts that makes for faster development and a more consistent user experience. It is these tools to which Salesforce refers when they proudly advertise the short development cycles that programmers experience when working on the Force.com platform. It makes my life as a developer easier, and I really appreciate that. Sometimes, however, you want to add a slightly more advanced feature, one that requires pushing your code off of and beyond the tracks that the platform provides you.

One area that Visualforce has made very simple is Ajax. Adding just one or two Visualforce tags and attributes can produce a responsive page that uses Ajax for updating information. One such tag is the actionStatus tag. Ajax operation will work just fine if you don’t use this tag, but the user won’t know that an Ajax update is, in fact, taking place. Add this tag to the page and reference it from the same place that called the Ajax to tell the user, “Hey, wait a second. We’ve got some data coming from the server, so you should wait for it to arrive before continuing.” Here’s the simplest way to use this tag, taken from the SF documentation for commandButton:

<apex:page controller="exampleCon">
  <apex:form id="theForm">
    <apex:outputText value="{!varOnController}"/>
    <apex:commandButton action="{!update}" rerender="theForm" value="Update" id="theButton"/>
  </apex:form>
</apex:page>

 

Code Review: In this example, the secret sauce that adds the Ajax is the action and rerender attributes on the commandButton tag. Clicking on the button will send an Ajax message back to the server that will get the latest value of varOnController, which is a property of the controller object that we assume is changing with time. When the message returns to the page, the element that the outputText element creates will be replaced with the new value.

Now, let’s imagine that it takes a few seconds for the controller to get this updated value back to us. The user will sit there, staring at the page, wondering if the button-click didn’t work. We should give him some feedback:

<apex:page controller="exampleCon">
  <apex:form id="theForm">
    <apex:outputText value="{!varOnController}"/>
    <apex:actionStatus startText=" (working…)" stopText=" (done)" id="updateStatus"/>
    <apex:commandButton action="{!update}" rerender="theForm" status="updateStatus" value="Update" id="theButton"/>
  </apex:form>
</apex:page>

 

Code Review: This code should be the same as the first one, but this time we’ve added an actionStatus tag, which we attach to the commandButton’s Ajax call by using the status attribute on commandButton. With this simple change, when the Ajax call begins, ( working…) will appear next to the outputText, and when it finishes, it will change to (done). This is a pretty good quick-and-done solution for user feedback.

The Problem: But what if we had a page that was heavy on form inputs, one that requires lots of user interaction? Is there a way to tell the user to wait before filling in more fields until the page updates? Well, I’ve found two possible solutions. Neither is perfect, but they both get the job done. What we want to achieve is to disable all inputs and buttons on the form while the page update is taking place.

1) The first way to accomplish this is to create a drop-down curtain effect, which drops a see-through curtain to capture clicks over the form. Here’s how solution number 1 works:

<apex:pageBlockSection title="Form 1" id="formSection" collapsible="false">
  <div id="loadingCurtain">
  <apex:inputField value="{!myObject.Name}"/>
  <apex:inputField value="{!myObject.Address}"/>
  <apex:inputField value="{!myObject.OtherFields}"/>
  <apex:commandButton action="{!update}" rerender="formSection" onclick="showLoadingDiv();" oncomplete="hideLoadingDiv();" value="Update" id="theButton"/>
</apex:pageBlockSection>

 

Code Review: This is just a pageBlockSection that is contained inside form tags, which aren’t shown. This one has three fields, though it could have more, and a button to post it back to the server. Instead of using the actionStatus tag, we’re going to call our own Javascript functions to manipulate the loading curtain:

$j = jQuery.noConflict();
//This escapes SF-created IDs
function esc(myid) {
  return '#' + myid.replace(/(:|\.)/g,'\\\\$1');
}
function showLoadingDiv() {
  var divToScreenEsc = esc("{!$Component.hardCostSection}");
  var newHeight = $j(divToScreenEsc + " .pbSubsection").css("height");//Just shade the body, not the header
  $j("#loadingDiv").css("background-color", "black").css("opacity", 0.35).css("height", newHeight).css("width", "80%");
}
function hideLoadingDiv() {
  $j("#loadingDiv").css("background-color", "black").css("opacity", "1").css("height", "0px").css("width", "80%");
}

 

I’m using jQuery here. I’ve had bad experiences trying to use vanilla Javascript, and I’ve vowed to never use it again. jQuery gives consistent results across browser DOMs and across Javascript implementations itself. Note the esc function. This is necessary to escape the non-standard character in SF-created IDs for use in jQuery selectors, and was a solution created by Wes Nolte. See his blog post on the solution here. Beyond that, we’re just selecting the loadingDiv and setting some styles, pretty simple. To get a bit more fancy, use CSS transitions by adding these styles to the loadingCurtain div: -webkit-transition: all 0.10s ease-out; -moz-transition: all 0.10s ease-out;


2) The second solution is to avoid adding another element to the DOM, and just using Javascript to disable all selectable fields and buttons in the form:

<apex:pageBlockSection title="Form 1" id="formSection" collapsible="false">
  <apex:inputField value="{!myObject.Name}"/>
  <apex:inputField value="{!myObject.Address}"/>
  <apex:inputField value="{!myObject.OtherFields}"/>
  <apex:commandButton action="{!update}" rerender="formSection" onclick="showLoadingDiv2();" oncomplete="hideLoadingDiv2();" value="Update" id="theButton"/>
</apex:pageBlockSection>

 

Code Review: This is the same VF snippet as above, minus the curtain div. This time, we’ll use Javascript to set the styles of the form elements themselves.

function showLoadingDiv2() {
  var divToScreenEsc = esc("{!$Component.formSection}");
  $j(divToScreenEsc).css("opacity", "0.35");
  $j(divToScreenEsc + " input, " + divToScreenEsc + " select").attr("disabled", "true");
}
function hideLoadingDiv2() {
  var divToScreenEsc = esc("{!$Component.formSection}");
  $j(divToScreenEsc).css("opacity", "1");
  $j(divToScreenEsc + " input, " + divToScreenEsc + " select").attr("disabled", "false");
}

 

This will disable all input and select elements in the specified form section as well as turn the entire section slightly transparent by setting the opacity to 35%.

 
That’s it, really. Two simple solutions that I came up with to give a better user experience. Improve up it! I’m not a pro at HTML and Javascript, and maybe you can help make it better – leave a comment! I made a screencast, located here, to demo the result of using this technique – I hope you find it useful.

Before concluding this post, I want to mention the more elegant version of this solution that you should investigate:
Keep in mind that the actionStatus tag has both onstart and onstop attributes, from which you can call the necessary Javascript functions from there. The advantage to doing it this way is that the functionality is again tied to the actionStatus tag . This adds a layer of abstraction between the functionality and its visual status indicator. You would call the Javascript functions from the actionStatus tag instead of the Ajax initiating tags, such as a commandButton, keeping your code DRY and happy.

This entry was posted in Technology and tagged , , , , . Bookmark the permalink.

3 Responses to Alternatives to the Average AJAX ActionStatus

  1. Wes says:

    Great post, this is definitely the way forward. Have you thought of making it into a plugin?

    Also I’ve recently found a better way to handle VF Ids. If you click through to the post that you’ve listed the first line links to the improved method.

  2. Avatar of Alex Berg Alex Berg says:

    Wes -
    Thanks for the comment! Yeah, I’ve thought about it, but found a few excuses to not make it into a VF component, if that’s what you mean.
    1) The code is kinda dependent on the IDs on the VF elements. (though I suppose VF components can take parameters… We should look into this. But is it possible to reference JS that is inside a VF component?)
    2) The code snippets are pretty small. It might be easier to manage by copy-paste.

    Let’s do some prototyping here:

    <c:ajaxProtect elementId=”esc(“{!$Component.formSection}”)”/>
    <apex:pageSection id=”formSection”>
    <apex:commandButton rerender=”formSection” onstart=”showLoadingDiv2();” oncomplete=”hideLoadingDiv2();”/>
    <!– Problem: How does this code know the JS function name to use? Would have to be unmanaged package for sure, but calling this JS function that was defined inside a component, is it technically possible? We could use a naming convention by changing the JS function names to reference the component name: ajaxProtectStart() and ajaxProtectComplete() –>
    </apex:pageSection>

    //In file “ajaxProtect.component”
    <apex:component access=”global”>
    <apex:attribute name=”elementId” description=”The ID of the component to protect.”
    type=”String” required=”true”/>

    function showLoadingDiv2() {
    $j(elementId).css(“opacity”, “0.35″);
    //Or use Wes’ new method of using SF IDs in jQuery (following statement is untested!):
    //$(‘[id$=' + elementId + ']‘).css(“opacity”, “0.35″);
    $j(elementId + ” input, ” + elementId + ” select”).attr(“disabled”, “true”);
    }
    function hideLoadingDiv2() {
    $j(elementId).css(“opacity”, “1″);
    $j(elementId + ” input, ” + elementId + ” select”).attr(“disabled”, “false”);
    }
    </apex:component>

    Limitation: It seems to me that this could only be used for one form – trying to use this twice would consequently introduce these two JS functions twice. I would guess that the first would not take effect, but rather, the second definition of the function would be mapped to that function name.

    What do you think?

  3. Rosalinda Juza says:

    CSS is designed primarily to enable the separation of document content (written in HTML or a similar markup language) from document presentation, including elements such as the layout, colors, and fonts.This separation can improve content accessibility, provide more flexibility and control in the specification of presentation characteristics, enable multiple pages to share formatting, and reduce complexity and repetition in the structural content (such as by allowing for tableless web design). -..

    See the most up to date posting at our new blog page
    <http://www.caramoan.ph

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>