Binding variables in an anonymous function inside a for each loop - actionscript-3

The following code will add only the last Widget in the loop to all the listener functions. All the functions will call toggleShow() on the last object.
Why does this happen, and is there a way to get each Widget object to be referenced in the corresponding function?
for each (var w:Widget in workspace.getWidgetList()) {
var widgetShow:NativeMenuItem = menu.addItem(new NativeMenuItem("Show " + w.title));
widgetShow.addEventListener(Event.SELECT, function():void {
w.toggleShow();
});
}

Make it a function, this happens because the loop runs to the end and w becomes the last element in that list so when the anonymous function is called it always uses the last element :
for each (var w:Widget in workspace.getWidgetList()) {
addMenuItem(w);
}
function addMenuItem(w:Widget):void
{
var widgetShow:NativeMenuItem = menu.addItem(new NativeMenuItem("Show " + w.title));
widgetShow.addEventListener(Event.SELECT, function():void {
w.toggleShow();
});
}

Related

Create a set of anonymous functions with parameters defined at definiton time

I'm attempting to gradually refactor existing code. I have a set of functions that are defined, and only differ by one of the internal arguments:
function loadGame1():void
{
loadGame("save1");
}
function loadGame2():void
{
loadGame("save2");
}
function loadGame3():void
{
loadGame("save3");
}
//... snip many, many lines
// Note- I cannot pass function arguments at this time!
picker(loadGame1, loadGame2, loadGame3 ...);
I'm trying to refactor at least part of this (I can't completely replace the whole thing yet, too many interdependencies).
Basically, I want to be able to generate a big set of functions with the difference between the functions being a internal parameter:
var fNames:Array = new Array("save1", "save2", "save3");
var funcs:Array = new Array();
for (var i = 0; i < fNames.length; i += 1)
{
trace("Creating function with indice = ", i);
funcs.push(
function() : void
{
saveGame(fNames[i]);
}
)
}
picker(funcs[0], funcs[1], funcs[2] ...);
However, as I understand it, closure is causing the state of i to be maintained beyond the scope of the for loop, and any attempt to call any of the generated functions is failing with an out-of-bounds error, which is what you would expect given that i will reach fNames.size + 1 before i < fNames.size evaluates to false.
So, basically, given that I need to generate functions that are passed as arguments to a pre-existing function that I cannot change currently. How can I dynamically generate these functions?
Try to use IIFE:
for (var i = 0; i < fNames.length; i += 1)
{
(function(i){
trace("Creating function with indice = ", i);
funcs.push(
function() : void
{
saveGame(fNames[i]);
}
)
})(i);
}

AS3 Passing Variable Parameters to a generic Function Menu / SubItems

I'm no code genius, but a fan of action script.
Can you help me on this:
I have a function that depending on the object selected, will call event listeners to a set of 'sub-items' that are already on stage (I want to reuse this subitems with changed parameters upon click, instead of creating several instances and several code).
So for each selected 'case' I have to pass diferent variables to those 'sub-items', like this:
function fooMenu(event:MouseEvent):void {
switch (event.currentTarget.name)
{
case "btUa1" :
trace(event.currentTarget.name);
// a bunch of code goes here
//(just cleaned to easy the view)
/*
HELP HERE <--
here is a way to pass the variables to those subitems
*/
break;
}
}
function fooSub(event:MouseEvent):void
{
trace(event.target.data);
trace(event.currentTarget.name);
// HELP PLEASE <-> How can I access the variables that I need here ?
}
btUa1.addEventListener(MouseEvent.CLICK, fooMenu);
btUa2.addEventListener(MouseEvent.CLICK, fooMenu);
btTextos.addEventListener(MouseEvent.CLICK, fooSub);
btLegislacao.addEventListener(MouseEvent.CLICK, fooSub);
Anyone to help me please?
Thank very much in advance. :)
(I'm not sure I got your question right, and I haven't developed in AS3 for a while.)
If you want to simply create function with parameters which will be called upon a click (or other event) you can simply use this:
btUa1.addEventListener(MouseEvent.CLICK, function() {
fooMenu(parameters);
});
btUa2.addEventListener(MouseEvent.CLICK, function() {
fooMenu(other_parameters)
}):
public function fooMenu(...rest):void {
for(var i:uint = 0; i < rest.length; i++)
{
// creating elements
}
}
If you want to call event listeners assigned to something else you can use DispatchEvent
btnTextos.dispatchEvent(new MouseEvent(MouseEvent.CLICK))
Remember, you can't use btTextos.addEventListener(MouseEvent.CLICK, carregaConteudo("jocasta")); because the 2nd parameter you pass while adding Eventlistener will be considered as function itself - there are two proper ways to use addEventListener:
1:
function doSomething(event:MouseEvent):void
{
// function code
}
element.addEventListener(MouseEvent.CLICK, doSomething); //notice no brackets
2:
element.addEventListener(MouseEvent.CLICK, function() { // function code });
So:
function fooSub(event:MouseEvent, bla:String):void
{
trace(event.currentTarget.name+" - "+bla);
// bla would be a clip name.
}
codebtTextos.addEventListener(MouseEvent.CLICK, function(e:MouseEvent) { fooSub(e, "jocasta") } );
Or try something like this if you want content to be dynamically generated:
btUa1.addEventListener(MouseEvent.CLICK, function() {
createMenu(1);
});
btUa2.addEventListener(MouseEvent.CLICK, function() {
createMenu(2);
});
function createMenu(id):void
{
// Switching submenu elements
switch (id)
{
case 1:
createSubmenu([myFunc1, myFunc2, myFunc3]); // dynamically creating submenus in case you need more of them than u already have
break;
case 2:
createSubmenu([myFunc4, myFunc5, myFunc6, myFunc7]);
break;
default:
[ and so on ..]
}
}
function createSubmenu(...rest):void {
for (var i:uint = 0; i < rest.length; i++)
{
var mc:SubItem = new SubItem(); // Subitem should be an MovieClip in library exported for ActionScript
mc.addEventListener(MouseEvent.CLICK, rest[i] as function)
mc.x = i * 100;
mc.y = 0;
this.addChild(mc);
}
}
Your question is rather vague; what "variables" do you want to "pass"? And what do you mean by "passing the variable to a sub item"? Usually "passing" means invoking a function.
If you can be more specific on what exactly your trying to do that would be helpful. In the meantime, here are three things that may get at what you want:
You can get any member of any object using bracket notation.
var mc:MovieClip = someMovieClip;
var xVal:Number = mc.x; // The obvious way
xVal = mc["x"]; // This works too
var propName:String = "x";
xVal = mc[propName] ; // So does this.
You can refer to functions using variables
function echo(message:String):void {
trace(message);
}
echo("Hello"); // The normal way
var f:Function = echo;
f("Hello"); // This also works
You can call a function with all the arguments in an array using function.apply
// Extending the above example...
var fArgs:Array = ["Hello"];
f.apply(fArgs); // This does the same thing
Between these three things (and the rest parameter noted by another poster) you can write some very flexible code. Dynamic code comes at a performance cost for sure, but as long as the frequency of calls is a few hundred times per second or less you'll never notice the difference.

AS3 dispatchEvent in forEach

I've populated an array.
i need to take each item in that array , perform some calculations on that item push the result to the array then move on to the next item in the array and so on.
The calculations are made in a separate class. Then when the calculations are completed i dispatch an event and listen for the class to complete.
I'm using forEach but i need to pause the forEach function and wait for the dispatchEvent listener before continuing but i can't seem to get it working.
Tracing seems to suggest that the forEach is just running through the array and resetting the calculation class each time.
Here is the jist of my code. I populate the array from a sql table on my server then initiate forEach.
can anyone suggest a solution please:
function handleLoadSuccessful(evt:Event):void
{
evt.target.dataFormat = URLLoaderDataFormat.TEXT;
var corrected:String = evt.target.data;
corrected = corrected.slice(1,corrected.length-1);
var result:URLVariables = new URLVariables(corrected);
if (result.errorcode=="0")
{
for (var i:Number=0; i < result.n; i++)
{
liveOrderArray.push(
{
code:result["ItemCode"+i],
qty:Number(result["LineQuantity"+i]) - Number(result["DespatchReceiptQuantity"+i])
})
}
liveOrderArray.forEach(allocate);
} else {
trace("ERROR IN RUNNING QUERY");
}
}
function allocate(element:*, index:int, arr:Array):void {
trace("code: " + element.code + " qty:" + element.qty );
allocationbible.profileCode = element.code.substring(0,1);
allocationbible.finishThk = Number(element.code.substring(1,3));
allocationbible.longEdgeCode = element.code.substring(3,4);
allocationbible.backingDetailCode = element.code.substring(4,5);
allocationbible.coreboardCode = element.code.substring(5,6);
allocationBible = new allocationbible;
allocationBible.addEventListener("allocated", updateAllocationQty, false, 0, true);
trace("*************************************");
}
function updateAllocationQty (evt:Event):void {
//add result to array
trace(allocationbible.coreboardLongCode);
}
If you need to halt execution of a script to wait for a function to finish then dispatching events is not how you want to do it. What you want to do is the function you call return the value you are waiting for and do not use events at all.
Could probably help more if I knew what you were doing in allocationbible
You cannot pause for..each loop, it is not possible in AS3. So you need to rewrite your code.
UPD: misunderstood your question last time. To wait upon the completion of calculations before further processing you can start processing of the next element in the allocation event handler. Something like this:
var currentItemIndex:int;
function startProcessing():void {
// population of the array
// ...
allocationBible = new allocationbible();
allocationBible.addEventListener("allocated", onAllocated);
currentItemIndex = 0;
allocate();
}
function allocate():void {
var element:* = liveOrderArray[currentItemIndex];
// configure allocationBible
allocationBible.process(element);
}
function onAllocated(e:Event):void {
trace("Allocated: " + allocationbible.coreboardLongCode);
// allocate the next item
currentItemIndex++;
if (currentItemIndex >= liveOrderArray.length) {
allocationBible.removeEventListener("allocated", onAllocated);
} else {
allocate();
}
}

How to Getting the Variable from Function in as3 without using get ,set method?

Hai am Getting trouble to retrive the values from function(addText).i Called from another function onFullScreen().I dont know how Can i do this,Kindly Help me?Here i attach my Code
private function addText()
{
nc = new NetConnection();
nc.addEventListener(NetStatusEvent.NET_STATUS, ncOnStatus);
function ncOnStatus(infoObject:NetStatusEvent)
{
trace("nc: "+infoObject.info.code+" ("+infoObject.info.description+")");
if (infoObject.info.code == "NetConnection.Connect.Success")
{
initSharedObject(chatSharedObjectName);
}
}
function formatMessage(chatData:Object)
{
trace("room"+chatData.message);
number = chatData.txtalign;//i want to retrive the value of number
number.toString();
return number;
}
function syncEventHandler(ev:SyncEvent)
{
var infoObj:Object = ev.changeList;
// if first time only show last 4 messages in the list
if (lastChatId == 0)
{
lastChatId = Number(textchat_so.data["lastChatId"]) - 1;
if (lastChatId < 0)
lastChatId = 0;
}
}
function connectSharedObject(soName:String)
{
textchat_so = SharedObject.getRemote(soName, nc.uri)
// add new message to the chat box as they come in
textchat_so.addEventListener(SyncEvent.SYNC, syncEventHandler)
textchat_so.connect(nc)
}
function connectSharedObjectRes(soName:String)
{
connectSharedObject(soName)
trace(soName)
}
function initSharedObject(soName:String)
{
// initialize the shared object server side
nc.call("initSharedObject", new Responder(connectSharedObjectRes), soName)
}
}
i using the variable in another function ,but I cannot retrive the Value.
private function onFullScreen(event:FullScreenEvent):void
{
mediaContainer.addMediaElement(alert);
alert.alert("Error",number);// if i cannot retrive the value hnumber here
}
The addText() method is asynchronous, meaning that you can't simply call it , you need to wait for the event listener to return a value.
I'm not sure why you would feel the need to enclose all these functions, it's not very legible and I doubt it's necessary. You're also missing quite a few semi colons...
In any case , I couldn't see where the formatMessage() method was called, it seems that's the only place where the "number" variable gets defined.
You could create a variable outside the scope of the functions.
private var num:int;
Then in your addText function, assign a value to the variable:
num = infoObject.info.code;
Then in your onFullScreen function, access the num variable:
alert.alert("Error", num);

as3 non-explicit function pointer

Please see code below. I will have a bunch of elements, that I want to run whatever "formula" refers to, for that element. In the code, where it says, "this works", it works as expected. However I need to fire off these formulas, without naming "firstElement" explicitly. Even though the nested for loop is a little clunky, I think it should work, but it causes the error listed below. How can I fire off the formulas, without naming the elements explicitly? Thanks!
var test:Object = {
element:
[
{ "firstElement":
{
formula:myFunction
}
}
]
}// end test object
public function RunThisFunctionFirst() {
test.element[0].firstElement.formula();//this works
for (var index in test.element){
for (var object in test.element[index]){
trace ("object " + object);// traces "firstElement", as expected
object.formula()// this causes error: Error #1006: value is not a function.
}
}
}
function myFunction (){
trace ("my function called");
}
Using a for each loop you can simplify your loop, and as previously said don't forget to typed your variable :
for each (var elm:Object in test.element) {
for each (var obj:Object in elm) {
var formula:Function = obj.formula as Function
if (formula!=null) formula()
}
}
Your variable object, in (var object ... ) is not a typed variable. The compiler will default this to an Object class, which of course is not a Function class. Try casting object as a Function. I'm guess that you have extended myFunction from Function class.
either by:
for (var object:Function in test.element[index])
or
for (var object:myFunction ... ) // if myFunction is extended from Function
Regarding the outer loop, element is an array, not an object, so you want to use for(;;) not for in.
Regarding the inner loop, object is the string "firstElement" not an object.
for (var i:int=0; i < test.element.length; i++)
{
for (var key:* in test.element[i])
{
trace("key " + key);
var object:* = test.element[i][key];
trace("object " + object);
if(typeof object === "object" && object.hasOwnProperty("formula"))
object.formula();
}
}