Implementing a Web Application Preloading Overlay

October 6th, 2008 - by phiggins

A common issue encountered when developing web applications with the Dojo Toolkit is a startup lag caused by the dynamic loading of modules and resources, further aggravated by a flash of unstyled content before the template system kicks in and sets up your widgets. This is especially true when using numerous Dijit components, and even more apparent when not using a custom build (such as loading your files from the Google or the AOL CDN). While it is typically recommended to use a custom build, it isn’t always practical, or even necessary. Perceived speed is speed, and we should do something about it.

Dojo is a very flexible toolkit, and can bend and otherwise be manipulated to handle any use case. Personally, I am a fan of the progressive use case: where JavaScript is used to enhance an otherwise perfectly working collection of clean markup and good styles. Sometimes though, especially in the case of intranet applications or “admin panel” type web interfaces, you will find yourself using many layout widgets and form components. Delivering content quickly in this case is essential, and hiding any ugliness resulting from template substitution is a vital aspect to delivering the best possible user experience. Today, I’m going to go over a common technique to provide at least the perception of perfectly designed full-page layout applications.

We’ll start with a sample application, loading Dojo and several Dijit components from the AOL CDN tree:

<html>
<head>

        <title>Beefy Layout Page</title>

        <!– every Dijit component needs a theme –>
        <link rel="stylesheet"
                href="http://o.aolcdn.com/dojo/1.2/dijit/themes/soria/soria.css">

        <style type="text/css">
                #preloader,
                body, html {
                        width:100%; height:100%; margin:0; padding:0;
                }
                #preloader {
                        background-color:#fff;
                        position:absolute;
                }
                #masterBc {
                        width:100%; height:100%;
                }
                #rightSide,
                #leftSide {
                        width:275px;
                }
                #topRight { background:#ededed; }
                #bottomCorner { height:175px; }
                .lorem { padding:7px; }
        </style>

        <!– load Dojo, and all the required modules –>
        <script src="http://o.aolcdn.com/dojo/1.2/dojo/dojo.xd.js"></script>
        <script type="text/javascript">
                dojo.require("dijit.layout.BorderContainer");
                dojo.require("dijit.layout.AccordionContainer");
                dojo.require("dijit.layout.ContentPane");
                dojo.require("dijit.layout.TabContainer");
                dojo.require("dojo.parser");
                dojo.addOnLoad(function(){
                        // instantiate all page widgets:
                        // could have set djConfig.parseOnLoad = true
                        dojo.parser.parse();
                });
        </script>

</head>
<body class="soria">
        <div id="preloader"></div>
        <div dojoType="dijit.layout.BorderContainer" id="masterBc">
                <div dojoType="dijit.layout.AccordionContainer" region="left"
                        id="leftSide">

                        <div dojoType="dijit.layout.AccordionPane" title="Top">
                          <p class="lorem">Lorem ipsum dolor sit amet, consectetuer
                            adipiscing elit. Nam facilisis enim. Pellentesque
                            in elit et lacus euismod dignissim. Aliquam
                            dolor pede, convallis eget, dictum a, blandit
                            ac, urna. Pellentesque sed nunc ut justo
                            volutpat egestas. Class aptent taciti sociosqu
                            ad litora torquent per conubia nostra, per
                            inceptos hymenaeos. In erat. Suspendisse
                            potenti. Fusce faucibus nibh sed nisi. Phasellus
                            faucibus, dui a cursus dapibus, mauris nulla
                            euismod velit, a lobortis turpis arcu vel dui.
                            Pellentesque fermentum ultrices pede. Donec
                            auctor lectus eu arcu. Curabitur non orci eget
                            est porta gravida. Aliquam pretium orci id nisi.
                            Duis faucibus, mi non adipiscing venenatis, erat
                            urna aliquet elit, eu fringilla lacus tellus quis
                            erat. Nam tempus ornare lorem. Nullam feugiat.
                          </p>
                        </div>
                        <div dojoType="dijit.layout.AccordionPane" title="Center">
                        </div>
                        <div dojoType="dijit.layout.AccordionPane" title="Bottom">
                        </div>
                </div>
                <div dojoType="dijit.layout.TabContainer" region="center">
                        <div dojoType="dijit.layout.ContentPane" title="First Tab">
                          <p class="lorem">Lorem ipsum dolor sit amet, consectetuer
                            adipiscing elit. Nam facilisis enim. Pellentesque
                            in elit et lacus euismod dignissim. Aliquam
                            dolor pede, convallis eget, dictum a, blandit
                            ac, urna. Pellentesque sed nunc ut justo
                            volutpat egestas. Class aptent taciti sociosqu
                            ad litora torquent per conubia nostra, per
                            inceptos hymenaeos. In erat. Suspendisse
                            potenti. Fusce faucibus nibh sed nisi. Phasellus
                            faucibus, dui a cursus dapibus, mauris nulla
                            euismod velit, a lobortis turpis arcu vel dui.
                            Pellentesque fermentum ultrices pede. Donec
                            auctor lectus eu arcu. Curabitur non orci eget
                            est porta gravida. Aliquam pretium orci id nisi.
                            Duis faucibus, mi non adipiscing venenatis, erat
                            urna aliquet elit, eu fringilla lacus tellus quis
                            erat. Nam tempus ornare lorem. Nullam feugiat.
                          </p>
                        </div>
                        <div dojoType="dijit.layout.ContentPane" title="Another Tab">
                        </div>
                </div>
                <div dojoType="dijit.layout.BorderContainer" region="right"
                         id="rightSide">

                        <div dojoType="dijit.layout.ContentPane" region="center"
                                 id="topRight">
</div>
                        <div dojoType="dijit.layout.ContentPane" region="bottom"
                                 splitter="true" id="bottomCorner">
</div>
                </div>
        </div>
</body>
</html>
 

You can see this example in action now.

The total size of CSS, JavaScript and HTML is 645KB, spread across 91 requests, taking a total of 2.13 seconds to load and parse when loaded from the local file system. This is less than ideal, and we’ve not even started added complex content! (To be fair, this code includes all comments inline, and no effort has been made yet to optimize this code.) You can see the various lorem paragraphs jump into place, much to the dismay of your designers and users alike. The build system will reduce this impact significantly, but we’re still just developing here, and keeping all the code modular is in our best interest.

Updating all Dojo URLs to point to the AOL CDN, we are able to reduce the page overhead to 104KB, taking a total of 2.12 seconds. In the next article we’ll convert this to a local tree again, and illustrate how a custom build can optimize this even further.

You’ll notice we’re calling dojo.parser.parse() from within a dojo.addOnLoad() function. Had we set djConfig.parseOnLoad = true, any registered addOnLoad functions wouldn’t execute until after parsing takes place. In most cases, this is convenient. In this case, however, we want more granular control over page-load behavior.

The technique we’ll be using to hide the rendering process is simple, and mostly uses plain CSS selectors and design, and minimal amounts of JavaScript to achieve the desired results. We start by adding a new node with position:absolute, taking it out of the layout flow, giving it a high z-index, and a centered background-image of a spinning loader. We’ll then overlay this node across the entire viewport, and do all of our parsing in the background. Add a DIV element with an id of “preloader” in the page (as close to the top of the markup as possible is best, but will work anywhere), and some simple rules:

<style type="text/css">
#preloader {
  width:100%; height:100%; margin:0; padding:0;
  background:#fff
    url(’http://o.aolcdn.com/dojo/1.2/dojox/image/resources/images/loading.gif’)
    no-repeat center center;
    position:absolute;
    z-index:999;
}
</style>
<div id="preloader"></div>
 

If all you see is a spinning icon, we’ve succeeded. Granted, we can’t interact with our page at all now, but we didn’t see any unstyled content. The next step would obviously be to show the content immediately after dojo.parser has run. This is where it is important to have onLoad parsing turned off (via djConfig.parseOnLoad = false) because, as mentioned earlier, addOnLoad fires after parsing when set to true.

Let’s create a simple function to call immediately after parsing to hide the overlay. It can be as simple as setting the CSS property “display” to “none”:

        var hideLoader = function(){
                dojo.style("preloader", "display", "none");
        }
        dojo.addOnLoad(function(){
                dojo.parser.parse();
                hideLoader();
        });
 

A preview of this step: Simply hiding the overly when ready.

It can also be something more aesthetically pleasing, like a fadeOut call. Fading out a node doesn’t affect the ‘display’ property at all, which we’ll need to set to ‘none’ to allow clicks and interaction, as the overlay is actually still in place, just hidden. We can replace our hideLoader function with a simple animation:

        var hideLoader = function(){
                dojo.fadeOut({
                        node:"preloader",
                        onEnd: function(){
                                dojo.style("preloader", "display", "none");
                        }
                }).play();
        }
 

The next optimization we can do involves a small code change. If no dojo.require() calls are made before the DOM is ready, addOnLoad will fire immediately. This works to our advantage here: by loading the simple 26KB dojo.js only, we’re given a really strong set of APIs, without ever including any Dijit components. Let’s defer the loading of the Dijit files until after the DOM is done (ensuring our overlay is up and spinning before ANY XHRs take place). Simply wrap them in an addOnLoad function, still defining our hideLoader function, though this time calling it from within an embedded onLoad function:

<script type="text/javascript">
        // our fancy preloader-hider-function:
        var hideLoader = function(){
                dojo.fadeOut({
                        node:"preloader",
                        duration:700,
                        onEnd: function(){
                                dojo.style("preloader", "display", "none");
                                // $("#preloader").css("display","none");
                        }
                }).play();
        }

        dojo.addOnLoad(function(){
                // after page load, load more stuff (spinner is already spinning)
                dojo.require("dijit.layout.BorderContainer");
                dojo.require("dijit.layout.AccordionContainer");
                dojo.require("dijit.layout.ContentPane");
                dojo.require("dijit.layout.TabContainer");
                dojo.require("dojo.parser");
               
                // notice the second onLoad here:
                dojo.addOnLoad(function(){
                        dojo.parser.parse();
                        hideLoader();
                });
        });
       
</script>       
 

The end result of our trivial code change can be seen in the after onLoad example.

dojo.addOnLoad is smarter (or at the very least does more) than body.onLoad / document.ready / et al. It knows when Dojo modules are in flight or otherwise being requested, and re-fires after all dependencies have been solved. You can use this technique over and over as often as you like. If a module is already ready, dojo.require() is a no-op, and any subsequent dojo.addOnLoad calls will fire relatively quickly.

That’s all there is to it! We’re still using 92 requests (43 from the CDN), and the same byte count on code, and we’ve even actually slowed down the page loading by 700ms (the duration of the fadeOut animation) but perceived speed is speed, and this simply feels a lot faster. Once we’re done developing this page, we can move on into creating a build to limiting the number of requests and concatenate all the CSS together in one fell swoop. The key here is giving the user the sensation of “working”. The loading indicator screams “bear with me, I’ve not left you hanging.”

A designer with good CSS skills can certainly style the content within the preloader node using any standard techniques, customizing the feel with ease, or a developer can create a more riveting animation to hide it. Anything is possible in with the new 1.2 release of the Dojo Toolkit. The Base 26KB dojo.js provides all the utilities needed for Animation, Events, Ajax, Package Loading, Styling, and Node Manipulation, Object orientation, JavaScript lang utilities and the Dijit add-ons can be deferred until much later in a page life cycle to accelerate page load time and application responsiveness.

Bookmark and Share

10 Responses to “Implementing a Web Application Preloading Overlay”

  1. Amazing!

    It is *almost* a Office XP looking page in 5 minutes. To not say app, that would take just a little more with functionalities.

    Thanks.

  2. Bin Hu says:

    Great article. I am looking forward to your article on custom building of Dojo.

  3. erick2red says:

    Nicely done.

  4. Gary says:

    Peter you rule. This and the custom build explanation, http://www.sitepen.com/blog/2008/04/02/dojo-mini-optimization-tricks-with-the-dojo-toolkit/, have been really helpful in getting good stuff out the door.

  5. Justin says:

    This is really cool but there is a slight error.
    The loading.gif in the example did not exist.
    I checked my own dojo1.2 and found you had the url slightly wrong.
    Missing the images/ before the loading.gif.
    Should be.
    dojox/image/resources/images/loading.gif
    If you fix it in this example, it should pretty much cut and paste and work right away.

  6. Keyton Weissinger says:

    Hi Peter,

    Thank you very much for writing this. Well done.

    Quick question. In using this on my own, I get the preloader screen to come up properly but that it’s just shown ABOVE my un-parsed HTML rather than OVER it.

    So for the time I see the spinner, I can just go over to the right, grab the scroll bar that is there, scroll down and see some hideous unparsed HTML. For whatever reason it doesn’t look like the preloader div is displaying OVER/ONTOP of my stuff.

    Any ideas?

    Thank you very much!

    Best,
    Keyton

  7. JM Rigade says:

    Hi,
    cool work, thank you.

    But I’ve some Dojo dialog, and if I change some liste box, an Ajax call is made, but without refresh page.

    So I’ve not waiting animation during ajax request (BDD and so on).
    How lock and grey dialog during Ajax request ?

  8. [...] can’t take any credit for this code so you’ll have to visit Peter Higgins blog at SitePen for all the necessary steps for implementing this solution. The tutorial is easy to follow and [...]

  9. Hi, it’s a great article with great tipps and ideas involved – thank you for that!

    One tiny problem though:
    If a user has Javascript disabled, nothing would appear besides the loading animation. One great architectural aspects of Dojo is that the page would (normally) even work without JavaScript. In this case, it doesn’t because the #preloader will not be hidden and the page would absolutely be useless.

    There might be a workaround with setting the css property to display:none in some area like:

    #preloader { display:none;}

    Regards from Germany,
    Lasse

  10. Hi,

    next try regarding my previous comment:

    “some area like” should read “some ‘noscript’ area” like

    <noscript>
    <style type =”text/css”>
    #preloader { idsplay:none }
    </style>
    </noscript>

Leave a Reply