1
comment
on 6/7/2013 10:09 AM

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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
//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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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.

.

Good job!

By on 6/7/2013 10:15 AM ()
IntelliFactory Offices Copyright (c) 2011-2012 IntelliFactory. All rights reserved.
Home | Products | Consulting | Trainings | Blogs | Jobs | Contact Us
Built with WebSharper