Bubble click event in swf - actionscript-3

I try to add a MovieClip to an existent SWF on the fly - inject an small code who do something like:
this.obj = new MovieClip(); // it is inside an object
obj.name = 'FLOOR';
obj.graphics.beginFill(0xFFFFFF, 0);
obj.graphics.drawRect(0,0,self.width, self.height);
obj.graphics.endFill();
obj.buttonMode = true;
self.addChildAt( floorLayerMC , 0); /* self is an reference for the this keyword, reference for the entire swf */
My question is: this SWF has many elements like images and textfields, and some of this elements has no event handler for click. I Need to find a way to "redirect" all of the events to my "FLOOR" element, using something like bubbling the event.
Of course, I can add the FLOOR in top of any elements BUT I have some elements with click handler. I can't ignore all of the elements. So my problem is:
if I click over an MovieClip with click handler, perform the original action.
if I click over an MovieClip without click handler, perform the FLOOR action.
I can't add a event handler in all of the elements.
Any Idea?

Listen for a click on the container movieclip's own stage (the movieclip that contains the FLOOR). In the handler method for the click event, do a hit test using hitTestPoint with the mouseX and MouseY of the container movieclip, and if the mouse is over any clickable objects, ignore the stage click. Store all the objects that are clickable in an array to do that test.
This code is untested but it would go something like this:
var exemptArray:Array = [ btn_mc1, btn_mc2, btn_mc3 ];
containerMC.stage.addEventListener(MouseEvent.CLICK, onClickMyMC);
function onClickMyMC( event:Event ):void
{
for(var i:int = 0; i < exemptArray.length; i++)
{
if( exemptArray[i].hitTestPoint(containerMC.mouseX, containerMC.mouseY) )
{
// do nothing, ignore the stage click ( and let the object with the click respond )
break;
}
else
{
// respond to the stage click
}
}
}
To build the exemptArray without knowing what objects are clickable ahead of time:
( untested but should be close enough to give you an idea ).
var exemptArray:Array = buildExemptArray();
function buildExemptArray():Array
{
var arr:Array = [];
for(var j:int = 0; j < containerMC.numChildren; j++)
{
if( containerMC.getChildAt(i).hasEventListener(MouseEvent.CLICK) )
{
arr.push( containerMC.getChildAt(i) );
}
}
return arr:
}
EDIT TO ANSWER QUESTION IN COMMENTS:
this.addEventListener(MouseEvent.CLICK, onClick) will add a click event to the whole object, children included.
this.stage.addEventListener(MouseEvent.CLICK, onClick) will add a click only to the movieclip's stage, not its children as well.
In as3 all movieclips have a stage property. If you wrote on the main timeline this.stage.addEventListener(MouseEvent.CLICK, onClick); that would be adding a stage click to the whole swf. But, if you wrote something like myMC.stage.addEventListener(MouseEvent.CLICK, onClick); it would only add a click to that movieclip's stage (myMC's stage). Since stage is below the display list, you can capture a click there in any movieclip. If you don't have access to all the objects that have mouse events ahead of time, you could loop through all the container's children and check if they have a mouseEvent with .hasEventListener(MouseEvent.CLICK);, create your exemptArray from that, then use the same logic above to ignore items in the exemptArray.

Related

Select "youngest" child (target only) under the mouse in AS3

tldr:
how does one tell a mouse event to only fire for the youngest child under the mouse, and not its parent?
I'm just making a solitaire game to try to better learn OOP. I have a Card class and a CardHome class. Each Card contains a CardHome which can hold a card, and so on. Thus, moving a card will move all the cards contained in it. The problem is that my mouse event is detecting the parent of all the cards. So what I think I need is a way to say "run this mouse event only for the youngest child of the target". Any way to do that?
private function pressCard(me:MouseEvent):void{
var c:Object = me.target as Card;
// place card in center of container x wise
c.x = 0;
// and down 5
c.y = 5;
// bring drag container up to front z order
addChild(c as Card);
// begin dragging drag container
c.startDrag(true);
// hide mouse
Mouse.hide();
// add listener to this card for mouse up
c.addEventListener(MouseEvent.MOUSE_UP,dropCard);
}
private function dropCard(me:MouseEvent):void{
var c:Object = me.target as Card;
// release drag container
c.stopDrag();
// remove mouse up listener from card
c.removeEventListener(MouseEvent.MOUSE_UP,dropCard);
Mouse.show();
// check for colision with a card that has a matching holder
for (var i:int = 0; i < deckArray.length; i++){
if (c.hitTestObject(deckArray[i]) && c != deckArray[i]){
trace("hit",deckArray[i]._containerOwned._occupied,deckArray[i]._flippable);
if (deckArray[i]._number == c._number + 1 && deckArray[i]._flippable == false && deckArray[i]._suit != c._suit && deckArray[i]._containerOwned._occupied == false){
c._containedIn.parent._containerOwned._occupied = false;
makeFlippable(c._containedIn.parent);
deckArray[i]._containerOwned.addChild(c as Card);
c._containedIn = c.parent;
c.x = 0;
c.y = 0;
break;
}
}
}
// add selected card back to home spot
c._containedIn.addChild(c);
c.x = 0;
c.y = 0;
}
private function mOver(me:MouseEvent):void{
trace(me.target._number);
trace(getHighestZ());
}
private function getHighestZ():Card{
var highestZ:int = 0;
var c:Card;
for (var i:int = 0; i < deckArray.length; i++){
if (deckArray[i].hitTestPoint(stage.mouseX,stage.mouseY) && deckArray[i].getChildIndex() > highestZ){
c = deckArray[i];
highestZ = deckArray[i].getChildIndex();
}
}
return c;
}
Each card is a child of a container in the card above it. I want to be able to select a card and drag it (and its children). The problem is that when I click card B, it selects card E. So what I need is to select the youngest child in the mouse event, and NOT its parent. Any way to control that?
I know I can do mouseChildren true or false. That's fine. It has to be set to true for all of the cards so that I can select them independent of their parent card. Do I need to do something with bubbling my event? That's something I've never understood.
Note: I realize there are problems with mouse mouse over function. Fixing that will be trivial and is kind of a placeholder for now.
tldr:
use a combination of useCapture set to true and the stopPropagation method. This allows a user to click an element and have the eventHandler only called for the target, not any of the elements between the target and the stage!
So the key here is to access the as3 event flow. When a mouse event gets triggered it propagates from the stage up through all objects that can receive that type of mouse event. In this case, the cards parents. Then it bubbles back down to the stage. So what I needed was to set the capture setting to true which switches the phase in which the event handler is used to the bubbling phase (this causes the first element to actually call the function to be the actual target) and then, instead of letting the event propagate back down to the stage, call the stopPropagation method. Looks something like this:
c.addEventListener(MouseEvent.MOUSE_OVER, mOver, true);
The true here says to fire the function on each element on the way back to the stage from the target. Then during the mOver function I do:
stopPropagation();
trace(c._Number);
// and any other code to do on this target
Which keeps the event from flowing back down through the other cards under the mouse.
Now instead of an output like
5
3
9
when the mouse is over the 9 card (which is over a 3 which is over 5) I now get simply
9
and then when I mouse over the 3 I get
3
So now the mouse is calling a function for the target only. Surprisingly non intuitive but allows for some serious flexibility and control!
from Adobe on Event Flow

Add event listener to the items in an array

I have an array with movieclips (r1, r2 etc) which are placed on stage dynamically every time there is a hittestobject. I want to add event listener to those movieclips in the array so that every time there is a click on them the last item to be removed. I have this code but it seems to be wrong because only the first element of the array is remove. Can you please help me?
var counter:int = 0;
function releaseToDrop(e:MouseEvent):void
{
Star(e.target).stopDrag();
if (Star(e.target).hitTestObject(target))
{
removeChild(Star(e.target));
var replace:Array = [r1,r2,r3,r4,r5,r6,r7,r8]
addChild(replace[counter]);
counter = counter+1;
}
else
{
trace("No collision.");
removeChild(Star(e.target));
}
}
replace[counter].addEventListener("click", bindClick(o));
function bindClick(o)
{
replace.splice(replace.indexOf(replace[counter]),1);
trace(counter);
}
For one, your bindClick function is inside the releaseToDrop function. This way that function will not be called. Also, try to use the static refence for the click event, so MouseEvent.Click instead of "click".
For your convenience, trace the current state of "counter" in the bindClick function, so you know what element will be removed. You will probably then find out where the flaw is in your logic.
A wise thing to to is also to check counter is not outside the scope of your array. So in bindClick, check for counter < replace.length. (for that the replace array of course has to be defined outside of the function scope of releaseToDrop.
Hope this gets you working in the right direction.
For future references adding addEventListener to the elements of an array so that every time there is a click on them the last item to be removed can be done by this code:
import flash.display.Sprite;
var replace:Array = [r1,r2,r3,r4,r5,r6,r7,r8]
var counter:int = 0;
// Every time there is a hittestobject an element of the array is placed on stage
function releaseToDrop(e:MouseEvent):void
{
Star(e.target).stopDrag();
if (Star(e.target).hitTestObject(target))
{
removeChild(Star(e.target));
addChild(replace[counter]);
counter = counter+1;
trace(counter);
}
}
// The first element of the array has an eventListener and when is clicked the last item of that array shown on stage is removed
var yourSprite:Sprite=new Sprite();
replace.push(yourSprite);
replace[0].addEventListener(MouseEvent.CLICK, myClickHandler, false, 0, true);
function myClickHandler (e:MouseEvent):void
{
counter =counter-1;
if (contains(replace[counter]))
removeChild(replace[counter]);
trace(counter);
}
Can you add an event listener to every movie clip that you spawn, then in each event handler, add a conditional to check if the clicked movie clip is the last item, then remove it (or do whatever you need to to it).
You should also accept Chicken's advice.

Making multiple objects draggable

I have about 50 symbols that I want to make draggable. Nothing fancy, just the ability to click it and drag it to a different location.
I found as3 code for doing so but when I paste it into my file it gives me errors:
**Error** Scene=Scene 1, layer=Units, frame=1:Line 9: The class or interface 'MouseEvent' could not be loaded.
function mouseDownHandler(evt:MouseEvent):void {
That code is:
// Register mouse event functions
fighter_uk.addEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler);
fighter_uk.addEventListener(MouseEvent.MOUSE_UP, mouseUpHandler);
fighter_uk.addEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler);
fighter_uk.addEventListener(MouseEvent.MOUSE_UP, mouseUpHandler);
// Define a mouse down handler (user is dragging)
function mouseDownHandler(evt:MouseEvent):void {
var object = evt.target;
// we should limit dragging to the area inside the canvas
object.startDrag();
}
function mouseUpHandler(evt:MouseEvent):void {
var obj = evt.target;
obj.stopDrag();
}
I'm using flash pro 8, so I tried finding as2 code but couldn't find it.
Also, is there an 'easy' way to code all 50 objects?
I think you're trying to compile AS3 code with AS2 compiler. Try changing your compilation settings to target AS3.
Also you may need to include the class import at the top of your code:
import flash.events.MouseEvent;
To drag 50 objects, add them all on the same container sprite and add the listener to the container sprite only:
var holder:Sprite = new Sprite();
for ( var i:int = 0, l:int = 50; i < l; i++ ) {
var dragee:YOUR_CUSTOM_OBJECT = new YOUR_CUSTOM_OBJECT();
holder.addChild(dragee);
}
addChild(holder);
holder.addEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler);
holder.addEventListener(MouseEvent.MOUSE_UP, mouseUpHandler);
holder.addEventListener(Event.MOUSE_LEAVE, mouseUpHandler);
var currentDragee:YOUR_CUSTOM_OBJECT = null;
function mouseDownHandler(evt:MouseEvent):void {
currentDragee = evt.target as YOUR_CUSTOM_OBJECT;
if ( currentDragee !== null ) {
currentDragee.startDrag();
holder.addChild(currentDragee); // bring current to front position
}
}
function mouseUpHandler(evt:Event):void {
if ( currentDragee !== null ) currentDragee.stopDrag();
currentDragee = null;
}
YOUR_CUSTOM_OBJECT being the object class you need to drag. Hope it helps!
This page seems to have the answers you are looking for (AS2 drag and drop). If you've already seen it, you'll need to explain why it's not good enough for your needs.
If you want to drag/drop multiple instances in AS2, you can still add the code to the movieClip symbol, export it from the library and load the instances up using attachMovie (all 50 of them). If they are all different, then attach the code as necessary to the clips themselves, or to some function elsewhere that will capture all the clicks and decide what was clicked. This is all very doable in AS2.
Remember you can use your onClipEvent(load) function to set up a lot of the initial lifting.
Here's a sample I made in AS2 for making a node tree. It's all draggable (mouse drag) and zoomable (with mouse Wheel). You can add nodes by clicking on the little down arrow in the node box. Each node is listening for the mouse.
You'll want to look at this section for the most part:
// Enable drag on button press
on (press)
{
startDrag(this);
}
// Stop the drag on release of mouse button
on (release)
{
stopDrag();
}
Besides this, I'm not really sure how your setup looks, so I hope this helps get the ball rolling. (Check the link, there's lots of little gems in there).
Flash Professional 8 only supports ActionScript 2 & 1
You can follow this official URL and learn how to do that in ActionScript 2, but I extremely recommend you to work with ActionScript 3.

Turn image array images to buttons AS3

I'm creating an app with a search function. I display the images by loading from the array the one's which match the search criteria. All the images are loaded from the library. I want to be able to click on an image as though it were a button. Once I click I want to goto frame 3 and change a variable integer to say which image was clicked on so that I can display the information about the photo in frame 3. Can I do this using an event listener say
imagesArray[i].addEventListener(MouseEvent.CLICK, imageClick);
function imageClick(event:MouseEvent):void
{
gotoAndStop(3);
current = i;
}
or similar,
Thanks
Yes, but it won't be as easy. First, Bitmaps do not process events, so you can't assign a listener directly to a Bitmap object. Next, there is no "i" available in such a construction, you have to determine that "i" by yourself. To do that, you parse event.target property, which is the object that's been clicked. You wrap each Bitmap object into a separate Sprite object, assign listeners to these sprites, then you parse event.target to get the relevant object reference out of it, grab the index via indexOf() call, and assign it to global current variable.
for (i=0;i<imageArray.length;i++) {
var sp:Sprite=new Sprite();
sp.addChild(imageArray[i]);
// position "sp" correctly here
addChild(sp);
sp.addEventListener(MouseEvent.CLICK, imageClick);
}
function imageClick(e:Event):void {
var content=e.target.getChildAt(0); // the object that was wrapped
var i:int=imageArray.indexOf(content);
if (i==-1) return; // OW, out of array
current=i;
gotoAndStop(3);
}
I'm not so sure that setting "current" to "i" would work as the function wouldn't be running at the same time as the for loop.
I'm assuming that because you're using the [i] that you've added the event listeners in a for loop?
What you could do is:
for (var i:int = 0; i < (howeverManyTimesToRun); i++)
{
imagesArray[i].addEventListener(MouseEvent.CLICK, imageClick);
imagesArray[i].myIndex = i;
}
And then in your function (outside of the for loop)
function imageClick (e:MouseEvent) {
gotoAndStop(3);
current = e.currentTarget.myIndex;
}
Also in your for loop you might want to add
imagesArray[i].buttonMode = true;
To change the mouse cursor to a hand when their mouse goes over your image.

How to apply action to mulitple flash layers

I have 5 layers with symbols on each: a, b, c, d and e.
I am trying to work out how to apply the action bellow to a, c, d and e when you hover over b.
Also is there another action similar to ' gotoAndStop(0); ' that instead of going immediately to frame 0 it goes back the way it came?
Link to .Fla http://www.fileden.com/files/2012/11/27/3370853/Untitled-2.fla
stop();
stage.addEventListener(MouseEvent.MOUSE_OVER, playMovie); function playMovie(event) { play(); }
stage.addEventListener(MouseEvent.MOUSE_OUT, stopMovie); function stopMovie(event) { gotoAndStop(0); }
stop();
Thanks
EDIT
After looking at your .fla, here is what is missing/misplaced:
Layers in flash don't mean anything other than z-order/depth. You cannot manipulate a layer in code. All your animations are on the same timeline, so they will always play together. If you want an individual item to animate without the others, you'll have to do the animation on it's own timeline (not just it's only layer). You access your symbols own timeline by double clicking it - do your animation in there.
To reference items that are on the stage, you need to give them an instance name. You do that by clicking on the item that's on the stage, then in properties panel, there is field where you can put in an instance name. For the code below to work, you'd need to give them an instance name of "a","b","c","d","e" respectively. This is different than the symbol name in your library (though it can be the same name).
One way you could do this:
var btns:Vector.<MovieClip> = new Vector.<MovieClip>(); //create an array of all your buttons
btns.push(a,b,c,d,e); //add your buttons to the array
for each(var btn:MovieClip in btns){
btn.addEventListener(MouseEvent.MOUSE_OVER, btnMouseOver); // listen for mouse over on each of the buttons
btn.addEventListener(MouseEvent.MOUSE_OUT, btnMouseOut);
}
function btnMouseOver(e:Event):void {
for each(var btn:MovieClip in btns){ //loop through all your buttons
if(btn != e.currentTarget){ //if the current one in the loop isn't the one that was clicked
btn.play();
try{
btn.removeEventListener(Event.ENTER_FRAME,moveBackwards); //this will stop the backwards animation if running. it's in a try block because it will error if not running
}catch(err:Error){};
}
}
}
function btnMouseOut(e:Event):void {
for each(var btn:MovieClip in btns){ //loop through all your buttons
if(btn != e.currentTarget){ //if the current one in the loop isn't the one that was clicked
goBackwards(btn);
}
}
}
There is no nice way to play a timeline backwards, but there are ways to do it. One such way:
//a function you can call and pass in the item/timeline you want played backwards
function goBackwards(item:MovieClip):void {
item.stop(); //make sure the item isn't playing before starting frame handler below
item.addEventListener(Event.ENTER_FRAME, moveBackwards); //add a frame handler that will run the moveBackwards function once every frame
}
//this function will move something one frame back everytime it's called
function moveBackwards(e:Event):void {
var m:MovieClip = e.currentTarget as MovieClip; //get the movie clip that fired the event
if(m.currentFrame > 1){ //check to see if it's already back to the start
m.prevFrame(); //if not move it one frame back
}else{
m.removeEventListener(Event.ENTER_FRAME,moveBackwards); //if it is (at the start), remove the enter frame listener so this function doesn't run anymore
}
}