as3 textarea focus event bug - actionscript-3

I'm not sure how long this has existed, it seems to have just come up for the first time.
If you create a TextArea component (author or runtime) and give it focus, you'll get 3 focus events.
create a new as3 fla, drag the TextArea component into your library and paste this on frame 1.
import flash.events.FocusEvent;
import fl.controls.TextArea;
var field = new TextArea();
addChild(field);
var field2 = new TextArea();
field2.x = 150;
addChild(field2);
field.addEventListener(FocusEvent.FOCUS_IN, onFocusIn);
function onFocusIn(event:FocusEvent):void{
trace(event.target);
}
Now click on the left field. Do you see 3 trace statements?
Any idea how to fix this?

You receive multiple FocusEvents, because TextArea contains a TextInput that it controls. When you Focus on the TextArea, the focus is actually given to the TextInput and you receive a focus event for the TextArea, and another for the TextInput.
The best way to limit the number of received event is to check if the target of the event is the same that the target you actually listen.
function onFocusIn(event:FocusEvent):void{
if (event.target == event.currentTarget) {
trace(event.target); // only the focus events generated by the TextArea.
}
}
EDIT So, I got back to the code with the issue about clicks, and the actual fix is really tricky. In fact, the source of the error was a combination of various same issues.
First : the fact that TextArea and the inner TextField both send Event at the beginning.
Second : After the beginning, when the TextField receive focus from a click, it is blocked by the parent.
And Third : When the focus come from out of the swf, in your case, the focus event is send two times (dunno why).
In order to fix it correctly, I had to listen to the unknown TextField within the TextArea (instead of the TextArea itself), and keep track of the focus leaving the Stage in order to inhibit the first of the two events generated. Which gives this :
import flash.events.FocusEvent;
import fl.controls.TextArea;
var stageFocus:Boolean = true;
var field = new TextArea();
addChild(field);
var field2 = new TextArea();
field2.x = 150;
addChild(field2);
field.addEventListener(FocusEvent.FOCUS_IN, onFocusIn);
function onFocusIn(event:FocusEvent):void{
if (event.target == event.currentTarget) {
if (event.target is TextField) {
if (stageFocus) {
// Finally ! one event at a Time and no miss.
trace(DisplayObject(event.target).parent);
} else {
stageFocus = true;
}
}
} else {
event.target.addEventListener(FocusEvent.FOCUS_IN, onFocusIn);
field.removeEventListener(FocusEvent.FOCUS_IN, onFocusIn);
}
}
// check if the focus leave the stage (the user clic out of the swf)
stage.addEventListener(FocusEvent.FOCUS_OUT, onStageFocusOut);
function onStageFocusOut(event:FocusEvent) {
if (event.relatedObject == null) {
stageFocus = false;
}
}

Related

Going from one scene to next and gettingType1009 Null object reference errors

Okay so in my main menu for my game, I have a glow effect so that when you hover over the invisible button 'startdesu', the script will make the whole menu glow, fading in and out. This code (from the event listener onwards) has been repeated for each button on the menu, with appropriate changes to the button instance name and the function names. The only problem is, when I click 'startdesu' and it takes me to the next scene, I start getting repetitive errors, "TypeError: Error #1009: Cannot access a property or method of a null object reference.
at bjvb_fla::MainTimeline/increaseGlow()". I've tried removing the glow event listeners when the buttons are clicked and everything but no luck. Please help! ;0;
Here is the essential code for brevity's sake, but I can post the whole thing if it helps.
(also, i get the same Null error for something else when i go from the game back to the start menu.
import flash.filters.*;
import flash.events.Event;
stop();
wow.alpha=0.5;
var menuGlow:GlowFilter = new GlowFilter();
menuGlow.color = 0xffffff;
menuGlow.blurX = 20;
menuGlow.blurY = 20;
menuGlow.alpha = 0;
menu.filters = [menuGlow];
var glow:Boolean = false;
startdesu.addEventListener(MouseEvent.MOUSE_OVER, addGlow);
startdesu.addEventListener(MouseEvent.MOUSE_OUT, removeGlow);
startdesu.addEventListener(Event.ENTER_FRAME, increaseGlow);
function addGlow(e:MouseEvent):void
{
glow = true;
}
function removeGlow(e:MouseEvent):void
{
glow = false;
}
function increaseGlow(e:Event):void
{
if(glow == true)
{
menuGlow.alpha = menuGlow.alpha + 0.02;
}
else if(glow == false)
{
menuGlow.alpha = menuGlow.alpha - 0.02;
}
menu.filters = [menuGlow];
}
This is the line that is likely causing the error:
menu.filters = [menuGlow];
There is probably no object with the instance name 'menu' in your second scene. You could fix the error by just adding a check if the object exists, like so:
function increaseGlow(e:Event):void
{
//if there's no menu object, return
if(!menu) return;
if(glow == true) {
menuGlow.alpha = menuGlow.alpha + 0.02;
} else {
menuGlow.alpha = menuGlow.alpha - 0.02;
}
menu.filters = [menuGlow];
}
But the correct solution would be to remove the event listeners. Not sure why it didn't work for you, but you should be able to just add this in the click event handler before you switch your scene.
startdesu.removeEventListener(Event.ENTER_FRAME, increaseGlow);
What errors do you get when you try to remove the event listener?

Fire an event when a TextField text property is set

I have a TextField instance on stage. The copy within the field is updated from outside my project by a 3rd party system that I have no access to. I don't know exactly how this is achieved but I need to know when it has happened.
I want a way of telling when the text is updated within the field so I can then format and position the field based on its new content. I've tried
myTextField.addEventListener(Event.CHANGE, updateMethod);
but it seems that this only fires when the text is changed by direct user interaction and not the kind of injection from outside that I am getting.
Is there any way that I can listen for an assignment to myTextField.text rather that only for changes made by the user via mouse/keyboard?
Appreciate any advice. Thanks in advance :)
Instead of directly accessing the text field, you can wrap it with your own class, and dispatch events when the setter is fired: (untested code)
public class MyTextField extends EventDispatcher {
private var textField:TextField;
public MyTextField(t:TextField) {
textField = t;
}
public function set text(s:String) {
textField.text = s;
var e:Event = new Event(Event.CHANGE);
dispatchEvent(e);
}
public function get text():String {
return textField.text;
}
}
And else where in your code:
var mytf:MyTextField = new MyTextField(stage.myTextField); //stage.myTextField is the reference to the text field on the stage
mytf.addEventListener(Event.CHANGE, function(e:Event) { /* get event */ });
// don't use stage.myTextField.text to set text, instead, use:
mytf.text = "setting text!"; //this will trigger the event callback above

Actionscript 3 drag and drop on multiple specific targes and change alpa for the dropped objects as well as stack targets

I have been trying to achieve three things in the project without success. I am new at this and have relied on tutorials to get this far. Here we go!!
a. I want to be able to drop label_3 and label_4 on either or targetlabel_3 and targetlabel_4 but not effect the other labels and targets.
b. I want to be able to drop label_2 on top of label_1 once it has been dropped. I am finding that when label_1 has been dropped, it hides the targetlabel_2 and label_2 can't find it's target.
c. I want to change the Alpa of each of labels _1, _2, _3, _4 and _5 to zero when they are dropped on their targets and change the Apha for labels _11, _21, _31, _41 and _51 to 100. (I have changed the Apha to 25 on these for the sake of making it easier for someone to see what I am trying to do).
I have been mucking around for days on this and have hit a brick wall.
Can anyone help please?
import flash.display.DisplayObject;
import flash.geom.Rectangle;
/* Drag and Drop
Makes the specified symbol instance moveable with drag and drop.
*/
var startX:Number;
var startY:Number;
var counter = 0;
var attempts = 0;
var rect:Rectangle;
rect=new Rectangle(100,100,700,500);
correct_txt.text=counter;
attempts_txt.text=attempts;
label_1.addEventListener(MouseEvent.MOUSE_DOWN,Drag);
label_1.addEventListener(MouseEvent.MOUSE_UP,Drop);
label_2.addEventListener(MouseEvent.MOUSE_DOWN,Drag);
label_2.addEventListener(MouseEvent.MOUSE_UP,Drop);
label_3.addEventListener(MouseEvent.MOUSE_DOWN,Drag);
label_3.addEventListener(MouseEvent.MOUSE_UP,Drop);
label_4.addEventListener(MouseEvent.MOUSE_DOWN,Drag);
label_4.addEventListener(MouseEvent.MOUSE_UP,Drop);
label_5.addEventListener(MouseEvent.MOUSE_DOWN,Drag);
label_5.addEventListener(MouseEvent.MOUSE_UP,Drop);
label_1.buttonMode = true;
label_2.buttonMode = true;
label_3.buttonMode = true;
label_4.buttonMode = true;
label_5.buttonMode = true;
function Drag(event:MouseEvent):void
{
event.target.startDrag(true,rect);
feedback_txt.text="";
event.target.parent.addChild(event.target);
startX=event.target.x;
startY=event.target.y;
}
function Drop(event:MouseEvent):void
{
event.target.stopDrag();
var myTargetName:String="target" + event.target.name;
var myTarget:DisplayObject=getChildByName(myTargetName);
if (event.target.dropTarget!=null&&event.target.dropTarget.parent==myTarget){
feedback_txt.text="Well done! You have selcted the correct label and placed it in the recommended position on the package.";
feedback_txt.textColor = 0xCC0000
event.target.removeEventListener(MouseEvent.MOUSE_UP,Drop);
event.target.removeEventListener(MouseEvent.MOUSE_DOWN,Drag);
event.target.buttonMode = false;
event.target.x=myTarget.x;
event.target.y=myTarget.y;
counter++;
correct_txt.text=counter;
correct_txt.textColor = 0x0000ff
attempts++;
attempts_txt.text=attempts;
attempts_txt.textColor = 0x0000ff
}else{
feedback_txt.text="Your attempt is not quite correct. You have either selected the incorrect label or placed it in the wrong position. Please try again.";
event.target.x = startX;
event.target.y = startY;
attempts++;
attempts_txt.text = attempts;
}
if (counter==5){
feedback_txt.text="Well done! You have correctly placed all 5 labels";
percentage_txt.text ="Based on your attempts, you have scored "+Math.round ((counter/attempts) *100)+" %";
percentage_txt.textColor = 0x0000ff
}
}
The easiest way to detect when a label is on another label is by using hittest in an enter frame event listener.
stage.addEventListener(Event.ENTER_FRAME, hit_test);
function hit_test(e:Event):void{
if (label_1.hitTestObject(targetLabel_1)) {
trace("Label_1 is hitting targetlabel_1");
label_hit();
}
if (label_2.hitTestObject(targetLabel_2)) {
trace("Label_2 is hitting targetlabel_2");
label_hit();
}
}
When the hittest is activated, the trace text is shown and the function is called. To change the alphas of the labels, use the function being called by the hittest. For example:
function label_hit()
{
label_1.alpha = 0;
label_2.alpha = 0;
label_3.alpha = 0;
}
If you are trying to have conditions to when things can be dragged, seen, or hit tested, that function is also where you can take care of them. For example, If you don't want a label to be visible until the hittest, you have the alpha set to 0 until the function sets it to 100. If you don't want a label to be drageable until then, you create the listener inside the function instead of earlier.
function label_hit()
{
label_1.alpha = 100;
label_1.addEventListener(MouseEvent.MOUSE_DOWN,Drag);
label_1.addEventListener(MouseEvent.MOUSE_UP,Drop);
}
If you want hittests to occur only after other hittests have already occured, place them in conditions and have the conditions met in the functions.
stage.addEventListener(Event.ENTER_FRAME, hit_test);
function hit_test(e:Event):void{
if (label_1.hitTestObject(targetLabel_1)) {
trace("Label_1 is hitting targetlabel_1");
label_hit();
}
if(condition)
{
if (label_2.hitTestObject(targetLabel_2)) {
trace("Label_2 is hitting targetlabel_2");
label_hit();
}
}
function label_hit()
{
var condition = true;
}

ActionScript 3 Tree - Node Value Pop-up After Mouse Hovers for "X" Seconds

I'm fairly new to ActionScript 3 (so I’m sorry if this is a naïve question) and I'm working on an existing project that uses a "Tree" menu. Each node in the tree represents a section in the application. Unfortunately, some of the section names (which are what's displayed in the node’s display value) are fairly long and requires the text to be truncated. As a result, there are times where the section names are cut off. To get around this, we want to give users the ability to see the entire title by moving their mouse cursor over the node for “X” seconds in which case a small pop-up renders the node's label.
Example
public var menuTree:Tree;
public function DoSomething(){
menuTree.addEventListener(ListEvent.ITEM_ROLL_OVER, onListItemRollover, false, 100);
}
private function onListItemRollover(event:ListEvent):void {
//IF MOUSE CURSOR IS STILL OVER NODE FOR "X" SECONDS DISPLAY NODE'S LABEL IN POP-UP
}
Thanks all in advance!
Without knowing more about your setup, I would probably setup something like this:
var timer:Timer;
var currentItem:*
for each (var node:* in menuTree) {
node.addEventListener(MouseEvent.MOUSE_OVER, overHandler);
node.addEventListener(MouseEvent.MOUSE_OUT, outHandler);
}
function overHandler(event:MouseEvent):void {
stopTimer();
currentItem = event.currentTarget;
timer = new Timer(2000, 1);
timer.addEventListener(TimerEvent.TIMER, showPopup);
timer.start();
}
function outHandler(event:MouseEvent):void {
stopTimer();
}
function showPopup(timerEvent:TimerEvent):void {
stopTimer();
//show popup code here
//use currentItem
}
function stopTimer():void {
if (timer) {
timer.stop();
timer.removeEventListener(TimerEvent.TIMER, showPopup);
}
}
So instead of adding the event listener to the menuTree you're going to loop though each item in the tree and add a listener to that item. Then when the user rolls over any given item it starts a timer that after 2 seconds will run a function to show the popup.

Convert TextArea into TextField in AS3

I've a TextArea component in my MovieClip. When I double click on it, I want to switch to TextField component, allowing me to change its content. When I click outside, I want to restart its original class (TextArea).
How can I do it?
I'm doing this, but didn't work:
element.addEventListener(MouseEvent.DOUBLE_CLICK, changeName);
private function changeName(e:MouseEvent):void{
e.target.type = TextFieldType.INPUT;
}
Where element is a TextArea (clasic and dynamic text). Thanks!
EDIT:
This is how my MovieClip looks. "Name" is the TextArea that I want to allow user changes. I'm setting it like this:
[Spanish interface]
Nombre de instancia = Instance name (empty)
Texto clásico (classic text)
Texto dinámico (dynamic text)
The MovieClip is controlling my by own base class (called 'ConfLayer'). Inside it I have this:
public function doStuff(e:MouseEvent):void{
// element = TextArea 'Name'
element.addEventListener(MouseEvent.DOUBLE_CLICK, changeName);
}
private function changeName(e:MouseEvent):void {
var tarea:TextArea = e.target as TextArea;
var tf:TextField = tarea.TextField; // this line throwing error
tf.type = TextFieldType.INPUT;
}
Because AS3 gives me errors, I tried this:
private function changeName(e:MouseEvent):void {
e.target.TextField.type = TextFieldType.INPUT;
}
When I double-click on TextArea element, the previous string removes and I can't write nothing.
You make field editable only after second click. My idea is that it's to late. the field is editable, but you'll need to click again. So, try to make ediable for a while (e.g. 300 mileseconds) after the first click. If there's no click you set the type back to DYNAMIC. After second click you'll start editing text field.
Following on my comment above, a TextArea has a textfield property.
So you should be able to do something like this:
private function changeName(e:MouseEvent):void{
var tarea:TextArea = e.target as TextArea;
var tf:TextField = tarea.textField; //make sure that textfield is camelCase!!!
tf.type = TextFieldType.INPUT;
}
I solved my own problem using background and border properties from TextField. Instead of create TextField directly on my MovieClip, I create it dynamically.
// Field 'Name' of the layer
var tarea:TextField = new TextField();
tarea.text = 'Layer';
tarea.x = -80;
tarea.y = -23;
tarea.type = TextFieldType.DYNAMIC;
tarea.height = 20;
// Format the TextField
var format_tarea:TextFormat = new TextFormat();
format_tarea.font = "Arial";
format_tarea.size = 14;
format_tarea.bold = true;
tarea.setTextFormat(format_tarea,0,tarea.length);
Then, I add it some listeners to allow changes when I click on it:
// Add event to allow name changes
tarea.addEventListener(MouseEvent.CLICK,changeName);
When I press ENTER key, I accept the changes
// Add event to accept changes on press ENTER key
tarea.addEventListener(KeyboardEvent.KEY_DOWN,acceptKeyboardName);
When TextField lost his focus, accept the changes too:
tarea.addEventListener(FocusEvent.FOCUS_OUT,acceptFocusName);
Last, I added to my own MovieClip
// Add to MC
mc.addChild(tarea);
Handlers associated to below events are these:
private function changeName(e:MouseEvent):void
{
e.target.setSelection(0,e.target.text.length);
e.target.border = true;
e.target.background = true;
e.target.type = TextFieldType.INPUT;
}
private function acceptKeyboardName(e:KeyboardEvent):void
{
if (e.charCode == 13) // When pres ENTER, remove border, background and restore dynamic type
{
e.target.type = TextFieldType.DYNAMIC;
e.target.border = false;
e.target.background = false;
}
}
private function acceptFocusName(e:FocusEvent):void
{
// When lost focus, remove border, background and restore dynamic type
e.target.type = TextFieldType.DYNAMIC;
e.target.border = false;
e.target.background = false;
}