FullCalendar Calendar Draggable - sydsutton/FS.FluentUI GitHub Wiki

This is a complex, contrived example with lots of printfn's and unnecessary properties to showcase FullCalendar's

image

2025-03-1410-32-04-ezgif com-video-to-gif-converter


type Event =
    | Presenter
    | Break
    | ``Team Bonding``

module Event =
    let getColors = function
        | Presenter -> "black", "orange"
        | Break -> "white", "green"
        | ``Team Bonding`` -> "black", "yellow"

    let fromText = function
        | Some "Presenter" -> Presenter
        | Some "Break" -> Break
        | Some "Team Bonding" -> ``Team Bonding``
        | _ -> Presenter

open Fable.Core.JsInterop

[<ReactComponent>]
let FullCalendar () =
    let isDialogOpen, setIsDialogOpen = React.useState false
    let newEventTitle, setNewEventTitle = React.useState ""
    let (selectedDate: DateSelectArg option), setSelectedDate = React.useState None
    let startTime, setStartTime = React.useState None
    let endTime, setEndTime = React.useState None
    let (dayEvent: Event), setDayEvent = React.useState Presenter
    let allDay, setAllDay = React.useState false
    let (calRef: IRefValue<CalendarRoot option>) = React.useRef None
    let containerEl = Browser.Dom.document.getElementById "external-events"
    let checkboxEl = Browser.Dom.document.getElementById "drop-remove"

    if containerEl <> null then
        FullCalendar.Draggable (containerEl, [
            draggable.itemSelector ".fc-event"
            draggable.minDistance 200
            draggable.eventData (fun el -> [
                    event.title el.innerText
                    event.backgroundColor "green"
                ]
            )
        ])

    let handleDateSelect =
        (fun selected ->
            let calendarApi = calRef.current.Value.getApi()
            let events = calendarApi.getEvents()
            printfn "calRef %A" (events |> Array.map (fun t -> t.title))
            setSelectedDate (Some selected)
            setIsDialogOpen true
            setStartTime (Some selected.start)
            setEndTime (Some selected.``end``)
        )

    let handleCloseDialog =
        fun _ ->
            setIsDialogOpen false
            setNewEventTitle ""
            setStartTime None
            setEndTime None
            setDayEvent Presenter

    let handleEventClick =
        fun (selected: EventClickArg) ->
            if Browser.Dom.window.confirm $"Are you sure you want to delete the event {selected.event.title}" then
                selected.event.remove ()

    let handleAddEvent =
        (fun (e: Browser.Types.Event) ->
            e.preventDefault ()

            match startTime, endTime with
            | Some startTime, Some endTime ->
                if newEventTitle <> "" && selectedDate.IsSome then
                    let calendarApi = selectedDate.Value.view.calendar
                    calendarApi.unselect ()

                    let (textColor, backgrounColor) = dayEvent |> Event.getColors

                    let newEvent = [
                        event.id newEventTitle
                        event.title newEventTitle
                        event.start startTime
                        event.end' endTime
                        event.allDay allDay
                        event.backgroundColor backgrounColor
                        event.textColor textColor
                    ]

                    let addedEventImpl=
                        calendarApi.addEvent (!!newEvent |> createObj |> unbox) None

                    addedEventImpl |> ignore
                    handleCloseDialog ()
            | _, _ -> ()
        )

    let calendarDialog =
        Fui.dialog [
            dialog.open' isDialogOpen
            dialog.onOpenChange (fun (d: DialogOpenChangeData<Browser.Types.MouseEvent>) ->
                d.``open`` |> setIsDialogOpen)
            dialog.children [
                Fui.dialogSurface [
                    Fui.dialogContent [
                        dialogContent.children [
                            Html.form [
                                prop.onSubmit handleAddEvent
                                prop.children [
                                    Fui.stack [
                                        stack.tokens [ stack.tokens.childrenGap 8 ]
                                        stack.horizontal false
                                        stack.children [
                                            Fui.dropdown [
                                                dropdown.value $"{dayEvent}"
                                                dropdown.onOptionSelect (fun (d: OptionOnSelectData) -> d.optionValue |> Event.fromText |> setDayEvent)
                                                dropdown.children [
                                                    for event in [ Presenter; Break; ``Team Bonding``] do
                                                        Fui.option [
                                                            option.value $"{event}"
                                                            option.text $"{event}"
                                                            option.children [
                                                                Fui.text $"{event}"
                                                            ]
                                                        ]
                                                ]
                                            ]
                                            Fui.input [
                                                input.type' "text"
                                                input.placeholder "Event Title"
                                                input.value newEventTitle
                                                input.onChange (fun e -> setNewEventTitle e)
                                                input.required true
                                            ]
                                            Fui.timePicker [
                                                timePicker.value ((startTime |> Option.defaultValue DateTime.Today).ToShortTimeString())
                                                timePicker.selectedTime startTime
                                                timePicker.onTimeChange (fun (t: TimeSelectionData) -> setStartTime t.selectedTime)
                                            ]
                                            Fui.timePicker [
                                                timePicker.value ((endTime |> Option.defaultValue DateTime.Today).ToShortTimeString())
                                                timePicker.selectedTime endTime
                                                match selectedDate with
                                                | Some sd ->
                                                    timePicker.dateAnchor sd.start
                                                    timePicker.startHour sd.start.Hour
                                                | None ->
                                                    prop.custom ("", "") |> unbox
                                                timePicker.onTimeChange (fun (t: TimeSelectionData) -> setEndTime t.selectedTime)
                                            ]
                                            Fui.checkbox [
                                                checkbox.isChecked allDay
                                                checkbox.onCheckedChange (fun c -> setAllDay c)
                                                checkbox.label "All Day Event"
                                            ]
                                            Fui.button [
                                                button.type' "submit"
                                                button.text "Add"
                                            ]
                                        ]
                                    ]
                                ]
                            ]
                        ]
                    ]
                ]
            ]
        ]

    let draggableEvents =
        Html.div [
            prop.id "external-events"
            prop.children [
                for i in [1..5] do
                    Html.div [
                        prop.key i
                        prop.className "fc-event fc-h-event fc-daygrid-event fc-daygrid-block-event"
                        prop.children [
                            Html.div [
                                prop.className "fc-event-main"
                                prop.children [
                                    Html.text $"My Event {i}"
                                ]
                            ]
                        ]
                    ]
                Fui.checkbox [
                    checkbox.id "drop-remove"
                    checkbox.label "Remove after drop"
                ]
            ]
        ]

    Html.div [
        prop.style [ style.width (length.vw 70) ]
        prop.children [
            draggableEvents

            FullCalendar.Calendar [
                calendar.plugins [
                    Plugin.dayGridPlugin
                    Plugin.timeGridPlugin
                    Plugin.interactionPlugin
                    Plugin.bootstrap5Plugin
                    Plugin.listPlugin
                    Plugin.multimonthPlugin
                ]
                calendar.ref calRef
                calendar.droppable true
                calendar.initialView.dayGridMonth
                calendar.eventDrop (fun i -> printfn "eventDrop %A" (i.delta.days) )
                calendar.eventChange (fun c -> printfn "event %A oldEvent %A" c.event.start c.oldEvent.start)
                calendar.editable true
                calendar.eventMaxStack 2
                calendar.dayMaxEventRows 3
                calendar.eventResize (fun info -> Browser.Dom.window.alert (info.event.title + " end is now " + info.event.``end``.ToString()))
                calendar.drop (fun (info: DropInfo) ->
                    if checkboxEl?checked = true then
                        info.draggedEl.parentNode.removeChild(info.draggedEl) |> ignore
                    else
                        ()
                )
                calendar.selectMirror true
                calendar.dropAccept (fun el -> printfn "api %A" el.innerText; true)
                calendar.nowIndicator true
                calendar.unselectAuto true
                calendar.unselect (fun unselectArg -> printfn "unselect %A" unselectArg)
                calendar.validRange [ range.start (DateTime.Today.AddDays -7); range.end' (DateTime.Today.AddDays 7)]
                calendar.selectable true
                calendar.select handleDateSelect
                calendar.eventClick handleEventClick
                calendar.themeSystem.bootstrap5
                calendar.dayMaxEvents true
                calendar.eventAdd (fun e -> printfn "eventAdd %A" (e.event.title))
                calendar.loading (fun b -> printfn "isLoading %A" b)
                calendar.buttonIcons [
                    buttonIcon.prev "chevron-left"
                ]
                calendar.eventsSet (fun (e: CalendarEvent array) -> printfn "eventsSet %A" (e |> Array.map (fun e -> e.title)))
                calendar.headerToolbar [
                    headerToolbar.start "today prev,next"
                    headerToolbar.center "title"
                    headerToolbar.end' "myCustomButton, dayGridMonth,timeGridWeek,timeGridDay,list"
                ]
                calendar.dayHeaderFormat [
                    dateFormat.weekday.short
                ]
                calendar.customButtons [
                    "myCustomButton",
                    [
                        customButton.text "Add event"
                        customButton.icon "plus-circle"
                        customButton.click (fun _ _ -> printf "Joke's on you, I don't do anything")
                    ]
                ]
                calendar.events "https://fullcalendar.io/api/demo-feeds/events.json?start=2/23/2025&end=4/5/2025"
            ]

            calendarDialog
        ]
    ]