Create a pseudo-class from a runtime-loaded image in AS3

Recently, I’ve been hacking at low-level AS3 features to try to find a way to load an external image and make a class out of it. Basically, I want to create multiple instances of that image using only the new operator. The image path isn’t known until runtime, so I’d like to make a special Loader subclass that knows the correct image path to load before it gets instantiated. As you can imagine, that’s not the easiest requirement to meet.

Screenshot of application built with the method described below Example Application (View Source Enabled)

A short time ago, Ben Stucki created a class to load external images as icons for Flex components. It’s a very clever solution. He uses a Dictionary to associate the image path with the intended parent. I wish I could have used it, but the class I’m trying to create needs to be instantiated somewhere that I can’t access the parent. Like I said, the class itself really needs to know the correct image URL to load. In AS2, it wouldn’t be too difficult to accomplish. You can hack __proto__ in AS2 to do some interesting runtime subclassing to make a unique class for every URL. Unfortunately, Adobe locked things down a bit in AS3 (for performance reasons, mostly), and __proto__ is no longer accessible to developers.

That said, let’s try to remember back even further, to our AS1 days, when true classes didn’t exist. Classes were function objects, and we added member variables and methods through prototype. Thankfully, class-like functions through prototype are still possible in AS3. Consider the following code:

var MyImageClass:Function = function():Loader
{
	//we need a Loader that automatically loads our image
	var loader:Loader = new Loader();

	//the URL will come from the prototype
	loader.load(new URLRequest(this.url));

	//our "constructor" returns the Loader!
	return loader;
};

//the image to load in our dynamically created "class"
MyImageClass.prototype.url = "yahoo_logo.gif";

This creates a constructor-like function that returns an instance of Loader. This loader automatically loads an image URL that we place in the function’s prototype. If you’re using Flex, you could easily modify this function to make it return a SWFLoader instead.

Usage is surprisingly simple. We pretend MyImageClass is a real class, and our friend the new operator takes care of everything.

//add two images to the display list
var image1:Loader = new MyImageClass();
this.addChild(image1);

var image2:Loader = new MyImageClass();
this.addChild(image2);

Two images will automatically load. That’s perfect!

…well, sadly, it’s not perfect. There’s some bad news. I had hoped this would be able to replace Ben’s code in Flex too, but I quickly discovered that while this special pseudo-class (as I now call it) behaves like a real class, Flash Player knows that it isn’t a real Class. For example, the icon style on the Button component in Flex expects a Class object. Our pseudo-class is actually a Function object, and these two types are not compatible. Flash Player will throw a runtime error if I try to set this style to a Function.

Thankfully, I need these pseudo-classes for a component that I built myself. I can ensure that I type my variables correctly (or at least branch depending on the type) so that both Class and Function can be used. As much as I’d like to be able to use these pseudo-classes for icon and skin styles in Flex, that’s just not in the cards.

If you’re interested, I built a utility class called LoaderUtil with a function createAutoLoader() that encapsulates this functionality into a simple function where you pass an image URL, and an optional LoaderContext (for tweaking cross-domain options and other stuff on the Loader), and you receive one of these pseudo-classes back.

About Josh Tynjala

Josh Tynjala is a frontend developer, open source contributor, bowler hat enthusiast, and karaoke addict. You might be familiar with his project, Feathers UI, an open source user interface library for Starling Framework that is included in the Adobe Gaming SDK.

Discussion

  1. Campbell

    Ahhh yes the restrictions of a strongly typed language. You cant create new classes at runtime and you cant delete instance variables etc etc. Sometimes it was nice to have these options to create some really magick things. I have started using the proxy methods a lot to try and get object to be a bit more dynamic.

  2. Torbjørn Caspersen

    Why not use a static public var or static getter/setter in your class to set the URL? Extend the images class, add static functions, override loader functions and you should be golden.

    I’ve used this technique before, great way to keep classes from knowing about each other.

    Feel free to contact me if anything is unclear.

  3. maliboo

    This function returns nothing but loader itself. So prototyping here is little bit useless;)

    BTW: __proto__ was there since SWF5;)

  4. Josh Tynjala

    I don’t understand what you mean, maliboo. I had to use prototype to associate the URL with the anonymous function so that it could be passed to the new Loader. Notice that I use this.url in the function.

  5. Josh Tynjala

    Torbjørn, I wouldn’t be golden with your suggested method. What if I have two images to load? The static var only supports a single image unless I take special care in timing when the class may be used. I’ll need to change the URL to load the second image, but what if the component needs to use the first URL again? It will have been lost.

    Trust me, I tried many possible approaches over a period of a few days, including what you’ve suggested.

  6. Ian Thomas

    Hi Josh,
    Sounds like I’m currently jumping through exactly the same hoops as you; having real trouble generating a Class object uniquely tied to a BitmapAsset with some associated bitmap Data (I’m trying to do something like you describe with the Flex .icon property).

    It’s maddening. Did you get any further than you describe here?

    Ian

  7. Scott

    There’s another solution, but it’s really hacky. You can create a stand-alone SWF that has your MyImageClass as a symbol in it (make sure to save the swf as uncompressed) and load it into your SWF as a bytearray. Save a copy of this bytearray once it’s loaded.

    When you need to create a new class, make a copy of the bytearray and patch the data in the bytearray containing the string to the string you’re interested in. This clearly will require some hacking around with the SWF file format to make this work. Finally, load from the bytearray and use getDefinition to get the class object.

    This is clearly way overkill for the case at hand, though. But it does illuminate thte possibility of generating classes at run-time. It’s just very, very difficult.

    Too bad actionscript doesn’t have eval(). 🙂

  8. Metal Hurlant

    Prototype isn’t strictly needed here. A function created within a function is a closure, so it keeps a snapshot of the outer functions’ locale variables in its scope.

    The body of createAutoLoader() can simply be:

    return function():Loader {
        var loader:Loader = new Loader();
        loader.load(new URLRequest(url), context);
        return loader;
    };

    One behavioral difference between this and an embedded asset is the dreaded one-frame minimum wait for the remotely loaded image, even when the image is already in the browser cache.
    There are workarounds for it. Ely Greenfield has a Flex SuperImage component that gets the general idea right.
    To deal with multiple loads of the same asset, you’d want to cache the BitmapData object, and associates that with the url. Further attempts to instanciate the AutoLoader would just create a new Bitmap with that BitmapData.

  9. Don

    Hi Josh.
    I’m pretty new to AS3. How do you use the “createAutoLoader” class function? I did the following but getting error during execution. Any help is appreciated.

    var image1 = LoaderUtil.createAutoLoader(o.url);
    addChild(image1);

    TypeError: Error #1034: Type Coercion failed: cannot convert Function to flash.display.DisplayObject.

  10. Josh Tynjala

    Don, it returns a function that you can use with the new keyword, like a class.

    var ImageType:Function = LoaderUtil.createAutoLoader(o.url);
    var image1:Loader = new ImageType();
    addChild(image1);
  11. Don

    Hi Josh, how do I resize the loaded bitmap from your loader function? I tried resize the image to half the size (see below), but nothing is displayed. Thanks in advance.

    //loaded image is 100x100, want to resize it to 50x50
    var ImageType:Function = LoaderUtil.createAutoLoader(o.url);
    var image1:Loader = new ImageType();
    image1.width = 50;
    image1.scaleY = image1.scaleX;
    addChild(image1);