haxe-dom provides target independant DOM manipulation. The goal of this project is to reduce duplicate code between the server and client without having to resort to single page apps. View state can be manipulated on the server, serialized into idiomatic HTML5 and reconstructed on the client.
Contruct the page (Neko/PHP/Java/etc):
import hxdom var page = new EHtml();var head = new EHead();var body = new EBody(); pagepage
Serialize to HTML:
writeToHttpSocket
The following is sent across the wire:
Init on client (JavaScript):
var page = hxdom
Custom Classes
The above example is cool and all, but it's not really practical for a full scale app. We need to be able to extend and encapsulate DOM components. Doing this is also very straightforward:
class MyCustomApp extends EHtml { public var headnull):EHead; public var bodynull):EBody; public function new (numTexts:Int) { super(); head = new EHead(); body = new EBody(); node node for (i in 0 ... numTexts) { addSomeTextToBody(); } } public function addSomeTextToBody ():Void { body } }
Build the HTML and send it to the client:
writeToHttpSocket
Sends this to the client:
Some TextSome TextSome TextSome TextSome Text
Load the custom app and add 2 more lines of text (JavaScript):
var myCustomApp:MyCustomApp = cast hxdommyCustomAppmyCustomApp
Event Listeners
You can also attach event listeners on the server:
class MyCustomApp extends EHtml { public function new () { super(); var body = new EBody(); node node bodyonClick); } public function onClick (_):Void { trace("Click!"); } }
Sends the following:
Load on client (JavaScript):
hxdom
And that's it! Clicking on the webpage will trigger a "Click!" to be traced to the console.
One unavoidable restriction on events is that you cannot listen to anonymous functions. The serializer needs to be able to reference the function and this is impossible with lambdas. For example this will result in a compilation error:
myElemfunction{ trace("Click!"); });
Tools
Using the DOM directly can be kind of annoying, so I've included a DomTools class that you should usually include as a "using". DomTools mimics the JQuery API and is built for chaining. Here is a quick example:
using hxdom //DOM manipulationvar div = new EDiv()"someid") //Eventsvar anotherDiv = new EDiv()someEventHandler);function someEventHandler (e:hxdom{ //Remeber this method needs to be a member of a class or static to be able to be serialized properly trace== anotherDiv); //Returns true} //Event delegation similar to JQuery, but filters by Haxe class type not selectorclass MyCustomElement extends ESpan { public function new () { super(); } public function hello () { trace("HI!"); }}var yetAnotherDiv = new EDiv()"click keypress", delegateHandler);yetAnotherDivfunction delegateHandler (e:hxdommyCustomElement:MyCustomElement):Void { //Remeber this method needs to be a member of a class or static to be able to be serialized properly myCustomElement}
Custom Event Systems
If you are interested in rolling your own event systems, but want to take advantage of the serializability of haxe-dom then you can use the hxdom.SFunc class. What it does is split any fully referenceable function into its instance (or class for static functions) and function name components. For example:
class MyClass { var eventHandler:hxdom public function new () { //Could call this on either the server or the client //MyClass instances are fully serializable //Internally SFunc stores [inst => this, func => "myEventListener"] eventHandler = hxdom } public function fireEvent ():Void { //Can call this on either the server or the client as well eventHandler } function myEventListener ():Void { trace("Event!"); } }
Don't want to pass around SFunc references? Use the hxdom.SFunc.macroMake() macro to roll your own seamless event system. This allows you to make completely transparent code like this:
someObjmyEventHandler); ... macro public function bind (ethis:Expr, type:ExprOf<String>, listener:ExprOf<Void -> Void>):ExprOf<Void> { return macro $ethis${hxdom}
Client Initialization
Sometimes it is important to have code that only executes on the client. This can be useful for things such as deferring queries to save on startup time or if you have some 3rd party client library that needs initialization. hxdom.js.ClientOnly serves this need by allowing you to tag specific methods for client-only execution.
To use it simply implement hxdom.js.ClientOnly to the target class then add @:client meta data to any function you want to run when the client is initialized. The function must take no arguments and should not return anything.
Adding @:client to a method does a couple of things behind the scenes:
- It removes the method from the server code, so you can use client-specific code such as js.Browser.window.alert("Alert!").
- It will mark the method to be called when hxdom.js.Boot is called (after initializing everything).
- It will call the method everytime a new instance of the class is created. The methods will always be called last in the constructor, but the execution order cannot be gaurunteed between multiple @:client methods.
FAQ
Can I use this with NodeJS?
Although I have not tested this yet you can use NodeJS by adding the -D use_vdom compiler flag. This flag makes the library use the virtual DOM instead of the actual one (which doesn't exist in NodeJS).
A Full Example
Run the compile.hxml file in the test directory to build index.html which you can then load in a browser.