-
Notifications
You must be signed in to change notification settings - Fork 54
Templating
In addition to creating HTML content with F# functions, Bolero enables inserting plain HTML templates in the form of a 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()
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.
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()
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()
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()
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:
- The current value, which generally comes from the Elmish model.
- 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 anint
or afloat
. -
<input type="checkbox">
has a boolean value, and can be filled by abool
. -
<input>
with other types or no type,<textarea>
and<select>
have an arbitrary value, and can be filled by astring
.
<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()