views:

131

answers:

2

I have a weird quirk in ActionScript. I need to pass the index to a callback function.

Here is my code

for (var i:Number = 0; ((i < arrayQueue.length) && uploading); i++)
{
    var lid:ListItemData=ListItemData(arrayQueue[i]);
    var localI:Number= new Number(i); // to copy?
    var errorCallback:Function = function():void { OnUploadError(localI); };
    var progressCallback:Function = function(e:ProgressEvent):void { lid.progress = e; OnUploadProgress(localI); };
    var completeCallback:Function = function():void { Alert.show('callback'+localI.toString()); OnUploadComplete(localI); }; // localI == arrayQueue.length - 1 (when called)
    Alert.show(localI.toString()); // shows current i as expected
    lid.fileRef.addEventListener(Event.COMPLETE, completeCallback);
    lid.fileRef.addEventListener(ProgressEvent.PROGRESS, progressCallback);
    lid.fileRef.addEventListener(HTTPStatusEvent.HTTP_STATUS, errorCallback);
    lid.fileRef.addEventListener(IOErrorEvent.IO_ERROR, errorCallback);
    lid.fileRef.addEventListener(SecurityErrorEvent.SECURITY_ERROR, errorCallback);

    lid.fileRef.upload(url, 'File');
}

Any idea on how to pass in the index to my callbacks? .upload does not block.

+1  A: 

Passing additional parameters for your callbacks is possible via some kind of delegate function or closure. However it is often considered a bad practice. You may use event target property instead to determine your index based on FileReference.

Edit: here is a sample of using closures:

function getTimerClosure(ind : int) : Function {
    return function(event : TimerEvent) {
     trace(ind);
    };
}

for (var i = 0; i < 10; i++) {
    var tm : Timer = new Timer(100*i+1, 1);
    tm.addEventListener(TimerEvent.TIMER, getTimerClosure(i));
    tm.start();
}

This will continuously trace numbers from 0 to 9.

Edit2: here is a sample of creating a delegate based on a function closure:

function timerHandler(event : Event, ...rest) : void {
    trace(event, rest);
}

function Delegate(scope : Object, func : Function, ...rest) : Function {
    return function(...args) : void {
     func.apply(scope, args.concat(rest));
    }
}

var tm : Timer = new Timer(1000, 1);
tm.addEventListener(TimerEvent.TIMER, Delegate(this, this.timerHandler, 1, 2, 3));
tm.start();

However this is a bad approach since unsubscribing for such a listener is a hell pain. This in turn will probably cause some memory leakages, which will decrease overall performance of your application. So, use with caution!


Bottom line: if you know how to work with closures, use them - it is a wonderful thing! If you don't care about your application performance in a long perspective, use closures - it's simple!

But if you are unsure about closures, use a more conventional approach. E.g. in your case you could create a Dictionary that matches your FileReference objects to appropriate indices. Something like that:

var frToInd : Dictionary = new Dictionary(false);
// false here wouldn't prevent garbage collection of FileReference objects

for (var i : int = 0; i < 10; i++) {
    // blah-blah stuff with `lib` objects
    frToInd[lib.fileRef] = i;
    // another weird stuff and subscription 
}

function eventListener(event : Event) : void {
    // in the event listener just look up target in the dictionary
    if (frToInd[event.target]) {
     var ind : int = frToInd[event.target];
    } else {
     // Shouldn't happen since all FileReferences should be in 
     // the Dictionary. But if this happens - it's an error.
    }
}

-- Happy coding!

dragonfly
The `target` won't work for me because `target` will only get me the `FileReference`, which is not what I need.
Daniel A. White
But the `FileReference`s for different `lid`s are also different. At least you could store them in an Array and find necessary one in the callback.
dragonfly
Also I can bring up the delegate or closure sample. But it's a bad idea to use one.
dragonfly
Why is it a bad idea to use the closures here? I find your example perfectly fine.
Chetan Sastry
@Daniel, @Chetan: I added some explanation, delegate creation sample and proper solution to the question.
dragonfly
A: 

I have a weird quirk in ActionScript

It's not a quirk, it's variable scope. You should read this article: http://help.adobe.com/en_US/ActionScript/3.0_ProgrammingAS3/WS5b3ccc516d4fbf351e63e3d118a9b90204-7f9d.html#WS5b3ccc516d4fbf351e63e3d118a9b90204-7f8c

And you really shouldn't use anonymous, it just makes everything more confusing. You're actually making multiple copies of the same object.

If the arrayQueue is in scope, you can use this code to get the index:

GetArrayIndex(e.currentTarget);

function GetArrayIndex(object:Object):Number
{
    for(var i:Number = 0; 0 < arrayQueue.length; i++)
    {
        if(object === arrayQueue[i])
            return i;
    }
}

You should consider using an uint for the index.

Lillemanden