views:

148

answers:

3

I'm using a function to lazy-load the Sizzle selector engine (used by jQuery):

var sizzle_loaded;

// load the Sizzle script
function load_sizzle(module_name) {

  var script;

  // load Sizzle script and set up 'onload' and 'onreadystatechange' event
  // handlers to ensure that external script is loaded before dependent
  // code is executed
  script = document.createElement('script');
  script.src = 'sizzle.min.js';
  script.onload = function() {
    sizzle_loaded = true;
    gather_content(module_name);
  };
  script.onreadystatechange = function() {
    if ((script.readyState === 'loaded' || script.readyState === 'complete') &&
        !sizzle_loaded) {
      sizzle_loaded = true;
      gather_content(module_name);
    }
  };

  // append script to the document
  document.getElementsByTagName('head')[0].appendChild(script);

}

I set the onload and onreadystatechange event handlers, as well as the sizzle_loaded flag to call another function (gather_content()) as soon as Sizzle has loaded. All of this is needed to do this in a cross-browser way.

Until now, my project only had to lazy-load Sizzle at one point in the script, so I was able to just hard-code the gather_content() function call into the load_sizzle() function.

However, I now need to lazy-load Sizzle at two different points in the script, and call a different function either time once it's loaded. My first instinct was to modify the function to accept a callback function:

var sizzle_loaded;

// load the Sizzle script
function load_sizzle(module_name, callback) {

  var script;

  // load Sizzle script and set up 'onload' and 'onreadystatechange' event
  // handlers to ensure that external script is loaded before dependent
  // code is executed
  script = document.createElement('script');
  script.src = 'sizzle.min.js';
  script.onload = function() {
    sizzle_loaded = true;
    callback(module_name);
  };
  script.onreadystatechange = function() {
    if ((script.readyState === 'loaded' || script.readyState === 'complete') &&
        !sizzle_loaded) {
      sizzle_loaded = true;
      callback(module_name);
    }
  };

  // append script to the document
  document.getElementsByTagName('head')[0].appendChild(script);

}

Then, I could just call it like this:

load_sizzle(module_name, gather_content);

However, the other callback function that I need to use takes more parameters than gather_content() does.

How can I modify my function so that I can specify a variable number of parameters, to be passed with the callback function? Or, am I going about this the wrong way?

Ultimately, I just want to load Sizzle, then call any function that I need to (with any arguments that it needs) once it's done loading.

Thanks for any help!

+3  A: 

The general idea here is to create a closure or lambda. In a way, you can consider them as a function that's pre-loaded with parameters that's ready to be called. This is also sometimes called a delegate.

load_sizzle( module_name, function()
{
  gather_content();
});

Then, for your other case

load_sizzle( module_name, function()
{
  some_other_function( param1, param2 );
});

More reading on closures.

Peter Bailey
Awesome, thanks, Peter!
Bungle
+1  A: 

You can use the apply method on the callback:

function load_sizzle(module_name, callback,args) {

  var script, args=args || []; //Be sure that an array is passed

  // load Sizzle script and set up 'onload' and 'onreadystatechange' event
  // handlers to ensure that external script is loaded before dependent
  // code is executed
  script = document.createElement('script');
  script.src = 'sizzle.min.js';
  script.onload = function() {
    sizzle_loaded = true;
    callback.apply(window,[module_name].concat(args)); //Add the module_name as first argument and then every other argument specified by the user
  };
  script.onreadystatechange = function() {
    if ((script.readyState === 'loaded' || script.readyState === 'complete') &&
        !sizzle_loaded) {
      sizzle_loaded = true;
      callback(module_name);
    }
  };

  // append script to the document
  document.getElementsByTagName('head')[0].appendChild(script);

}

Then as third argument of load_sizzle you can pass an array of extra arguments for the function.

You can also improve the code with this:

function load_sizzle(module_name, callback,args,bind) {

  var script, args=args || [],bind=bind || window;

  // load Sizzle script and set up 'onload' and 'onreadystatechange' event
  // handlers to ensure that external script is loaded before dependent
  // code is executed
  script = document.createElement('script');
  script.src = 'sizzle.min.js';
  script.onload = function() {
    sizzle_loaded = true;
    callback.apply(bind,[module_name].concat(args)); //Add the module_name as first argument and then every other argument specified by the user
  };
  script.onreadystatechange = function() {
    if ((script.readyState === 'loaded' || script.readyState === 'complete') &&
        !sizzle_loaded) {
      sizzle_loaded = true;
      callback(module_name);
    }
  };

  // append script to the document
  document.getElementsByTagName('head')[0].appendChild(script);

}

In this way the argument number 4 (if specified) can be an object that will be the "this" inside the callback that you pass.

mck89
Thanks very much, mck89! I ended up going with Peter Bailey's solution (it seems more elegant to me, and requires fewer code changes). However, FWIW, your answer was very educational in its own right. Thanks again!
Bungle
+1  A: 

You can use arguments array to get all arguments that were passed into the function.

function example() {
for( var i = 0; i < arguments.length; i++ ) {
    alert('argument ' + i + ' ' + arguments[i]);
}
}

example('any', 'number', 'of', 'arguments);
Andy