diff --git a/README.md b/README.md index 805a9e6..2c4b1d6 100644 --- a/README.md +++ b/README.md @@ -126,8 +126,7 @@ Some important notes: Signals defined later in the DOM tree override those defin - [Binding to Signals](#signal-binding) - [Events and Triggers](#events-and-triggers) - [Actions and Functions](#actions-and-functions) -- [Miscellaneous Actions](#miscellaneous-actions) -- [When to $](#_when-to-_) +- [When to $](#when-to-) ## _Attribute Index_ @@ -136,15 +135,14 @@ Some important notes: Signals defined later in the DOM tree override those defin - [data-class](#dsclass--data-class) - [data-computed](#dscomputed--data-computed) - [data-effect](#dseffect--data-effect) -- [data-ignore](#dsignore-dsignorethis-dsignoremorph--data-star-ignore) +- [data-ignore](#dsignore--dsignoreself--dsignoremorph--data-star-ignore) - [data-indicator](#dsindicator--data-indicator) - [data-json-signals](#dssignals--dssignal--data-signals) +- [data-init](#dsinit--data-init) - [data-on](#dsonevent--data-on) - [data-on-intersect](#dsonintersect--data-on-intersect) - [data-on-interval](#dsoninterval--data-on-interval) -- [data-on-load](#dsonload--data-on-load) - [data-on-signal-patch](#dsonsignalpatch--dsonsignalpatchfilter--data-on-signal-patch) -- [data-on-signal-patch-filter](#dsonsignalpatch--dsonsignalpatchfilter--data-on-signal-patch) - [data-ref](#dsref--data-ref) - [data-show](#dsshow--data-show) - [data-signals](#dssignals--dssignal--data-signals) @@ -228,7 +226,7 @@ Example: setting the innerText of a `
` to a value that is updated by a serv ### [Ds.bind : `data-bind`](https://data-star.dev/reference/attributes#data-bind) Creates a two-way binding from a signal to the "value" of an HTML "input" element. Can be placed on any HTML element on which data can be input or choices -selected (e.g. `input`, `textarea`, `select`, `checkbox` and `radio` elements, as well as web components. (Although not necessary, you can find the `switch` statement in the +selected (e.g. `input`, `textarea`, `select`, `checkbox` and `radio` elements, as well as web components. Although not necessary, you can find the `switch` statement in the [source](https://github.com/starfederation/datastar/blob/main/library/src/plugins/attributes/bind.ts) to see how signals are translated). The signal will be created if it does not already exist. And the type of the signal is preserved during binding; if an element's value changes, the signal value is automatically converted to match the original (see the [documentation](https://data-star.dev/reference/attributes#data-bind) for an example.) @@ -285,6 +283,16 @@ Elem.div [ Ds.style "display" "$hiding && 'none'" ] [ Text.raw "Might be hiding" Events and triggers result in [Datastar expressions](https://data-star.dev/guide/datastar_expressions) being executed. This can result in signal changes and other expressions being run. Example: clicking a button to send a request or an element scrolling into view. +### [Ds.init : `data-init`](https://data-star.dev/reference/attributes#data-init) + +Runs an expression when the element is loaded into the DOM. **Important:** when patching elements, +`ElementPatchMode.Replace` the [Datastar expression](https://data-star.dev/guide/datastar_expressions) +will be fired a second time, but will not with `ElementPatchMode.Outer`. + +```fsharp +Elem.div [ Ds.init (Ds.get "/moreAgents") ] [] +``` + ### [Ds.onEvent : `data-on`](https://data-star.dev/reference/attributes#data-on) Attaches an event listener to an element, executing a [Datastar expression](https://data-star.dev/guide/datastar_expressions) whenever the event is triggered. @@ -297,7 +305,7 @@ Elem.div [ Ds.onEvent("mouseenter", "$show = !$show"); Ds.onEvent("mouseexit", " ```fsharp Elem.button [ Ds.onClick "$show = !$show" ] [ Text.raw "Peek-a-boo!" ] -Elem.div [ Ds.onLoad (Ds.get "/edit") ] [] +Elem.div [ Ds.init (Ds.get "/edit") ] [] ``` #### `data-on` Modifiers @@ -310,9 +318,9 @@ Modifiers allow you to alter the behavior when events are triggered. (Modifiers | Passive // * - can only be used with built-in events | Capture // * - can only be used with built-in events | Delay of TimeSpan - | DelayMs of int // identical to Delay, but just milliseconds - | Debounce of Debounce // timespan, leading, and notrail - | Throttle of Throttle // timepan, noleading, and trail + | DelayMs of int // identical to Delay, but using milliseconds instead + | Debounce of Debounce // timespan, leading, and notrailing + | Throttle of Throttle // timepan, noleading, and trailing | ViewTransition | Window | Outside @@ -329,16 +337,7 @@ Elem.div [ Results in: ```html -
-``` - -### [Ds.onLoad : `data-on-load`](https://data-star.dev/reference/attributes#data-on-load) - -Runs an expression when the element is loaded into the DOM. **Important:** when patching elements, `ElementPatchMode.Replace` the [Datastar expression](https://data-star.dev/guide/datastar_expressions) -will be fired a second time, but will not with `ElementPatchMode.Outer`. - -```fsharp -Elem.div [ Ds.onLoad (Ds.get "/moreAgents") ] [] +
``` ### [Ds.effect : `data-effect`](https://data-star.dev/reference/attributes#data-effect) @@ -367,12 +366,14 @@ Elem.div [ Ds.onIntersect ("$intersected = true", visibility = Half, onlyOnce = Elem.div [ Ds.onIntersect ("$intersected = true", visibility = Half, onlyOnce = true, throttle = Throttle.With(TimeSpan.FromSeconds(1.0))) ] [] ``` -### [Ds.onSignalPatch | Ds.onSignalPatchFilter: `data-on-signal-patch`](https://data-star.dev/reference/attributes#data-on-signal-change) +### [Ds.onSignalPatch | Ds.onSignalPatchFilter : `data-on-signal-patch`](https://data-star.dev/reference/attributes#data-on-signal-patch) Runs an expression any signal changes. This should be used sparingly, as it is cost intensive. ```fsharp -Elem.div [ Ds.onAnySignalChange "$show = !$show" ] [] +Elem.div [ Ds.onSignalPatch "$show = !$show" ] [] + +Elem.div [ Ds.onSignalPatchFilter (SignalsFilter.Include "/foo/") ] [] ``` ### [Ds.onInterval : `data-on-interval`](https://data-star.dev/reference/attributes#data-on-interval) @@ -407,7 +408,7 @@ All signals, that do not have an underscore prefix, are sent in the request. `@get` will send the signal values as query parameters. All others are sent within a JSON body. ```fsharp -Elem.div [ Ds.onLoad (Ds.get "/get") ] [] +Elem.div [ Ds.init (Ds.get "/get") ] [] Elem.button [ Ds.onClick (Ds.post "/post") ] [ Text.raw "Post" ] @@ -450,21 +451,21 @@ Toggles all the signals that start with the prefix. This is useful for toggling Elem.div [ Ds.onEvent (OnEvent.SignalsChanged, (Ds.toggleAll "foo.")) ] [] ``` -### [Ds.ignore | Ds.ignoreThis | Ds.ignoreMorph : `data-star-ignore`](https://data-star.dev/reference/attributes#data-star-ignore) +### [Ds.ignore | Ds.ignoreSelf | Ds.ignoreMorph : `data-star-ignore`](https://data-star.dev/reference/attributes#data-ignore) Datastar walks the entire DOM and applies plugins to each element it encounters. It’s possible to tell Datastar to ignore an element and its descendants by placing a data-star-ignore attribute on it. This can be useful for preventing naming conflicts with third-party libraries. `Ds.ignore` will force Datastar to ignore the element and all child elements. -`Ds.ignoreThis` only affects the attribute it is attached to. +`Ds.ignoreSelf` only affects the attribute it is attached to. ```fsharp Elem.div [ Ds.ignore ] [ Elem.div [ Ds.text "ignoredAsWell" ] [] ] -Elem.div [ Ds.ignoreThis ] [ +Elem.div [ Ds.ignoreSelf ] [ Elem.div [ Ds.text "thisIsNotIgnored" ] [] ] @@ -487,7 +488,7 @@ Elem.pre [ Ds.jsonSignalsOptions (SignalsFilter.Include "/foo/") ] [] ## _When to `$`_ You may have noticed in the sample code that the `$` is used in some places, but not others. At first, it might be -confusing when a `$` is required, but it really isn't all that complicated when you think of it as either being a signal path or an expression. +confusing when a `$` is required, but it really isn't all that complicated when you think of it as either being a signal path or not. The `$` symbol is a shorthand to get the value of the signal (e.g. `$count` -> `count.value`), so when the `$` is elided, you are referring to the signal directly. [`Ds.bind signalPath`](#dsbind--data-bind) is two-way binding to the signal, so it requires the signal path, no `$`. @@ -627,15 +628,16 @@ are mirrored with a function with `sse` as their prefix instead of `of`. ```fsharp let handleStream = (fun ctx -> task { - do! Response.sseStartResponse ctx + do! Response.sseStartResponse ctx // make sure this is called first; sends the appropriate headers let mutable counter = 0 - while true do // all Datastar methods (unless requested otherwise) will throw on ctx.RequestAborted + while true do // all Datastar methods will throw on ctx.RequestAborted do! Response.ssePatchSignal ctx (sp"counter") counter do! Response.sseHtmlElements ctx ( Elem.pre [ Attr.id "counterId" ] [ Text.raw counter.ToString() ] ) do! Task.Delay(TimeSpan.FromSeconds 1L, ctx.RequestAborted) counter <- counter + 1 + }) ``` See the [Streaming example](examples/Streaming) for more. diff --git a/examples/ClickToEdit/ClickToEdit.fs b/examples/ClickToEdit/ClickToEdit.fs index 74c459c..5a1860f 100644 --- a/examples/ClickToEdit/ClickToEdit.fs +++ b/examples/ClickToEdit/ClickToEdit.fs @@ -16,7 +16,7 @@ let ofMainBody : HttpHandler = ] Elem.body [] [ Text.h1 "Example: Click to Edit" - Elem.div [ Attr.id "contact"; Ds.onLoad (Ds.get "clickToEdit/view") ] [] + Elem.div [ Attr.id "contact"; Ds.onInit (Ds.get "clickToEdit/view") ] [] ] ] Response.ofHtml htmlXml diff --git a/examples/ClickToLoad/ClickToLoad.fs b/examples/ClickToLoad/ClickToLoad.fs index b0fecfd..417d2e9 100644 --- a/examples/ClickToLoad/ClickToLoad.fs +++ b/examples/ClickToLoad/ClickToLoad.fs @@ -35,7 +35,7 @@ let handleIndex: HttpHandler = [ Elem.th [] [ Text.raw "Name" ] Elem.th [] [ Text.raw "Email" ] Elem.th [] [ Text.raw "ID" ] ] - Elem.tbody [ Attr.id "agent_rows"; Ds.onLoad (Ds.get "/moreAgents") ] [] ] ] ] + Elem.tbody [ Attr.id "agent_rows"; Ds.onInit (Ds.get "/moreAgents") ] [] ] ] ] Response.ofHtml html diff --git a/examples/Signals/Signals.fs b/examples/Signals/Signals.fs index f528f1d..d2b47fe 100644 --- a/examples/Signals/Signals.fs +++ b/examples/Signals/Signals.fs @@ -125,7 +125,7 @@ let handleIndex : HttpHandler = Elem.label [ Attr.for' "r6" ] [ Text.raw "Six" ] Elem.br [ // note that this must follow AFTER the refs are created above - Ds.filterOnSignalPatch (sf"^checkBoxSignal$") + Ds.onSignalPatchFilter (sf"^checkBoxSignal$") Ds.onSignalPatch "$r4.name = $r5.name = $r6.name = ($checkBoxSignal ? 'radioGroup2' : 'radioGroup1')" ] ] diff --git a/examples/Streaming/Streaming.fs b/examples/Streaming/Streaming.fs index 8cf618a..ff43572 100644 --- a/examples/Streaming/Streaming.fs +++ b/examples/Streaming/Streaming.fs @@ -41,11 +41,11 @@ let handleIndex ctx = task { Elem.body [ Ds.signal (SignalPath.userName, user) Ds.signal (SignalPath.displayType, UserState.displayBadApple) - Ds.filterOnSignalPatch (SignalsFilter.Include SignalPath.displayType) + Ds.onSignalPatchFilter (SignalsFilter.Include SignalPath.displayType) Ds.onSignalPatch (Ds.get "/channel") Ds.safariStreamingFix ] [ - Elem.div [ Ds.onLoad (Ds.get "/stream") ] [] + Elem.div [ Ds.onInit (Ds.get "/stream") ] [] Text.h1 "Example: Streaming" diff --git a/src/Falco.Datastar/Ds.fs b/src/Falco.Datastar/Ds.fs index 4186f8b..1a2cc97 100644 --- a/src/Falco.Datastar/Ds.fs +++ b/src/Falco.Datastar/Ds.fs @@ -10,7 +10,7 @@ open StarFederation.Datastar.FSharp [] type Ds = static member cdnSrc = - @"https://cdn.jsdelivr.net/gh/starfederation/datastar@1.0.0-RC.5/bundles/datastar.js" + @"https://cdn.jsdelivr.net/gh/starfederation/datastar@1.0.0-RC.6/bundles/datastar.js" /// /// Shorthand for `Elem.script [ Attr.type' "module"; Attr.src cdnSrc ] []` @@ -120,7 +120,7 @@ type Ds = /// /// Create a signal that refers to the HTML element it is assigned to; after a data-ref is created, you can access attributes of the element. - /// e.g. data-on-click="$signalRefName.value='newValue'". + /// e.g. data-on:click="$signalRefName.value='newValue'". /// Note: that if an element's attribute changes, the expressions containing this signal will not fire. /// https://data-star.dev/reference/attributes#data-ref /// @@ -188,7 +188,7 @@ type Ds = /// https://data-star.dev/reference/attributes#data-ignore /// /// Attribute - static member ignoreThis = + static member ignoreSelf = DsAttr.start "ignore" |> DsAttr.addModifier { Name="self"; Tags = [] } |> DsAttr.create @@ -253,14 +253,14 @@ type Ds = /// /// Fires the expression when the element is loaded. - /// https://data-star.dev/reference/attributes#data-on-load + /// https://data-star.dev/reference/attributes#data-init /// /// The expression to evaluate when the event is triggered; https://data-star.dev/guide/datastar_expressions /// The time to wait before executing the expression in milliseconds; default = 0 /// Wrap expression in document.startViewTransition(); default = false /// Attribute - static member onLoad (expression, ?delayMs, ?viewTransition) = - DsAttr.start "on-load" + static member onInit (expression, ?delayMs, ?viewTransition) = + DsAttr.start "init" |> DsAttr.addModifierOption (delayMs |> Option.map DsAttrModifier.DelayMs) |> DsAttr.addModifierNameIf "viewtransition" (defaultArg viewTransition false) |> DsAttr.addValue expression @@ -283,7 +283,7 @@ type Ds = /// /// Fires the expression when a signal is changed. Filter using Ds.filterOnSignalPatch - /// hhttps://data-star.dev/reference/attributes#data-on-signal-patch + /// https://data-star.dev/reference/attributes#data-on-signal-patch /// /// The expression to evaluate when the event is triggered; https://data-star.dev/guide/datastar_expressions /// The time to wait before executing the expression in milliseconds; default = 0 @@ -304,7 +304,7 @@ type Ds = /// /// Regex of signal paths to be included and excluded /// Attribute - static member filterOnSignalPatch (signalsFilter:SignalsFilter) = + static member onSignalPatchFilter (signalsFilter:SignalsFilter) = DsAttr.start "on-signal-patch-filter" |> DsAttr.addValue (signalsFilter |> SignalsFilter.serialize) |> DsAttr.create @@ -437,4 +437,4 @@ type Ds = /// https://stackoverflow.com/questions/8788802/prevent-safari-loading-from-cache-when-back-button-is-clicked /// static member safariStreamingFix = - Attr.create "data-on-pageshow.window" "evt?.persisted && window.location.reload()" + Attr.create "data-on:pageshow.window" "evt?.persisted && window.location.reload()" diff --git a/src/Falco.Datastar/Falco.Datastar.fsproj b/src/Falco.Datastar/Falco.Datastar.fsproj index 83f9116..d51bbd4 100644 --- a/src/Falco.Datastar/Falco.Datastar.fsproj +++ b/src/Falco.Datastar/Falco.Datastar.fsproj @@ -1,7 +1,7 @@ Falco.Datastar - 1.0.0 + 1.1.0 Datastar Bindings for the Falco web toolkit. @@ -19,7 +19,7 @@ Falco.Datastar - 1.0.0 + 1.1.0 fsharp;web;falco;falco-sharp;data-star https://github.com/falcoframework/Falco.Datastar Apache-2.0 @@ -33,6 +33,7 @@ true true true + Falco.Datastar diff --git a/src/Falco.Datastar/Types.fs b/src/Falco.Datastar/Types.fs index db87d84..6bbf0d8 100644 --- a/src/Falco.Datastar/Types.fs +++ b/src/Falco.Datastar/Types.fs @@ -5,7 +5,6 @@ open System.Collections.Generic open System.Text open System.Text.Json open System.Text.Json.Nodes -open System.Text.RegularExpressions open System.Web open Falco.Markup open StarFederation.Datastar.FSharp @@ -189,20 +188,20 @@ type RequestOptions = type Debounce = { TimeSpan:TimeSpan Leading:bool - NoTrail:bool } - static member With (timeSpan:TimeSpan, ?leading:bool, ?noTrail:bool) = - { TimeSpan = timeSpan; Leading = (defaultArg leading false); NoTrail = (defaultArg noTrail false) } - static member With (milliseconds:int, ?leading:bool, ?noTrail:bool) = - { TimeSpan = TimeSpan.FromMilliseconds(milliseconds); Leading = (defaultArg leading false); NoTrail = (defaultArg noTrail false) } + NoTrailing:bool } + static member inline With (timeSpan:TimeSpan, ?leading:bool, ?noTrailing:bool) = + { TimeSpan = timeSpan; Leading = (defaultArg leading false); NoTrailing = (defaultArg noTrailing false) } + static member inline With (milliseconds:int, ?leading:bool, ?noTrailing:bool) = + { TimeSpan = TimeSpan.FromMilliseconds(milliseconds); Leading = (defaultArg leading false); NoTrailing = (defaultArg noTrailing false) } type Throttle = { TimeSpan:TimeSpan NoLeading:bool - Trail:bool } - static member With (timeSpan:TimeSpan, ?noLeading:bool, ?trail:bool) = - { TimeSpan = timeSpan; NoLeading = (defaultArg noLeading false); Trail = (defaultArg trail false) } - static member With (milliseconds:int, ?noLeading:bool, ?trail:bool) = - { TimeSpan = TimeSpan.FromMilliseconds(milliseconds); NoLeading = (defaultArg noLeading false); Trail = (defaultArg trail false) } + Trailing:bool } + static member inline With (timeSpan:TimeSpan, ?noLeading:bool, ?trailing:bool) = + { TimeSpan = timeSpan; NoLeading = (defaultArg noLeading false); Trailing = (defaultArg trailing false) } + static member inline With (milliseconds:int, ?noLeading:bool, ?trailing:bool) = + { TimeSpan = TimeSpan.FromMilliseconds(milliseconds); NoLeading = (defaultArg noLeading false); Trailing = (defaultArg trailing false) } type OnEventModifier = /// Trigger event once. Can only be used with the built-in events @@ -237,36 +236,36 @@ type DsAttrModifier = { Name:string Tags:string list } with - static member Delay (delay:TimeSpan) = + static member inline Delay (delay:TimeSpan) = { Name = "delay"; Tags = [ $"{delay.TotalMilliseconds}ms" ] } - static member DelayMs (delay:int) = + static member inline DelayMs (delay:int) = { Name = "delay"; Tags = [ $"{delay}ms" ] } - static member DurationMs (duration:int, leading:bool) = + static member inline DurationMs (duration:int, leading:bool) = { Name = "duration" Tags = [ $"{duration}ms" if leading then "leading" ] } - static member Throttle (throttle:Throttle) = + static member inline Throttle (throttle:Throttle) = { Name = "throttle" Tags = [ $"{throttle.TimeSpan.TotalMilliseconds}ms" if throttle.NoLeading then "noleading" - if throttle.Trail then "trail" + if throttle.Trailing then "trailing" ] } - static member Debounce (debounce:Debounce) = + static member inline Debounce (debounce:Debounce) = { Name = "debounce" Tags = [ $"{debounce.TimeSpan.TotalMilliseconds}ms" if debounce.Leading then "leading" - if debounce.NoTrail then "notrail" + if debounce.NoTrailing then "notrailing" ] } - static member OnEventModifier (onEventModifier:OnEventModifier) = + static member inline OnEventModifier (onEventModifier:OnEventModifier) = match onEventModifier with | Once -> { Name = "once"; Tags = [] } | Passive -> { Name = "passive"; Tags = [] } @@ -291,22 +290,16 @@ type DsAttr = HasCaseModifier:bool Value:string voption } with - static member private removeOnRegex = Regex("(^on-?|^data-on-?)", RegexOptions.Compiled) - static member inline start name = { Name = name; Target = ValueNone; Modifiers = []; Value = ValueNone; HasCaseModifier = false } - static member startEvent eventName = - let removeOn str = - match str with - | "online" -> "online" - | str -> DsAttr.removeOnRegex.Replace(str, "") - { Name = $"on-{(removeOn eventName)}"; Target = ValueNone; Modifiers = []; Value = ValueNone; HasCaseModifier = false } + static member inline startEvent eventName = + { Name = $"on"; Target = ValueSome eventName; Modifiers = []; Value = ValueNone; HasCaseModifier = false } static member inline addTarget name dsAttr= { dsAttr with Target = ValueSome name } - static member addSignalPathTarget (signalPath:SignalPath) = + static member inline addSignalPathTarget (signalPath:SignalPath) = signalPath |> SignalPath.keys |> Seq.map SignalPath.kebabValue @@ -339,7 +332,7 @@ type DsAttr = |> (fun sb -> match dsAttr.Target with | ValueNone -> sb - | ValueSome target -> sb.Append('-') |> _.Append(target) + | ValueSome target -> sb.Append(':') |> _.Append(target) ) |> (fun sb -> match dsAttr.Modifiers with @@ -353,7 +346,7 @@ type DsAttr = ) |> _.ToString() - static member create dsAttr = + static member inline create dsAttr = let dsAttrKey = dsAttr |> DsAttr.generateKey match dsAttr.Value with | ValueSome value -> Attr.create dsAttrKey value diff --git a/test/Falco.Datastar.Tests/DsTests.fs b/test/Falco.Datastar.Tests/DsTests.fs index a16b5e5..ccf8b21 100644 --- a/test/Falco.Datastar.Tests/DsTests.fs +++ b/test/Falco.Datastar.Tests/DsTests.fs @@ -15,4 +15,4 @@ module DsTests = [] let ``Ds.bind should create an attribute`` () = testElem [ Ds.bind "signalPath" ] - |> should equal """
div
""" + |> should equal """
div
"""