I have this:
for (var i:int = 0; i < 3; i++) {
var newChoice:MainButton = new MainButton(function(){
trace(this["func" + i])} );
}
public function func0 ...
public function func1 ...
public function func2 ...
(When clicked, MainButton calls the function in the argument)
However, I get func3, which I assume is do to it finding the value of i. But shouldn't it pass by value since it's a number? How do I get the wanted result? Thanks
You're not passing anything, except the function itself (which is passed by reference).
What's happening is that the function creates a closure around the variable i, changing its lifetime. When the anonymous function is called, i is still in its original scope, but the loop has already finished, leaving i at 3.
So, the closure is essentially keeping i in the scope of the function even after the original, declaring function has finished.
Instead of closing over the variable, you wanted to close over the variable's value at the time the function is created. You can achieve this with an intermediate variable that's set only once before being closed over:
for (var i:int = 0; i < 3; i++) {
var j = i; // New variable each time through the loop; closure will close over a different variable each time (that happens to have the same name)
var newChoice:MainButton = new MainButton(function(){
trace(this["func" + j])} );
}
Related
Basically I have 2 movieclip objects with some code, currently just to trace them. The blue circles when clicked will say 'Blue' and the red ones when clicked will say 'Red'. This works fine in theory until I add a population loop, which adds more of them. Then only 1 of each colour correctly works, the rest are just 'mock' circles. I wish for each circle to tell me their colour.
This is my code for the .fla:
import flash.events.MouseEvent;
BlueBall.addEventListener(MouseEvent.CLICK, fun1)
function fun1(e:MouseEvent){
trace("Blue!");
}
RedBall.addEventListener(MouseEvent.CLICK, fun2)
function fun2(e:MouseEvent){
trace("Red!");
}
and this is the population loop in an .as file:
private function PopulateCircles():void
{
for (var i:int=0; i < 10; i++)
{
var blueCircle:BlueCircle = new BlueCircle();
this.addChild(blueCircle);
var redCircle:RedCircle = new RedCircle();
this.addChild(redCircle);
}
}
tldr; how do I get the on-click events to occur on every newly populated circle?
Pretty easy, actually. Just as you subscribe method to listen the predesigned instances' events, you can subscribe via temporary variable references. As long, as the variable holds the reference (or a pointer in C++ terms), you can address the instance and do anything you could do to a predesigned MovieClip:
private function PopulateCircles():void
{
var aRed:RedCircle;
var aBlu:BlueCircle;
for (var i:int = 0; i < 10; i++)
{
// If there are no mandatory constructor arguments,
// you can omit the () brackets.
aRed = new RedCircle;
aBlu = new BlueCircle;
// Disperse clips to random places.
aBlu.x = 500 * Math.random();
aBlu.y = 500 * Math.random();
aRed.x = 500 * Math.random();
aRed.y = 500 * Math.random();
// Subscribe methods to newly created instances.
aRed.addEventListener(MouseEvent.CLICK, fun2);
aBlu.addEventListener(MouseEvent.CLICK, fun1);
// You're operating inside 'this' object,
// no need to explicitly point it out.
addChild(aRed);
addChild(aBlu);
}
}
This seems simple in other languages, but I don't understand the error. I have 7 buttons that I would like to each take my gallery movieclip to a certain frame when you click on them.
Error: 1067: Implicit coercion of a value of type int to an unrelated type flash.events:MouseEvent.
Error: 1136: Incorrect number of arguments. Expected 2.
Error: 1067: Implicit coercion of a value of type void to an unrelated type Function.
Any help?
function gotoImage(event:MouseEvent, frameParam:int):void
{
MovieClip(this.root).gallery.gotoAndStop(frameParam);
}
t1.addEventListener(MouseEvent.CLICK, gotoImage(1));
t2.addEventListener(MouseEvent.CLICK, gotoImage(2));
t3.addEventListener(MouseEvent.CLICK, gotoImage(3));
t4.addEventListener(MouseEvent.CLICK, gotoImage(4));
t5.addEventListener(MouseEvent.CLICK, gotoImage(5));
t6.addEventListener(MouseEvent.CLICK, gotoImage(6));
t7.addEventListener(MouseEvent.CLICK, gotoImage(7));
You've got two things off with your code:
First, in ActionScript, event handlers always have the same signature:
function someHandler(e:Event):void { .. }
Sometimes the Event argument is a more specific subclass of Event, such as MouseEvent, but there is always just one argument.
The addEventListener method needs a function itself, not the result of invoking a function.
// Here's a function:
function multiply(i1:int, i2:int):int { return i1 * i2; }
// Here's assigning the result of **invoking** a function:
var result:int = multiply(2,3);
// Here's assigning a **function itself** to a variable:
var f:Function = multiply;
// You can invoke the function via the variable f in two different ways:
var result1 = f(2,3);
var result2 = f.apply(null, [2,3]);
So, you'll need to change your code to follow the above to points. You'll have to associate the buttons with jumping to a specific frame one of two ways:
Simple but repetitive: Use a separate handler for each button, with the frame hard coded into each handler.
1a. Named functions (most verbose):
function onT1Click(e:MouseEvent):void {
MovieClip(this.root).gallery.gotoAndStop(1);
}
t1.addEventListener(MouseEvent.CLICK, onT1Click);
// etc. etc.
1b. Anonymous functions:
t1.addEventListener(MouseEvent.CLICK, function(e:Event):void {
MovieClip(this.root).gallery.gotoAndStop(1);
});
// etc. etc.
More elegant: Use the same handler, and store the association between button and frame elsewhere, such as in a Dictionary. If you stick with your naming convention you could even fill the Dictionary in a for loop getting the buttons by name:
var buttonToFrame:Dictionary = new Dictionary();
for(var i:int = 1; i < 8; i++) {
var btn:Button = this["t" + i.toString()];
buttonToFrame[btn] = i;
btn.addEventListener(MouseEvent.CLICK, onClick);
}
function onClick(e:MouseEvent):void {
var btn:Button = Button(e.currentTarget);
var frameNum:int = buttonToFrame[btn];
MovieClip(this.root).gallery.gotoAndStop(frameNum);
}
Just change this
t1.addEventListener(MouseEvent.CLICK, function(me:MouseEvent):void{ gotoImage(me, 1)});
t2.addEventListener(MouseEvent.CLICK, function(me:MouseEvent):void{ gotoImage(me, 2)});
and so on...
This is possible with a roundabout approach. For the event handler, use a function that returns a nested anonymous function.
private var textFieldA:TextField = new TextField;
private var textFieldB:TextField = new TextField;
public function setParameterizedTextWhenTextFieldsAreClicked ():void {
addChild(textFieldA);
textFieldA.text = 'Text field A';
textFieldA.addEventListener(MouseEvent.CLICK, showCustomMessage("One"));
addChild(textFieldB);
textFieldB.text = 'Text field B';
textFieldB.y = 20;
textFieldB.addEventListener(MouseEvent.CLICK, showCustomMessage("Two"));
// NOTE: We must use strongly referenced listeners because weakly referenced
// listeners **will get garbage collected** because we're returning
// an anonymous function, which gets defined in the global namespace and
// thus, the garbage collector does not have anything pointing to it.
}
private function showCustomMessage (message:String):Function {
// NOTE: You can store the following function to a class variable
// to keep it in memory, which would let you use weakly referenced
// listeners when using this as an event handler. Many people
// would find that awkward. I would discourage that.
return function (e:MouseEvent):void {
var textField:TextField = e.target as TextField;
textField.text = message; // "message" argument is available because
// this function's scope is kept in memory.
}
}
Bear in mind, the use of anonymous functions and reliance on function scope being kept in memory seem to present garbage collection complications.
I am trying to loop through my 10 "bomb" movieclips and assign an eventlistener that calls the same function but passes the current movieclips name. the "bomb" movieclip names are incremented.
Below is my try
var i:number;
i=0;
while (i <= 10){
var current_bomb:Movieclip = (movingbomb_+i);
current_bomb.addEventListener(MouseEvent.ROLL_OVER, function updateBomb(current_bomb));
i++
}
function updateBomb(currentBomb):void{
currentBomb.gotoAndPlay(2);
}
Close, but not quite. Also, using a for loop is a much better idea here. With those changes, the code should look something like this:
for (var i:int = 0; i < 10; i++) {
var currentBomb:MovieClip = this["movingbomb_" + i];
currentBomb.addEventListener(MouseEvent.ROLL_OVER, function (evt:MouseEvent):void { updateBomb(currentBomb); });
}
function updateBomb(currentBomb:MovieClip):void {
currentBomb.gotoAndPlay(2);
}
Here's how this works.
The for loop simplifies all your while looping code into a single statement for efficiency.
We then select the current bomb using array-bracket selection syntax. To do this, you use this[name], where name is a string. The string we use will be "movingbomb_" with i tacked on to the end.
Finally, we create a unique anonymous function for each new event listener which redirects the mouse event to your updateBomb function and passes the currentBomb object.
So I've worked out a great way to create a pixelized wipe effect, with movie clips of pixels nested in rows. I've created a loop that goes through each row, and then another loop inside that loop for the pixels in each row. I then use a random number between 1-0 for for the delay before it alpha-ups the pixel. I used Greensock for the tween and it works great.
One problem, I can't stall the for loop for the rows, and every row comes up at once. Still, the pixels are staggered, and this is a nice effect, but I want it to move from left to right. So my code looks like this: (BTW, yes, AS3 does have setTimeout and it works fine)
function stripeWipeUp(stripe:MovieClip):void {
var total = stripe.numChildren;
for (var i:int = 0; i<total; i++) { // the rows
trace(i);
setTimeout(function() {
trace("setTimeout: "+i);
var row:MovieClip = stripe.getChildAt(i) as MovieClip;
var pixTotal = row.numChildren;
for (var j:int = 0; j<pixTotal; j++) { the pixels
var pix:MovieClip = row.getChildAt(j) as MovieClip;
var num = Math.floor(Math.random()*100)/100; // the delay
pixUp(pix, num); // my greensock function
}
}, 500);
}
}
What happens, with the setTimeout in there, is that the "i" for loop hits every row (there are 27) before one setTimeout gets finished. So, it doesn't work as it should - the next loop should not execute until the setTimeout is finished. Any idea how to accomplish this so each row gets stalled by about 500 ms? Thanks.
Your problem is with this.
trace("setTimeout: "+i);
"i" is not what you think it is.
The scope of "i" is in stripeWipeUp not the anonymous function.
With that being said "i" is always the value of the last itteration of the loop that was run across all of your anonymous functions.
So as you can see when the anon functions trigger "i" will most-likely be equal to "total".
This is why we avoid using anonymous functions as scope is very hard determine.
Create an array and store a custom class in it for each "pixel" that will control what you want to do.
In this class use a Timer Object not setTimeOut. setTimeOut is a garbage function left over from AS2.
drop the setTimeout and just add a delay multiplied with i
var num = ( i * 500 ) + Math.floor(Math.random()*100)/100; // the delay
I would encapsulate i and j, a Timer, and an event handler for the Timer, into a class. In the event handler I would:
Do your effect for the pixel corresponding to j and i
Update j (and i)
If there are more pixels, schedule the next event
I was able to get it to work by taking the inner part out and making it a separate function. I would prefer a much cleaner solution like Creynders', but for some reason that didn't work. Here is the code:
// the row of pixels
function row(stripe:MovieClip, i:int, up:Boolean, del:Number):void {
setTimeout(function() {
var row:MovieClip = stripe.getChildAt(i) as MovieClip;
var pixTotal = row.numChildren;
for (var j:int = 0; j<pixTotal; j++) {
var pix:MovieClip = row.getChildAt(j) as MovieClip;
var num = Math.floor(Math.random()*100)/100;
(up) ? pixUp(pix, num) : pixDown(pix, num);
}
}, del);
}
// the rows in the "stripe"
function stripeWipe(stripe:MovieClip, up:Boolean):void {
var total = stripe.numChildren;
var del:Number = 0;
for (var i:int = 0; i<total; i++) {
row(stripe, i, up, del);
del = del+100;
}
}
Is there a way to generically remove an object from an array?
(maybe not using array.filter or creating a new array)
Example:
var arr:Array= new Array();
//create dummy objs
for (var i:uint=0; i < 10; i++){
var someObject:SomeClassObject = new SomeClassObject();
someObject.Name ="Amit"+ i;
someObject.Site="http://www.mysite.com/"+i;
//...many more props
arr.push(someObject);
}
//
removeElement("Amit4",arr);
removeElement("Amit8",arr);
//...so on so forth
Currently im using array.splice() to remove object
for (var i:Number=0; i < arr.length; i++)
{
if (arr[i].Name == element)
{
arr.splice(i, 1);
}
}
I want to write removeElement in such a way that i can use it for different
types of objects.
currently removeElement becomes dependant on implmentation..
Suppose if i want to remove a file from array of files given file name..i wud have to
again write "removeElement" by changing criteria.
Also may be i can vary the criteria varing criteria?
example :
arr= removeElement("Site","http://www.mysite.com/6",arr)
will remove object from arr whose "Site" property is equal to "http://www.mysite.com/6"
(using above example)
ie. removeElement(criteria:object,criteria_value(s):object,arr)
Thanks All.
Use
if(array.indexOf(obj) != -1)
array.splice(array.indexOf(obj),1);
I think the most flexible approach is the one followed by Array::filter. It's up to the caller to determine whether an item should be filtered out of the list or not, through a function callback.
Now, if you want to do it in place, you could write a simple function like this:
function remove(list:Array,callback:Function):Array {
for(var i:int = list.length - 1; i >= 0; i--) {
if(!callback(list[i])) {
list.splice(i,1);
}
}
return list;
}
This returns the list, as it could be convenient if you wanted to chain calls, but it acts on the array you passed instead of creating a new one.
Also note that it loops backwards. Otherwise, splice will get you bogus results.
You could use it like this:
var arr:Array = [1,2,9,10,455];
trace(arr);
function removeCallback(item:Number):Boolean {
return item < 10;
}
remove(arr,removeCallback);
trace(arr);
This way you are not restricted to equality (or inequality). The caller determines if the item should be kept or removed, by returning true or false respectively (to match filter). So, it's pretty much like filter, except it works in-place. If you want, you could also keep the same interface for the callback (passing the index of the item and a reference to the original array) to make it more coherent.
By the way, you can use strings as indices for an array, and then you can safely use the 'delete' keyword to delete an object from inside the "middle" (there's actually no "middle" in this situation :) of the array.
e.g.:
var arr:Array = new Array();
arr['o1'] = new Object();
arr['o1'].someproperty = true;
arr['o2'] = new Object();
arr['o2'].someproperty = true;
arr['o3'] = new Object();
arr['o3'].someproperty = true;
trace (arr['o2'].someproperty);
//Returns 'true'
trace (arr['o2']);
//Returns '[object Object]'
delete arr['o2'];
trace (arr['o2']);
//Returns 'undefined'
trace (arr['o2'].someproperty);
//Returns 'TypeError: Error #1010: A term is undefined and has no properties.'
The disadvantage is you won't be able to know the length of the array (arr.length will return 0), but you can of-course track it yourself...
Here is a generic function which will do what you want:
public static function removeItem(array: Array, propertyName: String, value: String): Array
{
var newArray: Array = [];
for (var index: int = 0; index < array.length; index++) {
var item: Object = array[index];
if (item && item.hasOwnProperty(propertyName)) {
if (item[propertyName] != value)
newArray.push(item);
}
}
return newArray;
}