I have been reworking an older WebSharper jQuery Mobile app which used one sitelet (compiled to HTML file) for each JQM page. I decided to use another approach: a single-page application with JQM pages generated lazily and reused if needed. JQM has a sample which that gives an example of page reuse, so I implemented it with WebSharper. The resulting framework is easy to expand and manage.
First some helper functions for building page content:
namespace Sample
open IntelliFactory.WebSharper
open IntelliFactory.WebSharper.JQuery
open IntelliFactory.WebSharper.JQuery.Mobile
open IntelliFactory.WebSharper.Html
[<JavaScript>]
module App =
let mobile = Mobile.Instance
let HeaderDiv cont =
Div [ HTML5.Attr.Data "role" "header" ] -< cont
let ContentDiv cont =
Div [ HTML5.Attr.Data "role" "content" ] -< cont
let PageDiv id' cont =
Div [
HTML5.Attr.Data "role" "page"
Id id'
] -< cont |>! OnAfterRender (fun el ->
JQuery.Of el.Body |> Mobile.Page.Init
)
let ListViewUL cont =
UL [
HTML5.Attr.Data "role" "listview"
HTML5.Attr.Data "inset" "true"
] -< cont
Then the sample data, now stored locally, but it could be modified easily to make a web request for example using WebSharper's automatic RPC implementation.
//module App continued
type CategoryData =
{
Name : string
Description : string
Items : string list
}
let getCategoryData category =
match category with
| "animals" -> Some {
Name = "Animals"
Description = "All your favorites from aardvarks to zebras."
Items = [ "Pets"; "Farm Animals"; "Wild Animals" ] }
| "colors" -> Some {
Name = "Colors"
Description = "Fresh colors from the magic rainbow."
Items = [ "Blue"; "Green"; "Orange"; "Purple"; "Red"; "Yellow"; "Violet" ] }
| "vehicles" -> Some {
Name = "Vehicles"
Description = "Everything from cars to planes."
Items = [ "Cars"; "Planes"; "Construction" ] }
| _ -> None
Now, lets define the pages. A simple record can contain the DOM element of the page, and a function that should be run on page load (first use and reuse too). It returns a bool
deciding if we should proceed with the page change or cancel it.
//module App continued
type JQMPage =
{
Html: Element
Load: unit -> bool
}
// Page Ids
module Ids =
let [<Literal>] HomePage = "home"
let [<Literal>] ItemsPage = "items"
module Refs =
let [<Literal>] HomePage = "#home"
let [<Literal>] ItemsPage = "#items"
// State
let mutable selectedCategory = None
let HomePage =
let createListItem category text =
LI [
A [ HRef ""; Text text ]
|>! OnClick (fun _ _ ->
selectedCategory <- Some category
Refs.ItemsPage |> mobile.ChangePage
)
]
{
Html =
PageDiv Ids.HomePage [
HeaderDiv [ H1 [ Text "Categories" ] ]
ContentDiv [
H2 [ Text "Select a Category Below:" ]
ListViewUL [
createListItem "animals" "Animals"
createListItem "colors" "Colors"
createListItem "vehicles" "Vehicles"
]
]
]
Load = fun() -> true
}
let ItemsPage =
lazy
let title = H1 []
let description = P []
let itemsList = ListViewUL []
{
Html =
PageDiv Ids.ItemsPage [
HTML5.Attr.Data "add-back-btn" "true" ] -< [
HeaderDiv [ title ]
ContentDiv [
description
ListViewUL [ itemsList ]
]
]
Load = fun() ->
match getCategoryData selectedCategory.Value with
| Some categoryData ->
title.Text <- categoryData.Name
description.Text <- categoryData.Description
itemsList.Clear()
categoryData.Items |> List.iter (fun i -> (LI [Text i]) |> itemsList.Append)
JQuery.Of itemsList.Body |> Mobile.ListView.Refresh
true
| None -> false
}
let getJQMPage pageRef =
match pageRef with
| Refs.HomePage -> Some HomePage
| Refs.ItemsPage -> Some ItemsPage.Value
| _ -> None
Any pages can be initialized lazily, so the app loads as fast as possible. We will want to load the #home
page when the application starts, so there it is not needed. The helper function getJQMPage
matches the inner page hash URLs to the JQMPage
records we defined.
Finally we have to define a Web.Control
which handles the creating of pages on page change requests and loads the #home
page initially. The event handler for PageBeforeChange
checks if the DOM node for the required page has been inserted already and initializes it if it has'nt been. If Load()
would return false
we cancel the page change.
type AppControl() =
inherit Web.Control()
[<JavaScript>]
override this.Body =
Mobile.Events.PageBeforeChange.On(JQuery.Of Dom.Document.Current, fun (e, data) ->
match data.ToPage with
| :? string as pageUrl ->
match App.getJQMPage pageUrl with
| Some pageObj ->
let body = JQuery.Of "body"
let toPage =
match body.Children pageUrl with
| p when p.Length = 0 ->
let page = pageObj.Html
body.Append page.Body |> ignore
(page :> IPagelet).Render()
JQuery.Of page.Body
| p -> p
if not (pageObj.Load()) then e.PreventDefault()
| None _ -> ()
| _ -> ()
)
upcast Div [] |>! OnAfterRender (fun _ -> App.Refs.HomePage |> App.mobile.ChangePage)
WebSharper allows for an easy composition of dynamically created pages which can make creating mobile apps a quick and fun process.
Can’t find what you were looking for? Drop us a line.