The herd.thrillon
module contains a Mithril renderer for the
standard HTML module of the Ceylon SDK: ceylon.html
.
This combines the statically-typed, declarative and functional approach of
ceylon.html
with the simplicity, power, and performance of
Mithril.js
Instead of having a ceylon.html
node rendered directly to a string and assigned to the content
of a dom node, it allows rendering a ceylon.html
node as a Mithril component that can be directly
mounted at a given location in the HTML page DOM tree.
This means, in particular, that you can attach Ceylon functions as event handlers directly while defining your HTML.
A quick preview shows code example, which mounts a Ceylon HTML Mithril template to the DOM document root:
shared void run() { assert (exists root = document.body); mount { parent = root; object component satisfies Template { variable value count = 0; build() => Main { Div { H1 { "Thrillon: Mix Mithril and Ceylon Html DSL !" }, Button { attributes = [ event.click((evt) { count ++; }) ]; "``count`` clicks" } } }; } }; }
Calling this run()
method from the main HTML page is all you have to do
to have the button react to click events and increment the
displayed click counter.
The module defines bindings for some of the main Mithril API calls and structures:
Component
interface,VNode
interface,vnode()
hyperscript function,mount()
function,and a new type of Mithril component based on the ceylon.html
module:
Template
interface.To create a new Mithril component, simply implement the Template.build method
to return a ceylon.html Node
element.
This ceylon.html
-based Mithril component can then be attached to any Dom element
in the DOM tree with the mount function.
The event
object allows adding Ceylon functions as event handlers
in the attributes of a
ceylon.html node.
Button { attributes = [ event.click((evt) { // Ceylon code // 'evt' is a ceylon.interop.browser.dom::Event }) ]; "Button Text" }
More information is given on the documentation.
The WatchedValue
class provides a way to automatically
trigger an incremental redraw of the HTML DOM tree each time
a value is changed.
More information is given on the documentation.
A BoundValue
provides a way to associate a value
with an Html node, and bidirectionally bind it to a propety
of the html node.
More information is given on the documentation.
Mithril routing is provided by
the router
object which satisfies the Router
interface.
In a number of use-cases, we have to manage raw javascript objects:
for parameters of the Template.build()
method, or the route parameters
of the Router.redirect()
method for example.
In order to make the use of these row Javascript objects in a
pseudo-typesafe way, such values are declared as the JS
type.
Then a JsObject
can be instanciated with the JS
value, in order
to retrieve properties by using native Ceylon correspondence syntax:
value jsObject = JsObject(js); JsType? prop = jsObject["propName"];
The same can be done with the JsArray
.
And JsType
is either a simple value String
, Boolean
, etc…
or a JsObject
or a JsArray
Any arbitrary dynamic
Javascript value can be converted to a
JsType
by the jsType()
function.
In the case of complex, deep, Javascript objects, the JsPath
class allows pointing directly to an expected value in the Javascript
object tree structure like this:
value jsPath = JsPath(complexJavascriptObject); // get a[0].s String? sInFirstA = path.get("a").get(0).get("s").str; // get a.*.s {String*} sInAllAs = path.get("a")*.get("s")*.str;
A Thrillon application is packaged by using requireJS
Minimal RequireJs
code to bootstrap a thrillon_application module
would be like that:
require.config({ baseUrl : 'modules/', paths: { // Define where the real Javascript file is available for the Mithril/1.1.6 NPM // module, related to the requireJS `baseUrl` // When running from your development environment, the NPM module already has // been downloaded into `../node_modules` when compiling the Ceylon application. 'mithril/1.1.6/mithril-1.1.6': '/../node_modules/mithril/mithril.min' } }); require([ 'thrillon_application/1.0.0/thrillon_application-1.0.0'], function(example) { example.run(); });
In some use-cases, it might be useful, from event-handlers, to
have access to the underlying DOM node corresponding
to a given Html Node
.
button = Button { attributes = [ attachDom(), event.click((evt) { value domButton = dom(button); if (is HtmlDomElement domButton) { value style = domButton.getAttribute("style"); if(exists style, style == "background-color: yellow;") { domButton.setAttribute("style", "background-color: blue;"); } else { domButton.setAttribute("style", "background-color: yellow;"); } } }) ]; "Change color" }; return button;
The attachDom()
function is called as part of the Html attributes
.
Then the DOM node associated to the button
Html Node
through
the dom()
function.
Thrillon allows reusing external
Mithril components
by simply inserting a WrappedComponent
inside the ceylon.html
tree-like structure.
The lifecycle
top-level object provides wrapper methods for each
Mithril lifecycle event.
Every wrapper method produces an AttributeEntry
from the corresponding
event handler implemented as a Ceylon function type.
It is used when creating the ceylon.html
tree that will be rendered
by a Template
. Event Handlers can thus be added in the
attributes
parameter of the Html Node.
Pre { Code { clazz = "ceylon"; attributes = [ lifecycle.create((vnode) { dynamic { hljs.highlightBlock(vnode.dom); } }) ]; sourceCode } }
In this example, the highlight.js highlighting
is applied to the content of the Code
Html node, each time the
corresponding Dom node is created.
Packages | |
herd.thrillon | |
herd.thrillon.jsutils | Utilities that simplify the work with raw Javascript variables (objects or arrays) |
Dependencies | ||
ceylon.html | 1.3.3 | |
ceylon.interop.browser | 1.3.3 | |
mithril | 1.1.6 |
Values | |
event | shared event event This top-level object provides wrapper methods for each type of HTML DOM event. This allows producing an How to use itIt is used when creating the function buttonWitOnClickHandler() => Button { attributes = [ event.click((evt) { doAnActionOnEvent(evt) }); ]; "Standard Button" } function buttonWitCustomEventHandler() => Button { attributes = [ event.custom("ontouchmove", (evt) { doAnActionOnEvent(evt) }); ]; "Touchable Button" } |
key | shared key key Helper object to provide the Mithril key feature. Key provided from an already-known valueList<String> list; ... Table { TBody { for (i->item in list.indexed) Tr { attributes = [ key.set(i.string) ]; item } } }; Key provided as a functionclass Person(shared String firstName, shared String lastName) { shared String key() => "``firstName`` $$ ``lastName``"; } Set<Person> persons = nothing; Table { TBody { for (person in persons) Tr { attributes = [ key.build(person.key) ]; } } }; |
lifecycle | shared lifecycle lifecycle This top-level object provides wrapper methods for each Mithril lifecycle event. This allows producing an How to use itIt is used when creating the ExamplePre { Code { clazz = "ceylon"; attributes = [ lifecycle.create((vnode) { dynamic { hljs.highlightBlock(vnode.dom); } }) ]; sourceCode } } In this example, the highlight.js highlighting
is applied to the content of the |
router | shared Router router |
Functions | |
attachDom | shared AttributeEntry attachDom() Function that should be called as part of the Html Examplebutton = Button { attributes = [ attachDom(), event.click((evt) { value domButton = dom(button); if (is HtmlDomElement domButton) { value style = domButton.getAttribute("style"); if(exists style, style == "background-color: yellow;") { domButton.setAttribute("style", "background-color: blue;"); } else { domButton.setAttribute("style", "background-color: yellow;"); } } }) ]; "Change color" }; return button; |
buildBinder | shared AttributeEntry buildBinder<T, Exists>(WatchedValue<T,Exists> watchedValue, String boundAttributeName, AttributeEntry updateEvent(void code(Event evt)), T|Exists extractFromDom(HTMLElement dom)) Utility function to help construct the binder
when building new types of |
dom | shared DomNode? dom(Node? node) Function that retrieves the current dom node from a Html |
mount | shared void mount(DomElement parent, Component? component) Activates a component, enabling it to autoredraw on user events. This a wrapper on the corresponding Mithril API: m.mount() |
vnode | shared VNode vnode(String|Component element, dynamic attributes, {VNode|String*} children) Creates a virtual node (VNode). This a wrapper on the main hyperscript function of the Mithril API: m() |
Interfaces | |
Args | shared dynamic Args Wrapper on a |
BoundValue | shared BoundValue<T,Exists> A The
Several implementations are provided for the most common use-cases:
How it worksConsider the following code : object satisfies Template { value textArea = TextAreaValue(ExistingWatchedValue<String>("")); build(JS attrs) => Main { Div { TextArea { rows = 30; cols = 200; attributes = [ textArea.binder ]; }, Button { attributes = [ event.click((evt) { // Displays the content of `TextArea` window.alert("text = ``textArea.val``"); // Updates the content of `TextArea` textArea.val += " - suffix"; }) ]; "Display" } } } } Each time the text of the Similarly, each time the |
Component | shared dynamic Component Ceylon binding for a Mithril Component |
RouteResolver | shared dynamic RouteResolver Wrapper around the Mithril RouteResolver |
Router | shared Router Wrapper around the Mithril |
Template | shared Template This is the main interface that allows integrating ceylon.html and Mithril. This interface allows defining a Mithril To create a new Mithril component, simply implement the Template.build method
to return a ceylon.html This Exampleshared void run() { assert (exists root = document.body); mount { parent = root; object component satisfies Template { variable value count = 0; build() => Main { Div { H1 { "Thrillon: Mix Mithril and Ceylon Html DSL !" }, Button { attributes = [ event.click((evt) { count ++; }) ]; "``count`` clicks" } } }; } }; } |
VNode | shared dynamic VNode Ceylon binding for a Mithril VNode |
Classes | |
ExistingWatchedValue | shared ExistingWatchedValue<T> |
InputBoundValue | shared abstract InputBoundValue<T,Exists> |
InputCheckedStatus | shared InputCheckedStatus A Exampleobject satisfies Template { value checked = InputTextValue(InputType.checkbox, ExistingWatchedValue<Boolean>(false)); build(JS attrs) => Main { Div { Input { type = checked.type; attributes = [ checked.binder ]; } Button { attributes = [ event.click((evt) { // Use the input checked status to display a message window.alert(if (checked.val) then "It is checked" else "is it not checked"); }) ]; "Display" } } } } |
InputTextValue | shared InputTextValue<Exists> A Exampleobject satisfies Template { value name = InputTextValue(InputType.text, WatchedValue<String>(null)); build(JS attrs) => Main { Div { Input { type = name.type; attributes = [ name.binder ]; } Button { attributes = [ event.click((evt) { // Displays the content of the input text window.alert("Name = ``name.val else "<null>"``"); }) ]; "Display" } } } } |
SelectValue | shared SelectValue<Exists> A Exampleobject satisfies Template { value select = SelectValue(ExistingWatchedValue<String>("option1")); build(JS attrs) => Main { Div { Select { attributes = [ select.binder ]; Option { val = "option1"; "Option 1" }, Option { val = "option2"; "Option 2" }, Option { val = "option3"; "Option 3" } }, Button { attributes = [ event.click((evt) { // Displays the chosen option window.alert("Current choice = ``select.val``"); }) ]; "Display" } } } } |
TextAreaValue | shared TextAreaValue<Exists> A |
WatchedValue | shared WatchedValue<T,Exists = Null> A How it worksIf you have such a watched value: WatchedValue<Time> theTime = WatchedValue<Time>(null) ; And this code inside a Label { "Time is : ``theTime.val else ""``" } Then, in any place in your Ceylon code, if you set the time value with the following code: theTime.val = now().time(); This will update the content of the HTML label defined above. More details
value watchedList = ExistingWatchedValue<List<String>(ArrayList<String>()); ... watchedList.change((l) => l.add("aString");
|
WrappedComponent | shared WrappedComponent This class is a wrapper on a Mithril component
that can be inserted inside a |
event | shared event This top-level object provides wrapper methods for each type of HTML DOM event. This allows producing an How to use itIt is used when creating the function buttonWitOnClickHandler() => Button { attributes = [ event.click((evt) { doAnActionOnEvent(evt) }); ]; "Standard Button" } function buttonWitCustomEventHandler() => Button { attributes = [ event.custom("ontouchmove", (evt) { doAnActionOnEvent(evt) }); ]; "Touchable Button" } |
key | shared key Helper object to provide the Mithril key feature. Key provided from an already-known valueList<String> list; ... Table { TBody { for (i->item in list.indexed) Tr { attributes = [ key.set(i.string) ]; item } } }; Key provided as a functionclass Person(shared String firstName, shared String lastName) { shared String key() => "``firstName`` $$ ``lastName``"; } Set<Person> persons = nothing; Table { TBody { for (person in persons) Tr { attributes = [ key.build(person.key) ]; } } }; |
lifecycle | shared lifecycle This top-level object provides wrapper methods for each Mithril lifecycle event. This allows producing an How to use itIt is used when creating the ExamplePre { Code { clazz = "ceylon"; attributes = [ lifecycle.create((vnode) { dynamic { hljs.highlightBlock(vnode.dom); } }) ]; sourceCode } } In this example, the highlight.js highlighting
is applied to the content of the |