The new script loader component – part II

In part I, we’ve seen how to adapt our scripts for being used by the new script loader component. At the time, I’ve said that you can use an optional first step for giving info about a specific script. This process is know as script definition (at least, that’s what I call it). In this post, we’ll dig into this topic and talk about what you can do when you define a script.

Script definition is generally done through the Sys.loader.defineScripts method. There’s also another possibility here: you can call the Sys.loader.defineScript method (notice the lack of *s* in this case). The main difference between the two is that the latter expects only one script info object (while the first will let you pass several script objects). In this post, I’ll be showing snippets with the defineScripts method since that is the method that is used by the samples.

Script info objects don’t follow the MS AJAX OO approach; instead, they rely on literal JavaScript objects. This is good (and, if you ask me, it’s way better than having to define a JavaScript class just for passing values into a method!), but it also means you’ll have no intellisense. In other words, you’ll have to dig through the code to see which properties you’re suppose to use (to be honest, you do get intellisense when you’re callling the defineScripts method,but not within the { } used for defining the object). After some digging,I’ve found the following properties:

  • name: string which identifies the current script object. This name is used by other script objects for specifying dependencies on an existing script object;
  • executionDependencies: array of strings which identify the script info objects on which the current script object depends on;
  • dependencies: array of strings which identify script dependencies on existing files (as you might expect, we need to pass names that identify previously registered script into objects);
  • isLoaded: boolean which indicates if the current script object has already been loaded;
  • plugins: array of literal objects which identify plugins. Plugins are just simple methods which get added to the Sys.plugins object for easy invocation. We’ll return to this topic in future posts;
  • components: array of strings which identify the MS AJAX components exposed by this script. The advantage on using this property is that the loader will create on-the-fly helpers which simplify the JavaScript code needed for creating those components (more on future posts);
  • behaviors: the same as before, but in this case, the property expects a list of behaviors;
  • releaseUrl: path to the release version of the script file. The release version is (generally) improved for better performance (ex.: it generally is minified);
  • debugUrl: path to the debug version of the script file;
  • contains: array of string used to indicate the script objects “contained” in the current script. This is only used by composite scripts (more about this in future posts).

As you can see, there are lots of things going on here. The plugins, components and behaviors properties are there only to help you populate the Sys object with helper methods that simplify using those features from JavaScript. The contains property is very important and it is only used by composite scripts (rest assured that we’ll come back to this topic).

You might get a little confused with the fact that there are two properties (executionDependencies and dependencies) which look really similar. At least, I was, but I was lucky enough to get a great clarification on it by Dave Reed.

An execution dependency (specified through the executionDependencies property) means that the current script relies on a feature exposed by other script to *work* properly (instead of to *load* properly). For instance, lets assume that we’ve got two functions: A and B. For the sake of this discussion, lets also assume that each function is on a different script file (a.js and b.js) and that B calls A:

a.js
function A() { alert("a"); }

b.js
function B() { A(); alert("b"); }

If you’ve been using JavaScript for some time, you probably know that nothing prevents you from loading b.js before loading a.js. The only thing you need to ensure is that A is defined when you invoke B (ie, you can load the files in any order you want – for instance, you can start by loading b.js and then a.js; the only thing you need to guarantee is that you’ll call B only after a.js has been loaded). If you don’t believe me, then run the following test:

<head>
    <title></title>
    <script src="b.js" type="text/javascript"></script>
    <script src="a.js" type="text/javascript"></script>
</head>
<body>
   <script type="text/javascript">
        //previous scripts already loaded here!
       B();
   </script>
</body>

Taking things to an extreme, nothing would prevent you from loading only the b.js file if you didn’t need to invoke the B function (suppose you’ve got several other helpers in b.js and you’re not using B; in that case, there’s really no need for that extra a.js download).

On the other hand, suppose b.js needs to call A outside a function. Here’s how that might look:

b.js

A(); //global call

function B() { A(); alert("b"); }

In this scenario, you cannot load b.js before a.js. Doing so will result in getting an “object expected” exception because A isn’t defined yet. In this case, we’re facing a script dependency and this kind of thing can be expressed through the script info object’s dependencies property.

In the current release of MS AJAX, MS libs are always expressed through execution dependencies. For instance, here’s the literal object used for defining the history script object:

{ name: "History",
  executionDependencies: ["ComponentModel", "Serialization"],
  isLoaded: !!(Sys.Application && Sys.Application.get_stateString)
}

(as a side note, notice that checking for loading is done by testing for objects and their properties).

On the other hand, dependencies on JQuery libraries always mean loading the entire JQuery script file before trying to load the current script. Here’s how script info objects used for JQuery’s validation lib looks like:

{ name: "jQueryValidate",
  releaseUrl: ajaxPath + "jquery.validate/1.5.5/jquery.validate.min.js",
  debugUrl: ajaxPath + "jquery.validate/1.5.5/jquery.validate.js",
  dependencies: ["jQuery"],
  isLoaded: !!(window.jQuery && jQuery.fn.validate)
}

As you can see, JQuery’s validation plugin defines a dependency on the main JQuery library and that means that jquery.validate.js file will only be loaded after JQuery.js has been loaded and interpreted. Now, compare this behavior with the one you get with MS AJAX libs…

Since they’re based in execution dependencies, then you don’t really care about having to load  script file X only after script file Y has been loaded: instead, you rely on the script loader object because it will ensure that the callback wrapper methods (which, as you’ve seen, wrap the “real library code”) will get called in the correct order! As you might expect, this might lead to some interesting gains when you’ve got several JS files which depend on each other because you can get several files at the same time.

I couldn’t really end this post without mentioning the obvious: any *dependency* can be transformed into an *execution dependency* by introducing a wrapper function. That’s how the MS AJAX library files were transformed into execution dependencies (if you look at the code, you’ll see that every file has been wrapped in a execute method which is also passed as a callback method to the loader).

To show you how easy it is, we’ll update the previous snippet so that it uses this approach and lets us specify the dependencies between A and B as execution dependencies:

a.js

(function() { function initialize() {

        A = function() { alert("a"); }

    }

    if (window.Sys && Sys.loader) {

        Sys.loader.registerScript("A", null, initialize);

    }

    else {

        initialize();

    }

})();

b.js

(function() { function initialize() {

        A(); //global call

       
B = function() { A(); alert("b"); }

    }

    if (window.Sys && Sys.loader) {

        Sys.loader.registerScript("B", null, initialize);

    }

    else {

        initialize();

    }

})();

There are some important changes here. Functions defined within another function are “private” due to the way JavaScript works. So, to make A visible everywhere, I’ve had to change the code: instead of defining a named function, I’ve added a global variable A which points to an anonymous function which does the same work as the previously existing named function.

And now, we can simply write the following HTML:

<head>

    <
title></title>

    <
script src="Scripts/MicrosoftAjax/start.debug.js" type="text/javascript"></script>

    <
script type="text/javascript">

       
Sys.loader.defineScripts( null, [

                                    {

                                      releaseUrl: ”a.js”,

                                      debugUrl: ”a.js”,

                                      name: ”A”

                                   
},

                                    {

                                      releaseUrl: ”B.JS”,

                                      debugUrl: ”B.JS”,

                                      name: ”B”,

                                      executionDependencies:[”A”]

                                    }] );

    </script>

    <
script src="b.js" type="text/javascript"></script>

    <
script src="a.js" type="text/javascript"></script>

</
head>

<
body>  
</
body>

A couple of observations before ending this long post. You’ll probably want to define the scripts in an external file so that you can reuse that information across your pages. Another interesting thing: as you can see, we’re loading b.js before a.js and we’re not gettin any errors!

It’s also important to understand that specifying a script definition won’t really lead to automatic downloads of scripts. If I hadn’t explicitly added the a.js file in the previous snippet, you’d never see the alert message shown when A is called globally within b.js. However, since I’ve added that file, that means that B’s dependency is satisfied and its callback execution method will be fired (leading to its initialization and to the alert message being shown).

As we’ll see in the next post, we don’t really need to add those scripts by hand; instead, we can rely on using JavaScript for forcing the download of the necessary files! That will be the topic of the next post! Stay tuned for more on MS AJAX!

Advertisements

~ by Luis Abreu on October 16, 2009.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

 
%d bloggers like this: