Haxe tips: including/calling JS from your Haxe code

Posted on February 27, 2016

Whelp, just realized it’s been a year since I’ve posted stuff here. I’m not committing myself to a regular posting schedule or anything, but over the past year I’ve had my fair share of adventures with Haxe, and I finally have interesting stuff to blog about. So I’ll be dropping some articles like metaphorical mix tapes, yo.

Edit: I don’t know how but I overlooked this wonderful article by @elsassph: http://philippe.elsass.me/2014/11/haxe-working-with-javascript-libraries before I began writing mine. Philippe’s article covers the subject very well, but I hope mine is still relevant.

What to do when you need to call some JS? Well, you have three ways to do that.

Way 1: The standard library way

Haxe has a js package that contains external definitions for all the HTML5 JS types, and it also has externs for JQuery. You can find more extern definitions on Haxelib/GitHub (Node.js, pixi, lots more that I can’t remember at the moment).

If you’ve ever done some JS in the past, then js.Browser.document will make you feel right at home. That expression basically translates to the almighty document. So without any special consideration you could call js.Browser.document.addEventListener("click", onClickHandler); and the compiler will produce the expected JS result.

Way 2: The untyped way

This way is a tiny bit dirty, but justified when you’re just looking to break free of the Haxe typing system. For example, I was messing around with the Pointer Lock API the other day, and it’s still in flux and vendor-specific, so the current Haxe requestPointerLock() doesn’t work across all browsers. And you know what, that’s fine! Because we can write our own JS.

Let’s consider the Fullscreen API. Without any special consideration you could call js.Browser.document.requestFullscreen();, but you’ll find that on Mozilla for instance this wouldn’t work. Instead, you would have to call js.Browser.document.mozRequestFullScreen();.

But that would give us a compilation error because the Haxe definition of document doesn’t actually have a mozRequestFullScreen function defined since it’s not an official HTML5 function, just some vendor-specific stuff. But fear not! You can bypass compile-time type checking like this: untyped js.Browser.document.mozRequestFullScreen();. This tells the compiler to mind its own business and translate that into document.mozRequestFullScreen();.

I prefer the more direct approach though: untyped __js__("document.mozRequestFullScreen()");. This is personal preference, but I think it communicates more clearly what specific JS code you want the compiler to produce by, you know, immediately providing that code. The difference between untyped __js__("..."); and untyped ...; is that in the first way you write the actual raw JS, the Haxe compiler simply takes that and copies it directly into the compiled file.

That’s all I have to say about the untyped method, but let’s finish with an example. Here’s the class I wrote to take care of pointer locking. Tested on Firefox and Chrome, but it’ll be a cold day in hell before I launch IE.

package blip.utiljs;
import js.Browser;
import js.html.Element;
import js.html.Event;

/**
 * ...
 * @author Ohmnivore
 */
class PointerLock {
	
	private var canvas:Element;
	public var locked:Bool = false;
	
	public function new() {
		
	}
	
	public function init():Void {
		canvas = Browser.document.getElementsByTagName("canvas").item(0);
		Browser.document.addEventListener("click", requestPointerLock);
		Browser.document.addEventListener("pointerlockchange", pointerLockChange);
		Browser.document.addEventListener("mozpointerlockchange", pointerLockChange);
		Browser.document.addEventListener("webkitpointerlockchange", pointerLockChange);
		Browser.document.addEventListener("mousemove", mouseMove);
	}
	
	private function requestPointerLock():Void {
		if (!locked) {
			untyped __js__("this.canvas.requestPointerLock = this.canvas.requestPointerLock || this.canvas.mozRequestPointerLock || this.canvas.webkitRequestPointerLock");
			untyped __js__("this.canvas.requestPointerLock()");
		}
	}
	
	private function pointerLockChange():Void {
		untyped __js__("this.locked = (document.pointerLockElement === this.canvas || document.mozPointerLockElement  === this.canvas || document.webkitPointerLockElement  === this.canvas)");
		Blip.log.log("Pointer locked: " + locked);
	}
	
	private function mouseMove(e:Event):Void {
		if (locked) {
			var movementX:Float = untyped __js__("e.movementX || e.mozMovementX || e.webkitMovementX || 0");
			var movementY:Float = untyped __js__("e.movementY || e.mozMovementY || e.webkitMovementY || 0");
			Blip.mouse.handleMoveRelative(movementX, movementY);
		}
	}
}

Way 3: Use someone else’s externs or make your own

Note for lime/OpenFL users: use the <dependency/> tag to include external JS files like so: <dependency if="html5" path="dependencies/soundjs.min.js" /> or <dependency if="html5" path="dependencies/ammo.js" />

I don’t have much experience with externs, so here are two articles that will explain it better:

Here’s an example of extern definition from the OpenFL source: