Let me show the simplest possible self-contained example involving WebSharper sitelets that are coming with the 2.1 release. You define a website with two pages (and two actions):
type Action = | |
| Home | |
| AboutUs |
Let us quickly mash up a template for the website using F#-embedded HTML combinators. A template is just a function taking the body and decorating it:
module View = | |
let ( => ) a b = | |
A [HRef b] -< [Text a] | |
let Page title body = | |
PageContent <| fun ctx -> | |
{ | |
Page.Default with | |
Title = Some title | |
Body = | |
H1 [Text title] | |
:: | |
UL [ | |
LI ["Home" => ctx.Link Home] | |
LI ["AboutUs" => ctx.Link AboutUs] | |
] | |
:: | |
body | |
} |
Two things to notice:
- F# lets you define your own syntax, and the example makes liberal use of that (=>).
- Instead of generating URLs by hand, you ask the context to create a link to the action. This ensures safety: renaming Home action makes the project stop compiling, and moving it to a different URL keeps the links correct.
Now, you define the sitelets:
module Site = | |
let HomePage = | |
View.Page "Home" [ | |
Div [Text "Welcome to our website!"] | |
] | |
|> Sitelet.Content "/" Home | |
let AboutUsPage = | |
View.Page "About Us" [ | |
Div [Text "TODO: describe us."] | |
] | |
|> Sitelet.Content "/about" AboutUs | |
let Main = | |
Sitelet.Sum [ | |
HomePage | |
AboutUsPage | |
] |
HomePage and AboutUsPage are single-page sitelets, with a single URL and a single Action. They are combined into a website by the Sum operator.
Now, a little bit of administrative boilerplate:
type Website() = | |
interface IWebsite<Action> with | |
member this.Actions = [] | |
member this.Sitelet = Site.Main | |
[<assembly: WebsiteAttribute(typeof<Website>)>] | |
do () |
And you are done! Let’s browse:
So far so good. The pages have the expected URLs and the menu links work.
The above could have been accomplished by any reasonable web framework. Let us push the limit and spice it up a bit now. Let us add a few lines of F# that will actually compile to JavaScript:
module Client = | |
open IntelliFactory.WebSharper.Html | |
[<JavaScript>] | |
let Button label = | |
Button [Text label] | |
|>! OnClick (fun button _ -> | |
button.Text <- "CLICKED") | |
type ButtonControl(label: string) = | |
inherit Web.Control() | |
new () = new ButtonControl("unlabelled") | |
[<JavaScript>] | |
override this.Body = Button label :> _ |
So there is a button that changes its title when clicked. AND there is a control. Now, this while the Button lives on the client-side completely (constructs DOM nodes, in fact), the Control executes a quantum leap: the constructor executes on the server, and the body on the client. But that means you can use the Control to glue the server and the client together. Let us update the AboutUs page:
let AboutUsPage = | |
View.Page "About Us" [ | |
Div [Text "TODO: describe us."] | |
Div [new Client.ButtonControl("Click me!")] | |
] | |
|> Sitelet.Content "/about" AboutUs |
That’s it. The user will now see a clickable, JavaScript-enabled, F#-implemented button, right where you expect it. No script tags to worry about, no dependency chasing, no “ondocumentready” worries, it just works:
Below is the complete listing. As soon as the 2.1 release becomes public, you will able to enjoy running it yourself. Stay tuned!
namespace WebSharperSiteletsProject | |
open System | |
open System.IO | |
open System.Web | |
open IntelliFactory.Html | |
open IntelliFactory.WebSharper | |
open IntelliFactory.WebSharper.Sitelets | |
type Action = | |
| Home | |
| AboutUs | |
module View = | |
let ( => ) a b = | |
A [HRef b] -< [Text a] | |
let Page title body = | |
PageContent <| fun ctx -> | |
{ | |
Page.Default with | |
Title = Some title | |
Body = | |
H1 [Text title] | |
:: | |
UL [ | |
LI ["Home" => ctx.Link Home] | |
LI ["AboutUs" => ctx.Link AboutUs] | |
] | |
:: | |
body | |
} | |
module Client = | |
open IntelliFactory.WebSharper.Html | |
[<JavaScript>] | |
let Button label = | |
Button [Text label] | |
|>! OnClick (fun button _ -> | |
button.Text <- "CLICKED") | |
type ButtonControl(label: string) = | |
inherit Web.Control() | |
new () = new ButtonControl("unlabelled") | |
[<JavaScript>] | |
override this.Body = Button label :> _ | |
module Site = | |
let HomePage = | |
View.Page "Home" [ | |
Div [Text "Welcome to our website!"] | |
] | |
|> Sitelet.Content "/" Home | |
let AboutUsPage = | |
View.Page "About Us" [ | |
Div [Text "TODO: describe us."] | |
Div [new Client.ButtonControl("Click me!")] | |
] | |
|> Sitelet.Content "/about" AboutUs | |
let Main = | |
Sitelet.Sum [ | |
HomePage | |
AboutUsPage | |
] | |
type Website() = | |
interface IWebsite<Action> with | |
member this.Actions = [] | |
member this.Sitelet = Site.Main | |
[<assembly: WebsiteAttribute(typeof<Website>)>] | |
do () |