fsbolero / Bolero

Bolero brings Blazor to F# developers with an easy to use Model-View-Update architecture, HTML combinators, hot reloaded templates, type-safe endpoints, advanced routing and remoting capabilities, and more.
https://fsbolero.io
Apache License 2.0
1.06k stars 53 forks source link

Event only fires once and does not seem to execute all methods in update function #100

Closed metatron-the-chronicler closed 4 years ago

metatron-the-chronicler commented 4 years ago

Bolero version: 0.9.3-preview9

I'm trying to nest different views inside the main calendar component. However when I click the buttons for each individual day only the print statement seems to execute while no other items execute and no other commands seem to execute. The goal here is to switch between day, week and month views on various events. I would appreciate any insight into what I am not doing properly.

Types.fs

namespace Calendar
module Types =
    type TimeUnit =
        | Day of int
        | Week of int
        | Month of int
    type Message =
        | Next
        | Previous
        | Noop
        | Show of TimeUnit

    type Event={
        Name: string
        Duration: System.TimeSpan
        Location: string option
        }

Controls.fs


module rec Controls =
    open Calendar.Types
    open Elmish
    open Bolero
    open Bolero.Html
    open System

    type MenuItems = MenuItem list

    type MenuAttributes =
        { attrs: Attr list
          menuItems: MenuItems }

    type CallBacks = option<list<EventArgs -> unit>>
    //Represents an li with an aria role of menuitem
    type MenuItemAttributes =
        { attributes: Attr list
          bodyText: string }

    type MenuItem() =
        inherit ElmishComponent<MenuItemAttributes, string>()
        override __.View model dispatch = li (model.attributes @ [ "role" => "menuitem" ]) [ text model.bodyText ]

    type Menu() =
        inherit ElmishComponent<MenuAttributes, string>()

        override this.View model dispatch =
            ul (model.attrs @ [ "role" => "menu" ])
                [ forEach model.menuItems <| fun m -> ecomp<MenuItem, _, _> m.Model dispatch ]

    type DisplayAttribute =
        { attrs: Attr list
          value: TimeSpan
          format: string option }

    type TimeDisplay() =
        inherit ElmishComponent<DisplayAttribute, string>()
        override __.View model dispatch =
            let fmt =
                match model.format with
                | Some s -> s
                | None -> "h\:mm"
            div model.attrs [ span [ attr.``class`` "time-display" ] [ text <| model.value.ToString(fmt) ] ]

    type DayViewAttributes =
        { visible: bool
          timeRange: Node
          attrs: Attr list }

    type WeekView() =
        inherit ElmishComponent<DayViewAttributes, Message>()
        override __.View model dispatch =
            let q =
                if not model.visible then (model.attrs @ [ attr.``class`` "hide" ])
                else model.attrs
            table q []

    type DayView() =
        inherit ElmishComponent<DayViewAttributes, Message>()
        override __.View model dispatch =
            let q =
                if model.visible <> true then (model.attrs @ [ attr.``class`` "hide" ])
                else model.attrs
            dl q [ model.timeRange ]

    type CalendarAttributes =
        { attrs: Attr list
          containerAttributes: Attr list
          children: Node
          visible: bool
          header: Node option
          dayView: Node
          weekView: Node }

    type Calendar() =
        inherit ElmishComponent<CalendarAttributes, Message>()
        override __.View model dispatch =
            let m =
                cond model.header <| function
                | Some d -> d
                | None -> span [attr.style "display:none;"] []
            let extractView (a: 'a option) =
                cond a <| function
                | Some d -> ecomp<'a, _, _> d.Model dispatch
                | None -> span [attr.style "display:none;"] []

            let hideshow =attr.``class`` (if  model.visible <> true then  "hide" else null)

            concat
                [ div (model.containerAttributes @ [ "aria-live" => "polite" ])
                      [ m
                        model.dayView
                        model.weekView
                        div
                            ([ attr.id "calendar-grid"] @ [hideshow])
                            [ table model.attrs [ tbody [] [ model.children ] ] ] ] ]

Main.fs

module rec Main =
    open Elmish
    open Bolero
    open Bolero.Html
    open Bolero.Templating.Client
    open HelloWorld.Controls
    open System
    open Calendar.Types

    let daysInMonth month year = System.DateTime.DaysInMonth(year, month)
    let dayList max = seq { 1..max } |> Seq.toList

    type Model =
        {
            CalendarDays: int seq
            CurrentTime: DateTime
            visibility: bool
            timeSeries: (string * Event option list) list option
            dayVisible: bool
        }
    let monthChange time model =
                { model with CurrentTime = time
                             CalendarDays =
                                 daysInMonth time.Month time.Year |> dayList }

    let monthText (timeval: DateTime) =
        timeval.ToString
                ("MMMM", System.Globalization.CultureInfo.CreateSpecificCulture("en-US"))

    let genTimeSeries format =
        let ts = [ for i in 0..23 ->
                    match format with
                    | Some x ->
                        TimeSpan(i, 0, 0).ToString(x)
                    | None ->
                        TimeSpan(i, 0, 0).ToString("h:\mm")

         ]
        let bgf=[for m in ts ->
                    m,[None]]
        bgf

    let initModel =
        {
            CalendarDays =
                daysInMonth DateTime.Now.Month DateTime.Now.Year |> dayList
            CurrentTime = DateTime.Now
            visibility = true
            timeSeries = None
            dayVisible = false
        }

    let dayView model dispatch =
        let entry time =
            dt [] [  text time ]
        let grid (en: Event option list) =
            forEach en <| fun w ->
                cond w <| function
                    | Some g ->
                        dd [] [ span [ attr.``class`` "event-item" ] [ text <| g.Duration.ToString() ] ]
                    | None -> dd [attr.style "display:none;"] []
        let result =
            cond model.timeSeries <| function
                | Some x ->
                    forEach x <| fun (t, e) ->
                        let gridItems = grid e
                        concat [
                            entry t
                            gridItems
                            ]
                 | None -> empty
        ecomp<DayView,_,_>{ attrs = []; timeRange = result; visible = true; } dispatch 

    let update message model =
            match message with
                | Next ->
                    let newTime = model.CurrentTime.AddMonths(1)
                    monthChange newTime model
                | Previous ->
                    let newTime = model.CurrentTime.AddMonths(-1)
                    monthChange newTime model
                | Show d ->
                    match d with
                        | Day i ->
                            printfn "%i" i
                            let time = model.CurrentTime
                            let timeseries = Some(genTimeSeries None)
                            { model with  dayVisible = true; visibility = false; timeSeries = timeseries;CurrentTime = DateTime(time.Year, time.Month, i); (*timeSeries = timeseries*) }
                        | Month i ->
                            let time = DateTime(model.CurrentTime.Year, i, 1)
                            monthChange time model
                | Noop -> model

    let genCalendarGrid dispatch (dayseq: int seq): Node =
        let genRow col =
            tr [ attr.``class`` "calendar-row" ]
              [
                forEach col <| fun er ->
                   td [] [ span [] [ button [ //attr.href <| "#"
                                         on.click (fun _ -> dispatch (Show(Day er))) ]
                                         [ text <| er.ToString() ] ] ] ]

        let grouped = dayseq |> Seq.chunkBySize 5

        let res = forEach grouped <| genRow

        res

    let view model dispatch =
        let calendar =
            daysInMonth model.CurrentTime.Month model.CurrentTime.Year
            |> dayList
            |> genCalendarGrid dispatch
        let header = div [ attr.id "header-row" ] [
            div [ attr.``class`` "row" ] [
            span [] [ button [ on.click (fun _ -> dispatch Previous) ] [ text "<" ] ]
            ]
            div [ attr.``class`` "row" ] [
            span [] [ text <| monthText model.CurrentTime ]
            ]
            div [ attr.``class`` "row" ] [
            span [] [ button [ on.click (fun _ -> dispatch Next) ] [ text ">" ] ]
            ]
            ]
        let mjs = if (not model.visibility) && model.dayVisible then dayView model dispatch else empty
        ecomp<Calendar, _, _> { attrs = []; children = calendar
                                CalendarAttributes.header = Some header
                                visible = model.visibility
                                containerAttributes = [ attr.id "calendar-container" ]
                                dayView = mjs
                                weekView = empty} dispatch

    type MyApp() =
            inherit ProgramComponent<Model, Message>()

            override this.Program =
                Program.mkSimple (fun _ -> initModel) update view
    #if DEBUG
                |> Program.withHotReload
            #endif
OnurGumus commented 4 years ago

Can you add below code to your program so that you can see the traces:

        |> Program.withConsoleTrace
        |> Program.withErrorHandler 
            (fun (x,y) -> 
                Console.WriteLine("Error Message:" + x)
                Console.WriteLine("Exception:" + y.ToString()))

This will allow you to see all the messages all the errors and state changes. To me elmish almost mitigates most debugging needs.

metatron-the-chronicler commented 4 years ago

My string format on my timespan was broken. Thanks alot @OnurGumus.