Loading and Tracking Multiple Files at the Same Time (Part 1)

ActionScript 2.0

Not long ago, a blog guest entered into conversation with me about loading and tracking multiple files at the same time (see Event Handlers versus Event Listeners comments, starting with the eighth comment).  Tiemen was hoping for a way to use the MovieClipLoader class inside a custom ActionScript 2.0 class, not only to load external files — which isn’t especially difficult — but also to track each file individually; that is, to be able to pass individual functions into the custom class, and have that class manage the tedium of assigning the passed-in functions to the MovieClipLoader events of each file.  This particular requirement was the tricky part.  If the custom class merely had to manage event handlers for a single MovieClipLoader instance, that would be easy.  (In fact, this is completely possible.  Be aware, if you choose to read the other entry’s comments, I mistakenly believed at the time that the events of a single MovieClipLoader instance were not capable of providing useful information for more than one loading file at a time.  I’ve noted those errors in my replies.)  Because Tiemen wanted the ability to assign separate functions for each file’s events, well … that meant maintaining an array of MovieClipLoader instances.  Let’s take a look at one way to accomplish this goal. 

First, a bit of prep

In this solution, we’re going to write a custom AS2 class.  In order to write and use 3rd party classes, even if they’re your own, you need to understand the concept of classpaths in Flash.  If you feel you need a refresher, check out my Community MX Understanding Classpaths article.

Before we dive into any AS2 class code, let’s review what needs to occur in order to handle MovieClipLoader events in the first place.  We’ll need a) a MovieClipLoader instance, b) a generic Object instance to act as a listener, and c) a function to handle the desired event.  Here’s a super quick example, which assumes a movie clip container with the instance name containerClip (it is into this clip that an external JPG will be loaded).

var mcl:MovieClipLoader = new MovieClipLoader();

That’s our MovieClipLoader instance.  From this point forward, the variable mcl gives us access to all the functionality provided by the MovieClipLoader class.  In a moment, we’ll use this instance to invoke the MovieClipLoader.loadClip() method, for example.  But first, we need a generic Object instance to act as our liaison.

var listener:Object = new Object();
listener.onLoadInit = function(mc:MovieClip):Void {
  trace(mc + " has loaded");
}

What’s this?  Well, so far, we’ve instantiated an Object instance, which is referenced by the variable listener.  This listener object will handle the MovieClipLoader.onLoadInit event on behalf of the mcl instance above.  (To see all the events available to this class, look up the “MovieClipLoader class” entry of the ActionScript 2.0 Language Reference.)  This listener.onLoadInit property is assigned a function literal that simply traces an “I’m done” message to the Output panel.  What’s the mc parameter in that function?  That’s what allows MovieClipLoader to track files individually.  In my conversation with Tiemen, I had forgotten about the parameters (often optional) available to each event, even though the Language Reference clearly has them listed.

Here’s what the event signature looks like in the documentation:

onLoadInit = function([target_mc:MovieClip]) {}

What that means, in documentation-speak, is that when handling the onLoadInit event, the assigned function may optionally receive a single parameter of type MovieClip.  The reason you know it’s optional is because it’s shown inside a pair of brackets.  Brackets only indicate the state of being optional in documentation signatures such as this:  normally, brackets are known as the array access operator, or provide a shorthand way of creating an Array instance.

In actual practice, you can name the incoming parameter whatever you like (it doesn’t have to be target_mc); and in fact, in our example we’re using, simply, mc.  All this really means is that the variable mc may be used inside the function itself to refer to the movie clip that was chosen as the target for the loading file, as specified in the loadClip() method, which we’re about to see.

Now that we’ve assigned a function to the onLoadInit event, we need to “subscribe” our MovieClipLoader instance to the listener.

mcl.addListener(listener);

Finally, we’ll load up a file …

mcl.loadClip("file.jpg", containerClip);

All right.  When file.jpg loads the the first frame (perhaps the only frame) of its container clips has been executed, the onLoadInit event will be dispatched.  It will immediately be handled by the function literal shown above.  Let’s recap the whole thing and look at the code all together, because it’s actually quite short, in spite of several paragraphs’ explanation.

var mcl:MovieClipLoader = new MovieClipLoader();
var listener:Object = new Object();
  listener.onLoadInit = function(mc:MovieClip):Void {
  trace(mc + " has loaded");
}
mcl.addListener(listener);
mcl.loadClip("file.jpg", containerClip);

Same basic process for the other events available to this class.  If you’re only loading one or two images (or SWFs, etc.) then it isn’t much bother.  But if you’re loading, say, 50, all those event handler assignments could get pretty monotonous.

Making things simpler with a class

Again, if Tiemen (or anyone) didn’t mind assigning the same functions to the events of each loading file, a custom class probably wouldn’t make sense.  What do I mean?  Well, if all the developer cares about is noting that all 50 files have loaded, the existing ActionScript would be the only code necessary.  Each new call to loadClip() would start a new load process, but the single MovieClipLoader instance, mcl, would handle the onLoadInit (or any) event as easily for one as for all.  In the case of this …

mcl.loadClip("file1.jpg", containerClipA);
mcl.loadClip("file2.jpg", containerClipB);
mcl.loadClip("file3.jpg", containerClipC);

… the existing event handler for onLoadInit() would trigger that same trace() statement as each of the three JPGs loaded.  The value of mc would be different each time — because each JPG was loaded into its own separate container clip — so the traced messages would be “containerClipA has loaded,” “containerClipB has loaded,” etc. (in whatever order the JPGs happened to load; might not be A, B, then C).

But if each file is supposed to have its own distinct event handler function, we’ll need an array to hold one MovieClipLoader instance for each file loaded.  Here’s the actual AS2 class file code, and we’ll discuss it piece by piece afterward.

Don’t get nervous if it looks like a lot.  There are only two properties in this class, and only two methods.  I’ll meet you on the other side.

class MultiLoader {
  // PROPERTIES
  private var _clips:Array;
  private var _loaders:Array;
  // CONSTRUCTOR
  public function MultiLoader() {
    _clips = new Array();
    _loaders = new Array();
  };
  // METHODS
  // Add Clip
  public function addClip(
    url:String,
    target:MovieClip,
    handlers:Object
  ):Void {
    _clips.push(new Object());
    var current:Number = _clips.length - 1;
    _clips[current].url = url;
    _clips[current].target = target;
    _clips[current].handlers = handlers;
  }
  // Load Clips
  public function loadClips():Void {
    var i:Number = 0;
    var len:Number = _clips.length;
    var prop:String = "";
    for (i = 0; i < len; i++) {
      var listener:Object = Object();
      _loaders[i] = new MovieClipLoader();
      for (prop in _clips[i].handlers) {
        listener[prop] = _clips[i].handlers[prop];
      }
      _loaders[i].addListener(listener);
      _loaders[i].loadClip(
        _clips[i].url,
        _clips[i].target
      );
    }
  }
}

Class explanation

Okay, so what’s going on, here?

First, the class declaration:

class MultiLoader { … }

This is an arbitrarily named MultiLoader class.  When it’s finished, you’ll be able to instantiate it just like any native class, such as Array, Date, and the like.

This class has two properties.  They’re private, because outside code doesn’t need to know anything about them.

// PROPERTIES
private var _clips:Array;
private var _loaders:Array;

One Array instance, _clips, will hold information about what files to load.  (I could have called it _files, because MovieClipLoader can actually load much more than SWFs in recent years, but hey … _clips is what it is.  Incidentally, the underscore doesn’t affect the property at all, but as these are private, the underscore reminds me at a glance, “Hey, bub, these are private”; it’s just a convention.)

Next, the constructor.  This is what allows the class to be instantiated by timeline code (or by another class) at a later time.

// CONSTRUCTOR
public function MultiLoader() {
  _clips = new Array();
  _loaders = new Array();
};

Constructors don’t necessarily have to do anything, but this one initializes our two properties to an Array instance apiece.  These properties are now valid, empty arrays.

Here’s our first method, MultiLoader.addClip().

// METHODS
// Add Clip
public function addClip(
  url:String,
  target:MovieClip,
  handlers:Object
):Void {
  _clips.push(new Object());
  var current:Number = _clips.length - 1;
  _clips[current].url = url;
  _clips[current].target = target;
  _clips[current].handlers = handlers;
}

Note that it accepts three parameters:  url, target, and handlersMovieClipLoader, itself, requires a file URL and a target clip, so that explains the first two.  The handlers parameters is optional, and allows outside code to pass in an object that contains event handler functions.  I’m sure there are other ways this could have been done, but this seems an easy enough approach to me.  When the class is finished, you’ll be able to create a MultiLoader instance — say, ml — and invoke the addClip() method for as many files as you like, including as many events as you like, all in one shot per file.

ml.loadClip("file.jpg", containerClip, {onLoadInit:customFunction});

That’s fairly compact.  Because the final parameter is an object, it can contain as many name/value pairs as we need, the form of property:value groupings.  If you wanted to make two handler assignments, for example, you might put …

{onLoadInit:customFunctionA, onLoadProgress:customFunctionB}

… where assignments are separated by commas and indicate a) the desired event to handle, b) then a colon, then c) the custom function to be triggered by that particular event.

Now, here are the last few lines:

_clips.push(new Object());
var current:Number = _clips.length - 1;
_clips[current].url = url;
_clips[current].target = target;
_clips[current].handlers = handlers;

First, a new Object instance is added to the _clips array.  Next, a local variable, current, is declared and set to the value of _clips.length - 1.  What does that mean?  Well, the Array.length property returns the number of elements currently in an Array instance.  Arrays start counting at zero, rather than one, so if we want to manipulate the first element of the _clips array, we need element zero (_clips[0]).  The expression _clips.length -1 always points to the latest (most recent, or last) element in the _clips array.  The first time around, length is 1 … 1 - 1 is zero, so that gives us _clips[0].

_clips[current].url, therefore, points to the url property of the new Object just pushed into the array.  Of course, this Object instance won’t have a url property by default, but if one isn’t present, it will simply be made.  So _clips[current].url, in this context, means the url property of the object that lives in element 0 of the _clips array.  This property is set to the passed-in url parameter.  The same goes for the other two parameters.

The long and short of it is, the _clips array stores the necessary information to load an external file, which includes the file’s path, its target movie clip container, and any desired events to handle.

The loadClips() method is the most complex, so we’ll take smaller steps.

// Load Clips
public function loadClips():Void {
  var i:Number = 0;
  var len:Number = _clips.length;
  var prop:String = "";

So far, we’re simply setting up a few variables to be used by this method.  The i variable will take us through a for() loop; len simply refers to the current length of the _clips array (so that we don’t have to keep calculating that figure); prop is, for now, an empty string.

for (i = 0; i < len; i++) {

Here’s our for() loop.

var listener:Object = Object();
_loaders[i] = new MovieClipLoader();
for (prop in _clips[i].handlers) {
  listener[prop] = _clips[i].handlers[prop];
}
_loaders[i].addListener(listener);

These lines basically mimic the event handling we saw at the very beginning.  A generic Object instance is created, again, to act as our liaison.  The _loaders[i] expression refers to the _loaders array, which is paired with the _clips array.  If i is currently zero, for example, then _loaders[i] refers to the first element in the _loaders array, which is paired with the first element in the _clips array, _clips[i].  So … a MovieClipLoader instance is added to the _loaders array.  The next three lines, a for..in loop, handle all of the passed-in events at once.  This is some pretty neat code.

for..in simply steps through the properties in a given object.  The object in question, here, is the _clips[i].handlers object — that is, the handlers property of the generic Object instance at element i of the _clips array.  Remember, we passed in a handlers object as the third parameter to the addClip() method; here, we’re just accessing that object again.

In a for..in loop, the syntax goes:  for every something in an object … — where the “something” is a string (our prop string) — do … well, do something.  In this case, we’re looking for every property in the _clips handlers object that matches the current MovieClipLoader instance.  By using the array access operator, as described in this blog entry, we pull the function reference out of the relevant handlers object in the _clips array and assign it to the relevant event object in the listener object.

This is the single most “hard to get” line in this class, so maybe the following will illuminate it.  The value of i will keep changing with each pass through the for() loop, but the first time around, its value is 0.  That means …

listener[prop] = _clips[i].handlers[prop];

… is the same as saying …

listener[prop] = _clips[0].handlers[prop];

So far, so good, right?  In like fashion, the value of prop will keep changing with each pass through the for..in loop.  If there are a handful of properties inside the handlers object — the first of which happens to be, say, onLoadInit — then this …

listener[prop] = _clips[0].handlers[prop];

… is the same as saying …

listener["onLoadInit"] = _clips[0].handlers["onLoadInit"];

… which, in turn, is the same as saying …

listener.onLoadInit = _clips[0].handlers.onLoadInit;

… which (patience!) is the same as saying …

listener.onLoadInit = myCustomFunction;

What?!  What’s that last leap?  Well, the actual function may or may not be called myCustomFunction, but if you follow the pattern, the loadClip() method accepted three parameters.  The third parameter was an object that may have contained a property:value pair like this: {onLoadInit:myCustomFunction}.  We’re taking the second side of that colon and passing it to the “liaison” property of our listener object.  It’s just that there are potentially very many of these, and we’re keeping track of each one in an array (and pulling information to populate these liaison properties from another array).

Okay, let’s take it home.

    _loaders[i].loadClip(
      _clips[i].url,
      _clips[i].target
    );
  }
}

This rounds out the for() loop by invoking MovieClipLoader.loadClip() on each MovieClipLoader instance stored in the _loaders array in turn.  In a similar manner to the for..in technique we just saw, it passes the relevant url and target properties of each generic Object instance — the instance that stores the information for each file — to the loadClip() method.

Using the class

With all that out of the way, let’s take a quick look at how to use the MultiLoader class.

Assuming three container clips by the instance names clipA, clipB, and clipC

var ml:MultiLoader = new MultiLoader();
ml.addClip("file1.jpg", clipA, {onLoadProgress:progressA, onLoadInit:initA});
ml.addClip("file2.jpg", clipB, {onLoadProgress:progressB, onLoadInit:initB});
ml.addClip("file3.jpg", clipC, {onLoadProgress:progressC, onLoadInit:initC});
ml.loadClips();

That’s it.  The functionsprogressA, initA, and so on — are up to you.  But they’ll be wired up to the MovieClipLoader events on your behalf, without really any effort at all.  Here’s a full example with stand-in functions:

Just repeat the above, then follow it with …

function progressA(mc:MovieClip, loaded:Number, total:Number):Void {
  trace("progressA " + mc + ": " + loaded + "/" + total);
}
function progressB(mc:MovieClip, loaded:Number, total:Number):Void {
  trace("progressB " + mc + ": " + loaded + "/" + total);
}
function progressC(mc:MovieClip, loaded:Number, total:Number):Void {
  trace("progressC " + mc + ": " + loaded + "/" + total);
}
function initA(mc:MovieClip):Void {
  trace("initA " + mc);
}
function initB(mc:MovieClip):Void {
  trace("initB " + mc);
}
function initC(mc:MovieClip):Void {
  trace("initC " + mc);
}

Each loading file’s onLoadProgress and onLoadInit events will be handled by separate functions.  In this way, you can keep track of each loading JPG’s load progress individually.  In a follow-up entry, I’ll show one way to handle the load progress of all JPGs (or whatever files) collectively.

22 Responses to “Loading and Tracking Multiple Files at the Same Time (Part 1)”

  1. Tiemen Says:

    Wow David, incredible!

    Was busy for a few days, so didn’t check your blog- but you really outdone yourself here! What a vast entry… enough for me to digest for days…

    But totally awesome man, incredibly in depth and still of so much use for some many people (I think). I’m pretty confident there isn’t any comparable tutorial/lesson on the whole wide web!

    Thanks a lot, now time for me to attack da code! :-)

  2. Tiemen Says:

    Oh and I’m feeling famous too, being mentioned in the post (though in the role of the questioning kid, but nonetheless!) :-)

  3. David Stiller Says:

    Tiemen,

    Hope it helps. This was a fun exercise to work through, and I’ll have Part 2 up as soon as I can manage a bit more time.

  4. Tiemen Says:

    Yeah, it still doesn’t seem possible to include the container creation module inside the loader class. Which would make it the masterful loader class I’m after.

  5. David Stiller Says:

    Tiemen,

    Actually, you could. :) The ActionScript inside a class, after all, is nothing more than neatly packaged ActionScript, and it’s possible to create containers with ActionScript. I would still recommend passing in a target timeline from the outside (otherwise, how would the class know where you want your container?), but for example, this might do …

    class MultiLoader {
      // PROPERTIES
      private var _container:MovieClip;
      private var _clips:Array;
      private var _loaders:Array;
      // CONSTRUCTOR
      public function MultiLoader(target:MovieClip) {
        _container = target.createEmptyMovieClip(
          "multiLoaderContainer", target.getNextHighestDepth();
        );
        _clips = new Array();
        _loaders = new Array();
      };
    // ...

    Then you could modify the addClip() method to omit the target parameter and specify _container as your target instead.

    Just keep in mind, in AS2 movie clips cannot be re-parented: once a clip becomes the child of a timeline, it stays the child of that timeline or is removed (destroyed), period. So the original approach, I think, gives you more flexibility in terms of swapping clips with each other.

  6. grubby Says:

    I wanted to thank you personally since this blog has helped me a lot to solve my own problem!! on forwarding the MovieClipLoader’s events to methods inside a custom class since I needed two MCL’s and seperate listeners for each (where u cant use “this” for both) and was not even able to use two objects to handle each events… i realised from here that all i had to do is this:

    listener[prop] = _clips[i].handlers[prop];

    which meant;

    listener.onLoadProgress=this.wahteverCustomMethodInsideTheClass

    duh……..sometimes the answer is so simple and its in front of you but u just cant see it!

    keep up the good job!!
    cheerz

  7. David Stiller Says:

    grubby,

    Rockin’. :) Glad to have been of some help.

  8. jordan robinson Says:

    I am having issues getting this running. I keep getting:

    The class being compiled, ‘MultiLoader’, does not match the class that was imported, ‘com.mighty55.utilities.MultiLoader’.

  9. David Stiller Says:

    jordan,

    See “Understanding Classpaths” (free article) for an overview on global classpaths and how they relate to importing classes. The class file in this blog entry doesn’t have a package, so you would only have to import MultiLoader, if you have to import anything. You may already have someone else’s class on your hard drive that happens to share the name MultiLoader.

  10. jordan robinson Says:

    Yep, read through that article before I tackled building a test file. I’ve set up class paths a few times using the preference window in flash or in the actionscript settings during publish. I’ll double check everything but It does seem to be loading the actual file then giving me that error.

  11. David Stiller Says:

    jordan,

    Aha! You’re mighty55.com! :) So then your version of MultiLoader must exist in a nested folder structure on your hard drive that goes like this: com/mighty55/utilities, and the root path that holds that com folder should be referenced in your classpath settings. On top of that, your actual class file would start like this:

    class com.mighty55.utilities.MultiLoader {

    Is that what you’ve got?

  12. jordan robinson Says:

    I got it and it was a pathing issue. i forgot to add the full path in the class declaration of the .as file. Thanks for the help!

  13. David Stiller Says:

    jordan,

    There you go! :)

  14. discorax Says:

    I’m trying to re purpose this class to instead get all the stuff loaded at once and provide feedback for all of them to work all at once. I’m having a problem calling a private function within the class.

    Is it possible to set up a private function within this class that calls onLoadProgress and a different function within the class that calls onLoadComplete?

    Thanks,
    Ryan

  15. David Stiller Says:

    discorax,

    I’m not sure I fully understand your question, to be honest, but that said … have you read the follow up entry to this first one? Part 2 features a private getGroupProgress() method.

  16. Mandy Says:

    Hi David,

    This is a really organized class. Thanks for it. Reminds me of all ajax libraries these days the way you wrote this class. I think you should consider writing your own library and releasing it as open source with all the cool classes that you have written.

    Finally, I think adding an public function unloadClips() would also be very useful when all clips need to be removed before loading the second set. Since this class tracks all the registered clips&handlers it should also be able to unload them.

    Thoughts?

    Thanks,
    Mandy.

  17. Chuck Says:

    I have four images in the library (image1.jpg, image2.jpg, image3.jpg, image4.jpg).
    I have four empty movie clips which I created the on the main timeline (clip1_mc, clip2_mc, clip3_mc, clip4_mc).

    What I want to do is to ramdomly load and unload the images into the empty movie clips.
    So, for example image1.jpg might load into clip2_mc and then unload, image4.jpg might load into clip1_mc etc.

    Can you help?

  18. David Stiller Says:

    To Mandy …

    Thanks for your comments! Something in my memory is telling me — suggesting, more like — that I replied to you personally via email, months ago. If I didn’t, then I apologize profusely for waiting so long to reply! An unloadClips() method sounds like a great idea. Whenever I get around to updating this class (and there’s no saying when that might be), I’ll be happy to keep your suggestion in mind.

    To Chuck …

    The container clip is determined by the addClip() method (it’s the second parameter), so you could populate an array with references to your containers (clip1_mc, clip2_mc, etc.) and use the shuffle() suggested in “How to Jump Randomly to Frame Labels without Repeats” to mix them up. At that point, you could step through that array with a for loop to invoke addClip() — or heck, you could even do it by hand:

    // assuming the array has already been shuffled ...
    ml.addClip("file1.jpg", containers[0], {onLoadProgress:progressA, onLoadInit:initA});
    ml.addClip("file2.jpg", containers[1], {onLoadProgress:progressB, onLoadInit:initB});
    ml.addClip("file3.jpg", containers[2], {onLoadProgress:progressC, onLoadInit:initC});
  19. Chuck Says:

    I am not sure how to go about it.

  20. David Stiller Says:

    Chuck,

    Create an Array instance to store your references:

    var containers:Array = new Array(clip1_mc, clip2_mc, clip3_mc, clip4_mc);

    Now you have an array (a list, basically) that contains the instance names to your movie clips. You can call the variable what you like, but I chose containers because that’s the role these movie clips play — they’re containers for your images.

    Next, refer to the “Jump Randomly” article I linked to before to find the custom shuffle() function. Use that function to re-arrange the elements of your containers array. Now that they’ve been shuffled, you can create your MultiLoader instance and invoke its addClip() method as before — except, instead of specifying movie clip containers by name, you’ll refer to them via their references as stored in the containers array … and because it’s been shuffled (mixed randomly), you can reference your movie clip’s in order, from zero to three (thus making four references):

    ml.addClip("image1.jpg", containers[0], {onLoadProgress:progressA, onLoadInit:initA});
    ml.addClip("image2.jpg", containers[1], {onLoadProgress:progressB, onLoadInit:initB});
    ml.addClip("image3.jpg", containers[2], {onLoadProgress:progressC, onLoadInit:initC});
    ml.addClip("image4.jpg", containers[3], {onLoadProgress:progressD, onLoadInit:initD});
  21. annie Says:

    Hi David,

    I keep coming back to your website…great as usual :)

    I was just wondering if there’s a way to perform an action only after all of the clips/images associated with a MovieClipLoader have been loaded. Here’s what I’m doing now - using a counter - but maybe there’s an easier way? Say for example I want to order a set of variable-sized images once they’ve all loaded.


    class gallery() {
    private var galleryMC:MovieClip;
    private var images:Array;
    private var numImagesLoaded:Number;

    function gallery() {
    galleryMC = _root.createEmptyMovieClip("myGalleryMovieClip", _root.getNextHighestDepth());
    images = ["1.jpg", "2.jpg", "3.jpg", "4.jpg"];
    numImagesLoaded = 0;

    var gall = this;

    //load images and center
    var mcl:MovieClipLoader = new MovieClipLoader();
    var listener:Object = new Object();
    listener.onLoadInit = function(mc:MovieClip):Void {
    gall.numImagesLoaded++;
    if(gall.numImagesLoaded == gall.images.length - 1) {
    gall.orderGallery();
    }
    }
    mcl.addListener(listener);

    //load all the images
    for (var i=0; i

    Thanks,
    Annie

  22. David Stiller Says:

    Annie,

    Looks like part of your code got cut off, but I think the answer to your question lies in part 2 of this tutorial.

    You’ll see an onGroupProgress event that tells you groupBytesLoaded and groupBytesTotal values for the whole group of clips/images. I think that should do it for you, but let me know if that doesn’t make sense, okay?

Leave a Reply