Functional Reactive Programming and HTML
WebSharper.UI is a library providing a novel, practical, and user-friendly approach to UI reactivity. It includes:
- An HTML library usable on both server and client sides, allowing you to build HTML pages either by calling F# functions to create elements or by using template HTML files.
- A reactive layer for expressing user inputs and values computed from them as time-varying values, related to Functional Reactive Programming (FRP). This reactive system integrates with the HTML library to create reactive documents. If familiar with React, you'll notice similarities: instead of explicitly inserting, modifying, or removing DOM nodes, you return a value representing a DOM tree based on inputs. The main difference is that these inputs are nodes of the reactive system, not a single state value associated with the component.
- Client-side routing using the same endpoint type declaration as WebSharper server-side routing.
This page is an overview of the capabilities of WebSharper.UI.
Get the package from NuGet: WebSharper.UI.
Using HTML
WebSharper.UI's core type for HTML construction is Doc
. A Doc can represent a single DOM node (element, text), but it can also be a sequence of zero or more nodes. This allows you to treat equally any HTML snippet that you want to insert into a document, whether it consists of a single element or not.
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 the reactive page to learn more about this.
Constructing HTML
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 attributes and child nodes, of type Attr
and Doc
respectively.
Additionally, the text
function creates a text node.
Result:
Some HTML tags, such as option
, collide with standard library names and are therefore only located in the Tags
submodule.
Result:
One thing to note is that the tag functions described above actually return a value of type Elt
, 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; you can see an example of this below in the example for Doc.Empty
.
Additional functions in the Doc
can 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.Result:
-
Doc.Append
creates a Doc consisting of the concatenation of two Docs.Result:
For the mathematically enclined, the functions Doc.Empty
and Doc.Append
make Docs a monoid.
-
Doc.Concat
generalizesAppend
by concatenating a sequence of Docs.Result:
-
Doc.Element
creates an element with the given name, attributes and children. It is equivalent to the function with the same name from theHtml
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).Result:
-
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.Result:
Attrs
To create attributes, use corresponding functions from the attr
submodule.
Result:
Some attributes, notably class
and type
, are also F# keywords, so they need to be wrapped in double backquotes.
Result:
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).
Result:
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.Result:
-
Attr.Append
combines two attributes.Result:
-
Attr.Concat
combines a sequence of attributes.Result:
-
Attr.Create
creates a single attribute. It is equivalent to the function with the same name from theattr
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
.Result:
Event handlers
A special kind of attribute is event handlers. They can be created using functions from the on
submodule.
The handler function takes two arguments:
- The element itself, as a native
Dom.Element
; - The triggered event, as a native
Dom.Event
.
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.
HTML on the client
To insert a Doc into the document on the client side, use the Doc.Run*
family of functions from the module WebSharper.UI.Client
. Each of these functions has two variants: one directly taking a DOM Element
or Node
, and the other suffixed with ById
taking the id of an element as a string.
-
Doc.Run
andDoc.RunById
insert a given Doc as the child(ren) of a given DOM element. Note that it replaces the existing children, if any. -
Doc.RunAppend
andDoc.RunAppendById
insert a given Doc as the last child(ren) of a given DOM element. -
Doc.RunPrepend
andDoc.RunPrependById
insert a given Doc as the first child(ren) of a given DOM element. -
Doc.RunAfter
andDoc.RunAfterById
insert a given Doc as the next sibling(s) of a given DOM node. -
Doc.RunBefore
andDoc.RunBeforeById
insert a given Doc as the previous sibling(s) of a given DOM node. -
Doc.RunReplace
andDoc.RunReplaceById
insert a given Doc replacing a given DOM node.
HTML on the server
On the server side, using sitelets, you can create HTML pages from Docs by passing them to the Body
or Head
arguments of Content.Page
.
By opening WebSharper.UI.Server
, you can also just pass a full page to Content.Page
. This is particularly useful together with templates.
To include client-side elements inside a page, use the client
method, from inside WebSharper.UI.Html
.