Aug 2nd, 2016 - written by Kimserey with .
WebSharper.UI.Next comes with a simple template engine which can be used to build doc
elements. It is built using a F# typeprovider therefore gives typesafety for templating.
If you never used WebSharper or WebSharper.UI.Next before, I published a tutorial few months ago on how WebSharper works and how you can use it to create SPA’s https://kimsereyblog.blogspot.co.uk/2015/08/single-page-app-with-websharper-uinext.html.
WebSharper official documentation can be found here https://github.com/intellifactory/websharper.ui.next/blob/master/docs/Templates.md.
In this post I will explain some of the functionalities of WebSharper.UI.Next template and give specific examples to showcase how they can be used. This post will be compose by five parts:
1
2
3
4
5
1. Get started with templates
2. Template holes
3. Sub templates
4. On click event
5. Value bindings
The complete code can be found on GitHub - https://github.com/Kimserey/WSTemplate/blob/master/WSTemplate/Client.fs
Let’s start by a hello world template.
Create a hello.html
file containing the following div:
1
2
<!-- hello.html -->
<div>Hello world</div>
Then create a type which will load the template and provide a type for that template.
I suppose that’s why they called it typeprovider, it provides a type for a resource given, here html resource.
1
2
3
open WebSharper.UI.Next
type Hello = Templating.Template<"hello.html">
The Hello
type can then be used anywhere to create a div
containing Hello world
.
1
2
3
let main =
Hello.Doc()
|> Doc.RunById "main"
This code will produce a hello world
and place it in the html element of id main
.
From here we can already see how templates can be reused to quickly compose and create blocks of elements.
But static templates like this hello world
have very limited usage, UI.Next
templates provide much more functionalities.
It also allows to specify special tokens which can be used to modify the template.
One of these tokens are the holes
which we are going to view next.
When you define a template, the whole html
page becomes the template and is rendered.
Holes
are used to specify places where you need to insert extra doc
’s or elt
’s.
They can be defined using the attributes data-hole
or data-replace
.
data-hole
can be placed on elements where you want to provide your own elements.
For example, if you have a div
which represents a list and wish to create a reusable componant for it, you could do this:
1
2
3
<!--list.html-->
<ul data-hole="Links">
</ul>
Using this as template, you can then define the following:
1
type List = Templating.Template<"list.html">
1
2
3
4
5
let main =
List.Doc(Links = [
li [ text "Hello 1" ]
li [ text "Hello 2" ]
]) |> Doc.RunById "main"
Here we can observe that the typeprovider template added the Links
as argument of the Doc()
function.
Using the template, we get typesafety.
data-replace
can be used when you want to replace the whole element instead of the content only.
A typical example is when there is no parent to the element you need to replace in the current template.
1
2
3
<!--description.html-->
<h1>Some title</h1>
<div data-replace="Content"></div>
1
type Description = Templating.Template<"description.html">
1
2
3
4
let main =
Description.Doc(Content = [
p [ text "Something..." ]
]) |> Doc.RunById "main"
Sub templates are used to create reusable child components.
A typical example would be a list-group with two child list-item, a normal and an active list-item.
Sub templates can be defined usng data-children-template
or data-template
.
data-children-template
means that the content of the elements will be available as sub template.
1
2
3
4
5
6
7
8
<!--list-group.html-->
<div class="list-group">
<a href="#" class="list-group-item" data-hole="FirstBody" data-children-template="Item">
<h4 class="list-group-item-heading">${Title}</h4>
<p class="list-group-item-text"></p>
</a>
<a href="#" class="list-group-item" data-hole="SecondBody"></a>
</div>
Here we define a template composed by the child elements given by the first anchor tag.
Item
template can then be used in SecondBody
as well.
1
type ListGroup = Templating.Template<"list-group.html">
1
2
3
4
5
let main =
ListGroup.Doc(
FirstBody = [ ListGroup.Item.Doc(Title = "First") ],
SecondBody = [ ListGroup.Item.Doc(Title = "Second") ]
) |> Doc.RunById "main"
And alternative way to define sub template is data-template
.
data-template
means that the element itself plus the child elements will be available as sub template.
1
2
3
4
5
6
7
8
9
<!--list-group-2.html-->
<div class="list-group" data-hole="List">
<a href="#" class="list-group-item" data-template="ListItem">
<div>Some content</div>
</a>
<a href="#" class="list-group-item active" data-template="ActiveListItem">
<div>Some active content</div>
</a>
</div>
For example here we defined two templates ListItem
and ActiveListItem
which will produce difference <a>
.
We also defined a hole List
where we will insert the list items.
1
type ListGroup2 = Templating.Template<"list-group-2.html">
1
2
3
4
5
6
7
ListGroup2.Doc(
[
ListGroup2.ListItem.Doc()
ListGroup2.ListItem.Doc()
ListGroup2.ActiveListItem.Doc()
]
) |> Doc.RunById "main"
On click events are handled using the attribute data-click-event
.
It can be placed on any element to handle click events.
1
<button class="btn btn-block btn-lg btn-success" data-event-click="Send">Send</button>
1
type Button = Templating.Template<"button.html">
1
2
3
4
5
Button.Doc(
Send = fun el ev ->
// do something here
()
) |> Doc.RunById "main"
The data-event-click
provides a typesafe way to define callbacks. The Send
function is called with the Dom.Element
and the Dom.Event
.
Lastely string can be bound to the templae via simple markups.
Static string can be inserted using ${Value}
and dynamic string (reactive variable views) can be inserted using $!{Value}
.
For more details on Views, I posted a more in depth tutorial about Views
- https://kimsereyblog.blogspot.co.uk/2016/03/var-view-lens-listmodel-in-uinext.html
${Value}
is used to pass string values to the template.
It can be used on attribute values or text content.
1
2
3
4
5
<!--value.html-->
<a href="${Href}" class="list-group-item ${ExtraCls}">
<h4 class="list-group-item-heading">${Title}</h4>
<p class="list-group-item-text">${Text}</p>
</a>
For example, here we use it to set the href
attribute and to add an extra css class.
We also use it to set the title and text content.
1
type Value = Templating.Template<"value.html">
1
2
3
4
5
6
Value.Doc(
Href = "#",
ExtraCls = "test",
Title = "Title",
Text = "Content"
) |> Doc.RunById "main"
$!{Value}
is used to pass reactive Views to the template.
1
2
3
<!--value-2.html-->
<div>$!{Text}</div>
<button data-event-click="OnClick">Click</button>
1
type Value2 = Templating.Template<"value-2.html">
1
2
3
4
5
6
let text = Var.Create "Not clicked"
Value2.Doc(
Text = text.View,
OnClick = fun _ _ -> Var.Set text "Clicked!"
) |> Doc.RunById "main"
Text
is bound to a reactive View, when the button is clicked, the Var
is set to Clicked!
which propagate the changes to the View
and to the html doc.
The complete code can be found on GitHub - https://github.com/Kimserey/WSTemplate/blob/master/WSTemplate/Client.fs
There are times where defining HTML templates is quicker than composing elements with WebSharper.UI.Next.Html
combinators.
WebSharper provides both solutions where both are typesafe.
UI.Next template is a powerful tool with a small amount of features, it handles majority of the scenarios.
Hope you enjoyed reading this post as much as I enjoyed writing it.
As always, if you have any comments leave it here or hit me on Twitter https://twitter.com/Kimserey_Lam.
If you like reading about WebSharper, I published other extremely interesting topics:
Var
, View
, Lens
: https://kimsereyblog.blogspot.co.uk/2016/03/var-view-lens-listmodel-in-uinext.htmlWarp
: https://kimsereyblog.blogspot.co.uk/2016/03/prototyping-web-app-made-easy-with.html