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.


CONTENT

  1. What does it look like?
  2. Basics
  3. Event handlers
  4. Watched values
  5. Bi-directional bound values
  6. Routing
  7. Javascript utilies
  8. Packaging
  9. Dom access
  10. External component reuse
  11. Lifecycle handlers

WHAT DOES IT LOOK LIKE?

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.


BASICS

The module defines bindings for some of the main Mithril API calls and structures:

and a new type of Mithril component based on the ceylon.html module:

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.


EVENT HANDLERS

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.


WATCHED VALUES

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.


BI-DIRECTIONAL BOUND VALUES

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.


ROUTING

Mithril routing is provided by the router object which satisfies the Router interface.


JAVASCRIPT UTILITIES

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;

PACKAGING

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();
 });

DOM ACCESS

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.

Example
 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.


EXTERNAL COMPONENT REUSE

Thrillon allows reusing external Mithril components by simply inserting a WrappedComponent inside the ceylon.html tree-like structure.


LIFECYCLE HANDLERS

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.

How to use it

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.

Example
 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.


Platform: JavaScript
Packages
herd.thrillon
herd.thrillon.jsutils

Utilities that simplify the work with raw Javascript variables (objects or arrays)

Dependencies
ceylon.html1.3.3
ceylon.interop.browser1.3.3
mithril1.1.6
Values
eventshared event event

This top-level object provides wrapper methods for each type of HTML DOM event.

This allows producing an AttributeEntry from the corresponding event handler implemented as a Ceylon function type.

How to use it

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.

function buttonWitOnClickHandler() => Button {
     attributes = [
        event.click((evt) {
            doAnActionOnEvent(evt)
        });
     ];
     "Standard Button"
}

function buttonWitCustomEventHandler() => Button {
     attributes = [
        event.custom("ontouchmove", (evt) {
            doAnActionOnEvent(evt)
        });
     ];
     "Touchable Button"
}
keyshared key key

Helper object to provide the Mithril key feature.

Key provided from an already-known value

 List<String> list;
 ...
 Table {
     TBody {
         for (i->item in list.indexed)
         Tr {
             attributes = [ key.set(i.string) ];
             item
         }
     }
 };

Key provided as a function

 class 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) ];
         }
     }
 };
lifecycleshared lifecycle lifecycle

This top-level object provides wrapper methods for each Mithril lifecycle event.

This allows producing an AttributeEntry from the corresponding event handler implemented as a Ceylon function type.

How to use it

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.

Example

 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.

routershared Router router
Functions
attachDomshared AttributeEntry attachDom()

Function that should be called as part of the Html Node attributes to be able to retrieve the current dom node from a Html Node through the dom() function.

Example

 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;
buildBindershared AttributeEntry buildBinder<T, Exists>(WatchedValue<T,Exists> watchedValue, String boundAttributeName, AttributeEntry updateEvent(void code(Event evt)), T|Exists extractFromDom(HTMLElement dom))
given T of Boolean | Integer | Float | String
given Exists satisfies Null

Utility function to help construct the binder when building new types of BoundValues

domshared DomNode? dom(Node? node)

Function that retrieves the current dom node from a Html Node, assuming that it was previously requested with the attachDom() function

mountshared 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()

vnodeshared 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
Argsshared dynamic Args

Wrapper on a JS object to be used inside the Template.build() function

BoundValueshared BoundValue<T,Exists>
given T satisfies Object
given Exists satisfies Null

A BoundValue provides a way to associate a value with an Html node, and bidirectionally bind it to a propety of the html node.

The BoundValue interface :

  • is based on a WatchedValue
  • defines a binder that is an AttributeEntry and that should be passed to the attributes of the Html node it is bound to.

Several implementations are provided for the most common use-cases: TextAreaValue, InputTextValue, InputCheckedStatus, SelectValue

How it works

Consider 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 TextArea Html element is changed by the user, it is automatically updated in the textArea.val value.

Similarly, each time the textArea.val is assigned a new text value, it updates the text value of the TextArea Html element.

Componentshared dynamic Component

Ceylon binding for a Mithril Component

RouteResolvershared dynamic RouteResolver

Wrapper around the Mithril RouteResolver

Routershared Router

Wrapper around the Mithril m.route methods

Templateshared Template

This is the main interface that allows integrating ceylon.html and Mithril.

This interface allows defining a Mithril Component based on a Html tree returned as a Node by the build formal method.

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.

Example

     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"
                             }
                         }
                     };
             }
         };
     }
VNodeshared dynamic VNode

Ceylon binding for a Mithril VNode

Classes
ExistingWatchedValueshared ExistingWatchedValue<T>
given T satisfies Object
InputBoundValueshared abstract InputBoundValue<T,Exists>
given T satisfies Object
given Exists satisfies Null
InputCheckedStatusshared InputCheckedStatus

A BoundValue that can be used with a Input Html node and bound to its checked boolean attribute.

Example

   object 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"
           }
         }
       }
   }
InputTextValueshared InputTextValue<Exists>
given Exists satisfies Null

A BoundValue that can be used with a Input Html node and bound to its value text attribute.

Example

   object 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"
           }
         }
       }
   }
SelectValueshared SelectValue<Exists>
given Exists satisfies Null

A BoundValue that can be used with a Select Html node.

Example

   object 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"
           }
         }
       }
   }
TextAreaValueshared TextAreaValue<Exists>
given Exists satisfies Null

A BoundValue that can be used with a TextArea Html node. Its usage is shown as an example in the main documentation of BoundValue

WatchedValueshared WatchedValue<T,Exists = Null>
given T satisfies Object
given Exists satisfies Null

A WatchedValue allows defining values that will trigger a Mithril redraw event (and finally a DOM update) whenever it is changed.

How it works

If you have such a watched value:

WatchedValue<Time> theTime = WatchedValue<Time>(null) ;

And this code inside a Template:

   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

  • There is a variant named ExistingWatchedValue that can be used when the value is known from the begining, and cannot be null.

  • If the watched value is a list for example, you may want to call an operation on this list to change it. For this you can use the change method like this:

value watchedList = ExistingWatchedValue<List<String>(ArrayList<String>());
...
watchedList.change((l) => l.add("aString");
  • You can use the optional onChange parameter to add some other action to be done after the value is changed, and before DOM redraw operation is triggered.

  • If you need to set the value without triggering a DOM redraw (inside an event handler for example), just use the setQuietly method.

WrappedComponentshared WrappedComponent

This class is a wrapper on a Mithril component that can be inserted inside a ceylon.html tree structure as any other HTML Node

eventshared event

This top-level object provides wrapper methods for each type of HTML DOM event.

This allows producing an AttributeEntry from the corresponding event handler implemented as a Ceylon function type.

How to use it

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.

function buttonWitOnClickHandler() => Button {
     attributes = [
        event.click((evt) {
            doAnActionOnEvent(evt)
        });
     ];
     "Standard Button"
}

function buttonWitCustomEventHandler() => Button {
     attributes = [
        event.custom("ontouchmove", (evt) {
            doAnActionOnEvent(evt)
        });
     ];
     "Touchable Button"
}
keyshared key

Helper object to provide the Mithril key feature.

Key provided from an already-known value

 List<String> list;
 ...
 Table {
     TBody {
         for (i->item in list.indexed)
         Tr {
             attributes = [ key.set(i.string) ];
             item
         }
     }
 };

Key provided as a function

 class 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) ];
         }
     }
 };
lifecycleshared lifecycle

This top-level object provides wrapper methods for each Mithril lifecycle event.

This allows producing an AttributeEntry from the corresponding event handler implemented as a Ceylon function type.

How to use it

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.

Example

 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.