Mouse event currentTarget.x is null in HTML5

Hi!

I’m writing this one since I didn’t find an answer to this in the forums, so sorry if this has already been asked before. :sweat_smile:

In a mouse up event handler for a sprite, if I trace the value for “e.currentTarget.x” or “e.currentTarget.y”, while using the Flash or Windows targets, I get the correct X/Y values. But if I use the HTML5 or Neko targets, all I get are null values for both, even though tracing “currentTarget” returns “[object Sprite]”.

For now, all I’m doing is this:

function testclicks( e:MouseEvent ):Void 
{
      trace( e.currentTarget );
      trace( e.currentTarget.x );
      trace( e.currentTarget.y );
}

Am I doing something wrong here? Should I store these values somewhere else?

Thanks! :smile:

Ended up using a similar approach to the one used in the PiratePig example project (since I’m making a small puzzle, tried reading full code to build my own version :stuck_out_tongue: ).

Still, if somebody could answer why object properties are either null or 0 in html5, I’d be really thankful. :smile:

You can access the properties of the currentTarget in neko/html5 when you cast it to its type:

trace(cast(e.currentTarget, DisplayObject).x);

this works, but why this is necessary when targeting neko/Html5 I don’t know neither

2 Likes

Oh, thank you! :smile:

I thought it was a little bit strange the first time I tried accessing the properties. Tried adding properties to the object and copying the values in those properties but also didn’t work.

In the end, I just used the location of the mouse pointer (like in PiratePig) relative to the sprite in the stage. :stuck_out_tongue:

But will try to do it as I intended originally. To see which one has better results!

Thanks again. :slight_smile:

Hi

Sometimes it is very necessary to access the currentTarget (and its properties).

When you want the re-use the same handler function for many different objects
(objects that are created at runtime in a loop for example)

Yup, absolutely. :stuck_out_tongue:

Thanks for answering. :smile:

I just spent the better part of a day discovering this (or a similar) issue also. I guess I should have consulted the forum sooner. :slight_smile: I say similar issue because in my VSCode environment, trace seems to report the correct value for e.currentTarget ([object:Sprite]), but when I attempt to call its methods it fails. Here’s my code:

package;

import openfl.events.MouseEvent;
import openfl.display.Sprite;

class Main extends Sprite
{
    public function new()
    {
        super();

        var sprite:Sprite = new Sprite();
        sprite.graphics.lineStyle (1, 0x000000);
        sprite.graphics.beginFill (0xFF0000);
        sprite.graphics.drawRect (0, 0, 100, 100);
        sprite.graphics.endFill ();
        addChild(sprite);

        // Fails on HTML5, works on macOS
        sprite.addEventListener(MouseEvent.MOUSE_DOWN, function(e:MouseEvent):Void {trace("target: " + e.currentTarget); e.currentTarget.startDrag();});
        sprite.addEventListener(MouseEvent.MOUSE_UP, function(e:MouseEvent):Void {e.currentTarget.stopDrag();});

        // Works on both HTML5 and macOS
        // sprite.addEventListener(MouseEvent.MOUSE_DOWN, function(e:MouseEvent):Void {var s:Sprite = e.currentTarget; s.startDrag();});
        // sprite.addEventListener(MouseEvent.MOUSE_UP, function(e:MouseEvent):Void {var s:Sprite = e.currentTarget; s.stopDrag();});
    }
}

When I step into the startDrag() method, I see it first calls openfl_utils_Object.__get(startDrag), with this == Object, and this1 == Sprite. The startDrag call happens next, this == null, and subsequently fails when it tries to get this.stage.

Like your case, this is an HTML5 issue and works fine on macOS (and presumably other targets).

I suppose the workarounds of casting or binding the eventListener to ‘this’ will get me by for the moment, but it seems obvious that this should just work. To @MichaPooh 's point, this would prevent creating an eventListener that provides functionality for many objects since it can’t be bound to a particular instance (of ‘this’), nor would it know how to cast the currentTarget.

It would be great if anyone could shed some light on this in case this is a misunderstanding on my part.

In the original case that started this thread, event.currentTarget.x most likely fails because x has a getter function, and the compiler doesn’t realize that due to the type of event.currentTarget.

public var x(get, set):Float;

event.currentTarget is typed as openfl.utils.Object, which is an abstract, but it ultimately resolves to the Dynamic core type. With Dynamic, the Haxe compiler allows you to access arbitrary fields that may or may not exist without reporting any compile-time errors. It’s up to us developers to ensure that we’re doing things safely when using Dynamic so that we don’t get run-time exceptions or other strange behaviors.

When a variable is typed as Dynamic, the Haxe compiler has no idea that getter or setter functions should be used for certain properties. Basically, in the case of accessing event.currentTarget, the Haxe compiler tries to access a simple variable field named x instead of calling get_x(), like it would if a variable were typed as DisplayObject or Sprite. It doesn’t matter that the variable typed as Dynamic holds a Sprite at run-time either because the compiler has no way of knowing that at compile-time.

To put things another way, if you have any experience using reflection in Haxe, it’s like calling Reflect.field() instead of Reflect.getProperty(). Reflect.getProperty() first checks if a getter function exists, and then it falls back to a simple variable field, if it does not. Reflect.field() goes directly for the simple variable field, and it does not check for a getter function at all.

That’s why casting is the solution here. When you use cast(event.currentTarget, DisplayObject).x instead, you’re giving a hint to the Haxe compiler that event.currentTarget is a DisplayObject, and it can find the definition of the x property that declares that a getter should be used.

All that being said, some targets will automatically detect when a getter or setter exists at run-time. The Haxe language doesn’t provide that ability. It’s just how those targets coincidentally work. So that’s why it works for some targets, but not others. It should simply be considered a quirk of the specific target, and not something that you can rely on for all targets.

It’s basically the same thing with event.currentTarget.startDrag(). The Haxe compiler doesn’t know that the Dynamic field event.currentTarget actually holds a Sprite, so it may not be able to find the method named startDrag() reliably on all targets. cast(event.currentTarget, Sprite).startDrag()` is the way to go there too.

Whenever accessing event.target or event.currentTarget in an event listener, you should basically always cast to a more specific type. You can sometimes get away with not casting, but it has a risk that could have been easily avoided by casting every time. Additionally, you may actually be causing the Haxe compiler to generate less optimized code any time that you use Dynamic without casting, which means that your project may run slower. None of us want that!

Thanks, @joshtynjala. I’m still learning this ecosystem, so I really appreciate the insights. In this case, I expected the nature of the underlying languages, I expected the HTML5/Javascript implementation to be the more capable in working with runtime information than macOS/C++.

If you don’t mind, could you address why this line substituted for the MOUSE-DOWN line in the code above works on both HTML5 and macOS targets?

sprite.addEventListener(MouseEvent.MOUSE_DOWN, function(e:MouseEvent):Void {var d:Dynamic = e.currentTarget; d.startDrag();});

Based on your description, I’m assigning an Abstract/Dynamic to a Dynamic (that contains a Sprite), but in this case it finds the Sprite.startDrag method. Do you think I can count on it working on all targets?

When using openfl.utils.Object, the following JavaScript code is generated:

(openfl_utils_Object.__get(e.currentTarget,"startDrag"))();

When assigning to Dynamic, the following JavaScript code is generated:

var currentTarget = e.currentTarget;
currentTarget.startDrag();

In the first one, Object.__get() basically just calls Reflect.field() or Reflect.getProperty(), so in order to make things clearer, let’s rewrite it like this:

var startDrag = Reflect.getProperty(e.currentTarget, "startDrag");
startDrag();

Generally, in JavaScript, when you assign a method from an object to a variable, the value of this gets lost if you try to call the variable as a function. When startDrag() on its own is called, this is undefined, but e.currentTarget.stopDrag() will work because the method is called in the context of the object where it is defined.

It’s just a quirk of JavaScript, and the use of the abstract is kind of exposing that. I think it may actually be possible to update openfl.utils.Object to make it remember the value of this for methods by calling JavaScript’s Function.prototype.bind() method. I’ll try to look into it.

Thanks again. I’m admittedly weak with Javascript, but that all makes sense. I’m mostly trying to build my understand of Haxe and it’s derivatives, so it’s not critical and hopefully not dragging you from higher priorities.