Skip to content

Templating

Loic Denuziere edited this page Nov 16, 2018 · 9 revisions

In addition to creating HTML content with F# functions, Bolero enables inserting plain HTML templates in the form of a type provider.

Invoking the type provider

The type provider Bolero.Template takes one static parameter, which is a string and can be either:

  • A plain HTML string. It is recommended to use triple-quotes to avoid escaping issues:

    type Hello = Template<"""<div id="hello">Hello, world!</div>""">
  • A path to an HTML file, relative to the project root.

    type Hello = Template<"hello.html">

The difference between the two is based on a simple heursitic: if the string starts with the character '<', it is read as plain HTML, otherwise it is read as a file path.

To instantiate a template, call its constructor and then the .Elt() method.

let hello = Hello().Elt()

Holes

Templates can also define "holes" to be filled by content defined in F#.

Holes are defined in the HTML template with the following syntax: ${HoleName}. Such a hole defines a method with the same name, which you can call before .Elt() to fill its value.

<div id="${Id}">Hello, ${Who}!</div>
type Hello = Template<"hello.html">

let hello =
    Hello()
        .Id("hello")
        .Who("world")
        .Elt()

Here are the types of holes available.

Node holes

A hole define inside normal HTML content is a Node hole. It can be filled by either a string or a Node.

<div>Hello, ${Who}!</div>
type Hello = Template<"hello.html">

// Fill with a string
let hello = Hello().Who("world").Elt()

// Fill with a Node
let hello = Hello().Who(b [] [text "world"]).Elt()

Such a hole can be defined multiple times, and the content will be duplicated accordingly.

<p>
    <i>Computer, to ${Who}:</i>
    Hello, ${Who}!
</p>
type Hello = Template<"hello.html">

let hello = Hello().Who("world").Elt()

Attribute holes

A hole defined inside an HTML attribute can only be filled by a string.

<div class="greeting ${Class}">Hello, world!</div>
type Hello = Template<"hello.html">

let hello = Hello().Class("heading").Elt()

A hole can be defined both inside an HTML attribute and in normal HTML content; in this case it can still only be filled by a string.

<label>
    ${Label}:
    <input placeholder="${Label}" />
</label>
type Hello = Template<"hello.html">

let hello = Hello().Label("First name").Elt()

Event holes

Holes defined as the value of an event attribute, eg. an attribute whose name starts with on, are treated as event handlers. They are filled by passing an anonymous function of type UIEventArgs -> unit.

<button onclick="${Greet}">Hello!</button>
type Hello = Template<"hello.html">

let hello = Hello().Greet(fun _ -> printfn "Hello, world!").Elt()

Specific events have corresponding subtypes of UIEventArgs: for example, onclick uses UIMouseEventArgs.

<button onclick="${Greet}">Hello!</button>
type Hello = Template<"hello.html">

let hello =
    Hello()
        .Greet(fun e -> printfn "Clicked at (%i, %i)" e.ClientX e.ClientY)
        .Elt()

Data binding holes

Holes defined as the value of a bind define two-way binding with the element's value. The filling method for such a hole takes two arguments:

  1. The current value, which generally comes from the Elmish model.
  2. A setter function, which generally calls the Elmish dispatch function.
<input bind="${Username}">
type Model = { username: string }

type Message =
    | SetUsername of string

type Hello = Template<"hello.html">

let hello model dispatch =
    Hello()
        .Username(model.username, fun n -> dispatch (SetUsername n))
        .Elt()

The type of the binding value depends on the element on which the bind attribute is set:

  • <input type="number"> has a number value, and can be filled either by an int or a float.
  • <input type="checkbox"> has a boolean value, and can be filled by a bool.
  • <input> with other types or no type, <textarea> and <select> have an arbitrary value, and can be filled by a string.
<input type="checkbox" bind="${IsChecked}">
type Model = { isChecked: bool }

type Message =
    | SetChecked of bool

type Hello = Template<"hello.html">

let hello model dispatch =
    Hello()
        .IsChecked(model.isChecked, fun c -> dispatch (SetChecked c))
        .Elt()

The same hole name can be reused anywhere a string hole can be used, and it will be updated accordingly.

<div>
    <input bind="${Name}">
    <p>Hello, ${Name}!</p>
</div>
type Model = { name: string }

type Message =
    | SetName of string

type Hello = Template<"hello.html">

let hello model dispatch =
    Hello()
        .Name(model.name, fun n -> dispatch (SetName n))
        .Elt()
Clone this wiki locally