ithril
ithril copied to clipboard
Templates for haxe. Compiles to HTML or Mithril views.
ithril
Mithril 1.1.1 for Haxe.
Ithril uses Haxe macros to transpile Jade/Pug like templates into Mithril hyperscript.
Template Syntax
Mithril views are declared in a class that extends ithril.Component or implements ithril.View. The declaration must be inside [brackets] marked with a @m meta.
import ithril.*;
class MyComponent extends Component {
public override function view (vnode:Vnode) @m[
(div.intro)
(ul)
(li > 'Some')
(li > 'List items')
(ul.another-list)
(vnode.attrs.list => item)
(li > item)
(form)
(input[type="text"] (value = "Text value", onfocus = focus))
(input[type="checkbox"][required]) ['Check this']
];
}
class Views implements View {
public function view1(vnode:Vnode) @m[
(div > 'view one')
(MyComponent(list=['item one', 'item two', 'item three']))
]
public function view2(vnode:Vnode) @m[
(div > 'view two')
(MyComponent(list=['apples', 'oranges', 'bananas']))
]}
}
Elements
Any html element can be expressed in parentheses:
(img)
CSS classes can be set using the . operator:
(img.my-class-name.my-other-class-name)
An element id can be set with the + operator (as # wouldn't be valid haxe syntax):
(img+my-id)
Attributes can be used inside the selector:
(img[src="img.jpg"])
Attributes can also be expressed separately:
(img (src="img.jpg", alt=""))
(img ({src: "img.jpg", alt: ""}))
(img (aFunctionCallReturningAttributes()))
Children
A shortcut for defining one child:
(h1 > 'My title')
More than one child can simply be nested by using indentation:
(nav)
(ul.links)
(li)
(a[href="http://haxe.org"] > 'Haxe')
(li)
(a[href="http://github.com"] > 'Github')
Inline expressions
Any expression can be used inside brackets:
(h1)
['A string for example']
(button)
[this.buttonLabel]
(div)
[{
var blockExpression = "is Ok";
return blockExpression;
}]
(div)
[{
return true ? @m[
(div > 'this works too')
] : @m [ ]
}]
Conditionals
$if, $elseif, and $else can be used inside templates:
($if (headerSize == 1))
(h1 > 'Big')
($elseif (headerSize == 2))
(h2 > 'Not that big')
($else)
(p > 'Paragraph')
For loop
(link in links)
(a (href=link.url, target='_blank') > link.title)
// or:
($for (link in links))
(a (href=link.url, target='_blank') > link.title)
While loop
($while (expr))
(div > 'text')
Map
The following syntax can be used for any object (in this case links) with a map method:
(links => link)
(a (href=link.url, target='_blank') > link.title)
Map with null check
Using the >> or << operator adds a null check prior to map execution.
// using >>:
(links >> link)
(a (href=link.url, target='_blank') > link.title)
// or using <<:
(link << links)
(a (href=link.url, target='_blank') > link.title)
Translates to:
if (links != null)
[links.map(function(link) m('a', { href: link.url, target: '_blank' }, [ link.title ])];
else
[];
Try/Catch
Try/Catch is limited to a single catch of the Dynamic type.
($try)
(div > functionThatThrows())
($catch (err))
(div > err)
Trusted HTML
Embedding javascript or CSS assets requires marking content as trusted so it is not automatically escaped. Use the @trust meta:
(style > @trust css)
(script)
[@trust javascript]
Components
Custom components can be created by extending ithril.Component. A component can then be used in a view like any other element:
class Hello extends Component {
public override function view (vnode:Vnode) @m[
(div.component > 'Hello ' + vnode.attrs.name)
];
}
class World implements View {
public function helloView(vnode:Vnode) @m[
(Hello(name='World'))
]
}
Children
Children of a Component can be accessed via vnode.children:
class List extends Component {
public function view(vnode:Vnode) [
(ul)
(vnode.children => child)
(li > child)
];
}
Which can be used like this:
(List)
(span > 'A')
(span > 'B')
(span > 'C')
And would output:
<ul>
<li><span>A</span></li>
<li><span>B</span></li>
<li><span>C</span></li>
</ul>
Lifecycle
Mithril creates, reuses, and destroys components as specified by it's diffing algorithm. The lifecycle of a Component can be observed by overriding these methods:
public function oninit(vnode:Vnode) {}
public function oncreate(vnode:Vnode) {}
public function onupdate(vnode:Vnode) {}
public function onbeforeremove(vnode:Vnode) {}
public function onremove(vnode:Vnode) {}
public function onbeforeupdate(vnode:Vnode) {}
You can also specify these methods as attributes of both regular elements and components:
(div(oncreate=function() trace('oncreate')))
(MyComponent(oncreate=function() trace('oncreate')))
State
Mithril manages component state by cloning a component's fields post-constructor and storing them in vnode.state. Because component instances can be cached and reused by Mithril, accessing state must be thru vnode.state unless the initial state is being set.
class StatefulComponent extends Component {
var someState = "my state"; // set initial state value here or in constructor
var someMoreState:String;
override public function new(vnode:Vnode) {
someMoreState = "other state"; // can also set initial component state here
}
override public function view(vnode:Vnode) @m[
(div > vnode.state.someState) // access it via vnode.state
[vnode.state.someMoreState]
]
}
Rendering
Components can be rendered by passing a Component class to Mithril:
M.mount(js.Browser.document.body, MyComponent);
Or may be rendered in nodejs as html:
HTMLRenderer.render(Ithril.view(@m[ (div > 'view') ])).then(function(html) Sys.print(html))
Mithril routing is also supported:
M.route(js.Browser.document.body, "/", routes);
Usage
Any of your class methods can use ithril syntax if you either implement ithril.View or extend ithril.Component. Additionally you can use the static function ithril.Ithril.view to parse an ithril view.
import ithril.*;
import ithril.M.*;
class Web extends Component {
override public function view(vnode) @m[
#if nodejs
(!doctype)
(meta[charset="utf-8"])
(link[href="layout.css"][rel="stylesheet"])
(body)
#end
(div.intro)
(h1 > 'Ithril example')
(p > 'Hello world')
#if nodejs
(script[src="https://cdnjs.cloudflare.com/ajax/libs/mithril/1.1.1/mithril.min.js"])
(script[src="main.js"])
#end
];
}
class Main {
public static function main() {
// On the server
#if nodejs
HTMLRenderer.render(Web).then(function(html) Sys.print(html));
#else
// On the client
M.mount(js.Browser.document.body, Web);
#end
}
}
You can find this usage example in examples/simple.
Output
@m[ (div(attrs)) ] is transpiled to Mithril hyperscript m('div', attrs). On the browser end, it's passed directly to Mithril. In server instances ithril.HTMLRenderer can be used to render HTML.
Sample Website
An example website can be found at examples/website. You will need node, npm, sass, and either closure or uglifyjs installed in order to build and run it.
Initial build: (this will run npm install)
cd examples/website
haxe build.hxml
Start webserver: (listens on localhost:4200, and restarts when webserver.js changes)
npm run start
Auto-build: (rebuilds on file changes)
npm run autobuild