Approach towards Lazy Loading and booting Backbase

Introduction

I am not under the impression that what we are doing here is rocket science, but might be nice to share anyway. Lets first elaborate a bit on the situation at hand.

At my current client Backbase is used in a progressive enhancement kind of way:

<!-- Backbase Library -->
<script type="text/javascript" src="core/4_3_1/engine/backbase.js"></script>

<!-- Bindings and controllers -->
<script type="application/backbase" xmlns:myNS="http://my.domain/2010/controls">
  <xi:Include href="bindings/myNS/config.xml" xmlns:xi="http://www.w3.org/2001/XInclude" />
  <myNS:WidgetController />
</script>
<!-- Example of progressive enhanced hyperlink -->
<a widgetType="myType" onclick="javascript:MyController.loadWidget(event)">Click me</a>

For this to be working, you need to be aware that the WidgetController has created a top level plain Javascript object that interfaces to the Backbase WidgetController instance:

<d:constructor type="application/javascript">
  oThis = this;
  MyController = Window.MyController || {};
  MyController.loadWidget = function(event) {
   oThis.loadWidget(event);
 }
</d:contructor>

Although the upsides of this type of progressive enhancement in this way is obvious (tiny model tree, lazy loading of your bindings) you are still running into the problem of having the Backbase engine loaded and booted, even though you are not sure if anybody is actually going to click any link or button that triggers you widget. From terms of website optimization it seems to make sense and see if this can be improved, and after some hours playing around a workable solution seems to be at hand.

Going lazy

The major thing we want to prevent is not only to lazy load the bindings, but lazy load the backbase core engine as well. To achieve this, we need to create a dummy Javascript file that acts as a dummy or proxy.

It needs to make sure that all MyController interface methods seem to be available, but instead insert some lazy loading logic in between. The tricky part is to make sure that the controller overwrites the proxy interface functions once loaded.

<!-- Backbase Library is loaded lazy -->
<-- script type="text/javascript" src="backbase/4_3_1/engine/backbase.js"></script-->

<!-- Bindings and controllers are still needed once Backbase boots -->
<script type="application/backbase" xmlns:myNS="http://my.domain/2010/controls">
  <xi:Include href="bindings/myNS/config.xml" xmlns:xi="http://www.w3.org/2001/XInclude" />
  <myNS:WidgetController />
</script>

<script type="text/javascript">
MyController = window.MyController || {};

// Proxy function for the original loadWidget function.
// Note that it will be overwritten when the actual
// Backbase controller kicks in.
MyController.loadWidget = MyController.loadWidget || function() {
  var args = arguments;
  MyController.loadBackbase(function() {
    MyController.loadWidget.apply(MyController, args);
  });
} 

// Backbase lazy loader
MyController.loadBackbase = function(fCallback) {
  
  // Create a callback that runs every 100ms until Backbase has booted
  var waitUntilBackbaseIsRunning = function() {
     if(typeof bb == "undefined" || typeof bb.document == "undefined" || bb.document == null) {
       setTimeout(makeSureBackbaseIsRunning, 100);
       return;
     }
     fCallback();
  }

// Only if bb object does not exist, we need to load it
if(typeof bb == "undefined") {
var scriptTag = document.createElement('script');
scriptTag.setAttribute('src', 'backbase/4_3_1/engine/backbase.js');
scriptTag.setAttribute('type', 'text/javascript');
document.getElementsByTagName('head')[0].appendChild(scriptTag);

// When loaded in this way, Backbase will not boot automatically
MyController.tryAndBootBackbase();
}

// Use a timeout to start waiting for backbase to become available
  setTimeout(waitUntilBackbaseIsRunning, 100);
}

// Backbase booter
MyController.tryAndBootBackbase = function() {
  if(typeof bb == "undefined") {
    setTimeout(MyController.tryAndBootBackbase, 100);
    return;
  } 
  bb.boot();
</script>

Going through the code step by step we can identify several stages:

  1. First we create a series of proxy functions that call the
    MyController.loadBackbase

    function with a callback that seems to call itself with the original arguments. The key detail is to remember that the function gets overwritten once the controller has kicked in. So even though the name is the same, the function logic is updated with the original one.

  2. The
    MyController.loadBackbase

    basically tests if Backbase is loaded or not. When a

    bb

    object is not available, this indicates that

    backbase.js

    is not loaded.

  3. If
    bb

    is available, but

    bb.document

    is not (or null), the

    backbase.js

    is loaded, but Backbase hasn’t booted yet.

  4. If
    bb.document

    is available we know Backbase is available and we can call the original function. Even if the controller has not been launched yet, we know that it will launch and overwrite the original function any moment now. If we by accident call the MyController.loadWidget too soon, the process will just re-iterate until the interface method is overwritten.

And that is about it.

Be Sociable, Share!

Tags: , , , , ,

Leave a Comment

This blog is kept spam free by WP-SpamFree.