elsassph / fusetools-haxe

Using Haxe with FuseTools is possible - maybe even useful?!
31 stars 2 forks source link

Haxe for FuseTools

FuseTools is a new, promising, tool for crossplatform/mobiles apps development.

Application scripting is done in JavaScript, and one of the remarkable aspects of FuseTools is that it is strongly based on observer pattern and reactive programming. The result of this combination is the Observable object which combines those naturally/magically.

Of course you normally have to rely on your brain alone in JavaScript to leverage this crazy reactive programming approach. Unless you use Haxe to have strong typing!

Attention: proof-of-concept ahead

Haxe?

Haxe is a superb strongly typed language which can fit as well heavy-weight single-page applications but also distributed/reactive code as FuseTools applications.

Here's my attempt to convince myself (and maybe you) that the Haxe compiler and type system can bring a lot of value to Fuse projects: it's fun to play with JavaScript, but how do you control the integrity of your projects when they will have grown past the Poc size?

Observable

The Observable API is really rich and challenging to describe in Haxe, especially the fact that an Observable is not instantiated with the new keyword:

var Observable = require('FuseJS/Observable');

var who = Observable('World');

var hello = Observable(function() {
    return "Hello " + who.value;
});

module.exports = {
    hello: hello
}

// UX can bind `hello` and will update automatically whenever `who`'s value change!!!

Haxe doesn't like that so we have to have a slightly different approach, but it looks quite nice in the end:

import Fuse.observable;

class View {
    var who = observable('World'); // Observable<String>
    public var hello:Observable<String>;

    public function new() {
        hello = observable(function() {
            return 'Hello ${who.value}';
        });
        who.value = 42; // Compiler error: Int should be String
    }
}

module.exports = new View();

It may look like syntaxic sugar, but it's way more than that: this is fully strongly typed. Everything is validated by the compiler and guarranteed to be sound before running it.

module.exports

Normally Fuse scripting is "distributed": each view has its little inline script or requires an external JS for the view.

Each view script should expose observables and callbacks through a module.exportsobject (see previous point about Observables).

It is possible however to have shared code between views:

<JavaScript File="app.js" ux:Global="app" />
<JavaScript>
    var app = require('app');
    module.exports = new app.MainView();
</JavaScript> 

app.js would be Haxe generated, and to expose these view scripts we need to write:

class App {
    static function main() {
        untyped module.exports = {
            MainView: MainView
        };
    }
}

TODO: there should be a nice way to automate that

Callbacks

A Fuse UX view can bind actions to script callbacks.

The problem is that for some reason those functions are called out of scope, but there's a macro for that ;)

class MainView implements UXExport {
    var currentTab = observable(Tab.All);
    /*...*/ 
    public function showAll() {
        currentTab.value = Tab.All; // this.currentTab
    }
    /*...*/ 
}

module.exports = new MainView();

If the showAll function was just exposed as a function of the MainView instance, you'd discover that the scope is lost when the function is invoked.

The solution is to implement UXExport which will run a macro pre-binding all the public functions to the instance so that scope will be correct when the method is called by the view.