WebSharper

I am getting this excepection on the browser, using a Doc.Select with the latets UI.Next version, this is a simple example:

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
let Main() =
    let rv = Var.Create ""
    let data = ["Option A"; "Option B"; "Option C"]
    let renderOption() = 
        Doc.Select [
            attr.``class`` "form-control"
        ] (fun f -> f) data rv

    let rv = Var.Create ""
    let input = inputAttr [attr.value ""] []
    let output = h1 []
    div [
        input
        buttonAttr [
            on.click (fun _ _ ->
                async {
                    let! data = Server.DoSomething input.Value
                    output.Text <- data
                }
                |> Async.Start
            )
        ] [text "Send"]
        hr []
        h4Attr [attr.``class`` "text-muted"] [text "The server responded:"]
        divAttr [attr.``class`` "jumbotron"] [output]
        div [renderOption()]
    ]
  • Marco Orellana

    The temporary solution is use selectAttr insetad of Doc.Select but i loose the reactive var,

    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
    
    let Main() =
        let data = ["Option A"; "Option B"; "Option C"]
        let renderOption data = 
            selectAttr [attr.``class`` "form-control"] (
                data
                |> List.map (fun item ->
                    Tags.option [text item] :> _)
            )
        let input = inputAttr [attr.value ""] []
        let output = h1 []
        div [
            input
            buttonAttr [
                on.click (fun _ _ ->
                    async {
                        let! data = Server.DoSomething input.Value
                        output.Text <- data
                    }
                    |> Async.Start
                )
            ] [text "Send"]
            hr []
            h4Attr [attr.``class`` "text-muted"] [text "The server responded:"]
            divAttr [attr.``class`` "jumbotron"] [output]
            renderOption data
        ]
    • Marco Orellana

      I found the issue, it's the reactive var cannot be declared as empty string, i don't understand why because in another dom nodes like checkboxes as radio buttons i can use a empty string without this exception.

      • qwe2
        1
        2
        
        let rv = Var.Create "Option A"
        let data = ["Option A"; "Option B"; "Option C"]

        rv's initial value has to be in the list as this will be marked as selected by default.

        • Marco Orellana

          Cool thanks, makes sense. Another cuestion if i want to update another selection box using for example "Option A" why should use handler on.change or View<'T >

          • qwe2

            Sorry I don't quite understand your question. Could you give an example code (or pseudo-code) about what you would like to achive?

            • Marco Orellana

              Sure, this is de idea i made with jQuery but i don't now how to do this with UI next:

              https://gist.github.com/morellana88/d0877485c37271d75190

              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
              
              let firstSelect (data: string list) =
                  selectAttr[
                      attr.id "first-combo"
                      attr.``class`` "form-control"
                      on.change(fun _ e ->
                          e.PreventDefault()
                          let selectedOption = JQuery.Of("#first-combo").Val() |> string
                          updateSecondSelect selectedOption "#second-combo"
                      )
                  ](
                  data
                  |> List.map (fun item -> Tags.option [text item] :> Doc)
                  )
              
              let secondSelect (data: string list) =
                  selectAttr[attr.``class`` "form-control"; attr.id "second-combo"]
                      (data
                          |> List.map (fun item -> Tags.option [text item] :> Doc)
                      )
              
              let updateSecondSelect (id: string) = 
                  let newOptions = ["Option D"; "Option E"; "Option F"]
                  JQuery.Of(id).Empty().Ignore
                  newOptions
                  |> List.iter (fun opt -> 
                      let newOption = JQuery.Of("""<option value=""" + opt + """>""" + opt + """</option>""")
                      JQuery.Of(id).Append(newOption).Ignore)
                  JQuery.Of("#second-combo").Focus().Ignore
              • qwe2

                If you want to update an element based on the value of a reactive var you should use views. I'm still not crystal clear how you want to update the second select but hope this helps:

                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
                
                open WebSharper.UI.Next
                open WebSharper.UI.Next.Html
                open WebSharper.UI.Next.Client
                
                let firstSelect (data: string list) rvSelected =
                    let rv = Var.Create data.[0]
                    Doc.Select [attr.``class`` "form-control"] id data rvSelected
                
                let secondSelect (data: string list) (rvSelected : Var<string>) (dependsOn : View<string>) =
                    dependsOn
                    |> View.Map (fun e ->
                        let l = e :: data
                        // ensure that the value of the var is in the produced list
                        // could use None here if using the select variant with
                        // default value
                        if not <| List.exists ((=) rvSelected.Value) l then
                            Var.Set rvSelected e
                        Doc.Select [attr.``class`` "form-control"] id l rvSelected)
                    |> Doc.EmbedView
                
                let render () =
                    let a = ["A"; "B"; "C"]
                    let b = ["1"; "2"]
                
                    let first  = Var.Create a.[0]
                    let second = Var.Create b.[0]
                
                    Doc.Concat [
                        firstSelect a first
                        secondSelect b second first.View
                    ]
  • qwe2

    Here's a solution that would support an empty element by default:

    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
    
    type Doc with
        static member SelectDefault attrs show (options : 'T list) (def : Var<'T option>) =
            let setIndex (i : int) (el : Dom.Element) =
                el?selectedIndex <- i
    
            let el =
                selectAttr [
                    yield on.change (fun el _ev -> 
                        let idx : int = el?selectedIndex
                        if idx >= 0 then
                            Var.Set def <| Some options.[idx])
                    yield! attrs
                ] (options
                    |> List.mapi (fun i o ->
                        Doc.Element "option" [attr.value <| string i] [text <| show o] :> _))
                    
            setIndex -1 el.Dom
            Doc.Concat [
                el
                def.View
                |> View.Map (fun v ->
                    v |> Option.iter (fun e -> 
                        let idx = List.findIndex ((=) e) options
                        setIndex idx el.Dom)
                    Doc.Empty)
                |> Doc.EmbedView
            ]
    
    let Main() =
        let rv = Var.Create None
        let data = ["Option A"; "Option B"; "Option C"]
        let renderOption() = 
            Doc.SelectDefault [
                attr.``class`` "form-control"
            ] (fun f -> f) data rv
    
        div [
            div [renderOption()]
            rv.View |> View.Map (fun e -> defaultArg e "None") |> textView
        ]