First, I will define some types and create some helper method to open dialog or toast by program:
Define dialogs and toasts
type DialogContext = { Id: int; View: NodeRenderFragment }
type DialogController = { Close: unit -> unit }
type IComponentHook with
member hook.SharedStore = hook.ServiceProvider.GetService<IShareStore>()
member hook.Dialogs = hook.SharedStore.CreateCVal(nameof hook.Dialogs, List.empty<DialogContext>)
member hook.Toasts = hook.SharedStore.CreateCVal(nameof hook.Toasts, List.empty<DialogContext>)
member hook.OpenDialog(makeView: DialogController -> NodeRenderFragment) =
let id = Random.Shared.Next()
let cts = new CancellationTokenSource()
let controller = {
Close =
fun () ->
hook.Dialogs.Publish(List.filter (fun x -> x.Id <> id))
cts.Cancel()
cts.Dispose()
}
let dialogContext = { Id = id; View = makeView controller }
hook.Dialogs.Publish(List.append [ dialogContext ])
task {
try
do! Task.Delay(-1, cts.Token)
with _ ->
()
}
member hook.OpenToast(durationMs: int, makeView: DialogController -> NodeRenderFragment) =
let id = Random.Shared.Next()
let controller = {
Close = fun () -> hook.Toasts.Publish(List.filter (fun x -> x.Id <> id))
}
let dialogContext = { Id = id; View = makeView controller }
hook.Toasts.Publish(List.append [ dialogContext ])
ignore (
task {
do! Task.Delay durationMs
controller.Close()
}
)
Then, I will display dialogs and toasts in list adaptively.
Create a container to display the dialogs and toasts
[<FunBlazorCustomElement>]
type DialogContainer() =
inherit FunComponent()
override _.Render() =
html.inject (fun (hook: IComponentHook) ->
html.fragment [
adaptiview () {
let! dialogs = hook.Dialogs
for dialog in dialogs do
div {
key dialog.Id
class' "modal modal-open"
dialog.View
}
}
div {
class' "z-[999] toast toast-top toast-end"
adaptiview () {
let! toasts = hook.Toasts
for toast in toasts do
div {
key toast.Id
toast.View
}
}
}
]
)
Fianlly, I can use it in my business logic:
Create a button to trigger a dialog, after clicked Ok we can display toasts:
Use a button to start all
button {
class' "btn btn-primary"
onclick (fun _ -> task {
do! openDialog ()
openToast 1
do! Task.Delay 1000
openToast 2
openToast 3
})
"Start some task"
}
Use dialog
let openDialog () =
hook.OpenDialog(fun ctx -> div {
class' "modal-box"
p {
class' "py-4"
"Task will finish soon."
}
div {
class' "modal-action"
button {
class' "btn btn-primary"
onclick (ignore >> ctx.Close)
"Ok"
}
}
})
Use toast
let openToast (i: int) =
hook.OpenToast(
3_000,
fun _ -> div {
class' "alert alert-success"
$"Task{i} finished successful!"
}
)