#
HTML
Whether for your SPA or your client-server application, you will at some point undoubtedly be needing to work with HTML. To do so, you need to add WebSharper.UI
to your project:
dotnet add package WebSharper.UI
WebSharper.UI's core type for working with HTML is Doc
. A Doc can represent a single DOM node (an element or plain text), or sequence of zero or more nodes.
Additionally, client-side Docs can be reactive. A same Doc can consist of different elements at different moments in time, depending on user input or other variables. See
There are three main types of uses for your HTML:
HTML sent from the server to fulfill an incoming request.
#
Docs
#
HTML functions (aka. combinators)
The main means of creating Docs is by using the functions in the WebSharper.UI.Html
module. Every HTML element has a dedicated function, such as div
or p
, which takes a sequence of Attr
) and a sequence of child nodes (of type Doc
). Additionally, the text
function creates a text node.
Elt
vs Doc
You may sometimes need stronger guarantees about Doc
values, for instance, when there is a one-to-one correspondence to actual DOM nodes. To represent these "actual" HTML fragements, you can use Elt
values.
which is a subtype of Doc
that is guaranteed to always consist of exactly one element and provides additional APIs such as Dom
to get the underlying Dom.Element
. This subtyping means that you will sometimes need to upcast the result of such a function with :> Doc
to appease the compiler.
open WebSharper.UI.Html
let myDoc =
div [] [
h1 [] [text "Functional Reactive Programming and HTML"]
p [] [text "WebSharper.UI is a library providing a novel, pragmatic and convenient approach to UI reactivity. It includes:"]
ul [] [
li [] [text "..."]
]
]
<div>
<h1>Functional Reactive Programming and HTML</h1>
<p>WebSharper.UI is a library providing a novel, pragmatic and convenient
approach to UI reactivity. It includes:</p>
<ul>
<li>...</li>
</ul>
</div>
Some HTML tags, such as option
, collide with standard library names and are therefore only located in the Tags
submodule.
let myDropdown =
select [] [
Tags.option [] [text "First choice"]
Tags.option [] [text "Second choice"]
Tags.option [] [text "Third choice"]
]
<select>
<option>First choice</option>
<option>Second choice</option>
<option>Third choice</option>
</select>
The following sections list some notable functions in Doc
that you can use to create or combine Docs.
#
Doc.Empty
Creates a Doc consisting of zero nodes. This can be useful for example when you may not need to insert an element depending on a condition.
let myForm (withDropdown: bool) =
form [] [
input [attr.name "name"] []
if withDropdown then myDropdown else Doc.Empty
]
<form>
<input name="name" />
</form>
or:
<form>
<input name="name" />
<!-- ... contents of myDropdown here ... -->
</form>
#
Doc.Append
Creates a Doc consisting of the concatenation of two Docs.
let titleAndBody =
Doc.Append
(h1 [] [text "Functional Reactive Programming and HTML"])
(p [] [text "WebSharper.UI is a library providing ..."])
<h1>Functional Reactive Programming and HTML</h1>
<p>WebSharper.UI is a library providing ...</p>
For the mathematically inclined, the functions Doc.Empty
and Doc.Append
make Docs a monoid.
#
Doc.Concat
Generalizes
Append
by concatenating a sequence of Docs.
let thisPage =
Doc.Concat [
h1 [] [text "Functional Reactive Programming and HTML"]
p [] [text "WebSharper.UI is a library providing ..."]
ul [] [
li [] [text "..."]
]
]
<h1>Functional Reactive Programming and HTML</h1>
<p>WebSharper.UI is a library providing ...</p>
<ul>
<li>...</li>
</ul>
#
Doc.Element
Creates an element with the given name, attributes and children. It is equivalent to the function with the same name from the
Html
module. This function is useful if the tag name is only known at runtime, or if you want to create a non-standard element that isn't available inHtml
. The following example creates a header tag of a given level (h1
,h2
, etc).
let makeHeader (level: int) (content: string) =
Doc.Element ("h" + string level) [] [ text content ]
<h1>content...</h1>
or
<h2>content...</h2>
or etc.
#
Doc.Verbatim
Creates a Doc from plain HTML text.
Security warning!
This function does not perform any checks on the contents, and can be a code injection vulnerability if used improperly. We recommend avoiding it unless absolutely necessary, and properly sanitizing user inputs if you do use it. If you simply want to use HTML syntax instead of F# functions, take a look at templating.
let plainDoc = """
<h1 onclick="alert('And it is unsafe!')">
This is plain HTML!
</h1>"""
<h1 onclick="alert('And it is unsafe!')">
This is plain HTML!
</h1>
#
Attrs
To create attributes, use corresponding functions from the attr
submodule.
let myFormControl =
select [attr.name "mySelect"] [
Tags.option [attr.value "first"] [text "First choice"]
Tags.option [attr.value "second"] [text "Second choice"]
Tags.option [
attr.value "third"
attr.selected "selected"
] [text "Third choice"]
]
<select name="mySelect">
<option value="first">First choice</option>
<option value="second">Second choice</option>
<option value="third" selected="selected">Third choice</option>
</select>
Some attributes, notably class
and type
, are also F# keywords, so they need to be wrapped in double backquotes.
let myMain =
div [attr.``class`` "main"] [text "..."]
<div class="main">...</div>
HTML5 also defines any attribute whose names starts with data-
as a valid custom attribute. You can create such an attribute using the function data-
from module attr
(backquoted since it contains a non-standard character).
let myEltWithData =
div [attr.``data-`` "uid" "myDiv"] [text "..."]
<div data-uid="myDiv">...</div>
Like Doc
, a value of type Attr
can represent zero, one or more attributes. The functions in the Attr
module can create such non-singleton attributes.
#
Attr.Empty
Creates an empty attribute. This can be useful for example when you may not need to insert an attribute depending on a condition.
let makeInput (initialValue: option<string>) =
let valueAttr =
match initialValue with
| Some v -> attr.value v
| None -> Attr.Empty
input [valueAttr] []
<input value="initialValue..." />
or
<input />
#
Attr.Append
Combines two attributes.
let passwordAttr =
Attr.Append (attr.``type`` "password") (attr.placeholder "Password")
type="password" placeholder="Password"
#
Attr.Concat
Combines a sequence of attributes.
let passwordAttr =
Attr.Concat [
attr.``type`` "password"
attr.placeholder "Password"
attr.``class`` "pw-input"
]
type="password" placeholder="Password" class="pw-input"
#
Attr.Create
Creates a single attribute. It is equivalent to the function with the same name from the
attr
module. This function is useful if the attribute name is only known at runtime, or if you want to create a non-standard attribute that isn't available inattr
.
let eltWithNonStandardAttr =
div [Attr.Create "my-attr" "my-value"] [text "..."]
<div my-attr="my-value">...</div>
#
Event handlers
Event handlers can be added using the functions from the on
submodule.
let myButton =
button [on.click (fun _ _ -> JS.Alert "Hi!")] [text "Click me!"]
The handler function takes two arguments:
- The element itself on which the event was raised, as a native
Dom.Element
, and - The event arguments, as a subtype of the native
Dom.Event
.
open WebSharper.JavaScript
let myButton =
button [
attr.id "my-button"
on.click (fun e args ->
JS.Alert $"You clicked %s{e.Id} at x = %i{args.ClientX}, y = %i{args.ClientY}."
)
] [text "Click me!"]
In addition to the standard HTML events, on.afterRender
is a special handler that is called by WebSharper after inserting the element into the DOM.