Sveltish
source link: https://github.com/davedawkins/Fable.Sveltish
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Sveltish
An experiment in applying the design principles from Svelte to native Fable. Svelte is impressive in its own right, but I can't help thinking that Fable is a compiler that's already in our toolchain, and is able to do what Svelte does with respect to generating boilerplate.
It's all very much a work-in-progress, and I'm exploring what's possible, so the code is a sprawling mess. If this is worth pursuing, then it will need refactoring and organizing.
Some aspects that are working or in progress.
Check the end of this README to see news on progress.
DOM builder
Crude and minimal. It's Feliz-styled, but builds direct into DOM. If this project proceeds it would be good to layer on top of Feliz.
div [ class' "container" p [ text "Fable is running" ] ]
Stores
Similar to Svelte stores, using the same API
let count = Sveltish.makeStore 0 button [ class' "button" onClick (fun _ -> count.Value() + 1 |> count.Set) count.Value() |> sprintf "You clicked: %i time(s)" |> text ]
Bindings
The intention is to have Fable or a Fable plugin analyze the AST and produce bindings automatically. F# even in my inexperienced hands does an amazing job of reducing boilerplate to a minimum, but it's still boiler plate.
The button example above won't yet update on button clicks. Here's how we make that happen:
let count = Sveltish.makeStore 0 button [ class' "button" onClick (fun _ -> count.Value() + 1 |> count.Set) count.Value() |> sprintf "You clicked: %i time(s)" |> text ] (fun () -> count.Value() |> sprintf "You clicked: %i time(s)" |> text) |> bind count
It's ugly, but with Fable's help that can be made to look this:
let count = Sveltish.makeStore 0 button [ class' "button" onClick (fun _ -> count + 1 |> count.Set) count |> sprintf "You clicked: %i time(s)" |> text ]
Styling
Working like Svelte. Here's how the Svelte animation
example is coming along with respect to the styling.
let styleSheet = [ rule ".new-todo" [ fontSize "1.4em" width "100%" margin "2em 0 1em 0" ] rule ".board" [ maxWidth "36em" margin "0 auto" ] rule ".left, .right" [ float' "left" width "50%" padding "0 1em 0 0" boxSizing "border-box" ] // ... ] let view = style styleSheet <| div [ class' "board" input [ class' "new-todo" placeholder "what needs to be done?" ] todosList "left" "todo" (fun t -> not t.Done) |> bind todos todosList "right" "done" (fun t -> t.Done) |> bind todos ]
Transitions
Working on these right now. The key is being notified of a change in an element's visibility. The DOM intends to listen to a visibility expression (a Store<bool>
) and then update style display: none|<not-none>;
. Like a call to $.show()
in you-know-what.
Here's the code for this component:
let Counter attrs = let count = Sveltish.makeStore 0 div [ button [ class' "button" onClick (fun _ -> console.log("click") count.Value() + 1 |> count.Set) // Boiler plate to be generated by Fable plugin (fun () -> text <| if count.Value() = 0 then "Click Me" else count.Value() |> sprintf "You clicked: %i time(s)" ) |> bind count ] button [ class' "button" Attribute ("style", "margin-left: 12px;" ) onClick (fun _ -> 0 |> count.Set) text "Reset" ] // More boilerplate that can be generated automatically (div [ text "Click button to start counting" ]) |> transition (InOut (Transition.slide, Transition.fade)) (count |~> exprStore (fun () -> count.Value() = 0)) // Visible if 'count = 0' ]
The transition
wrapper manages visibility of the contained element, according to the expression. It then uses
the specified transitions to handle entry and exit of the element from the DOM.
We now have fade
, fly
and slide
transitions
(Html.div [ className "hint"; text "Click button to start counting" ]) |> Bindings.transition (Both (Transition.fly,[ X 100.0; Y 100.0; ])) (count |~> exprStore (fun () -> count.Value() = 0 && props.ShowHint)) // Visible if 'count = 0'
We also have an each
control that manages lists. Items that appear in, disappear from and move around in the list
can be transitioned:
let todosList cls title filter = Html.div [ className cls Html.h2 [ text title ] Bindings.each todos (fun (x:Todo) -> x.Id) filter (Both (Transition.fade [])) (fun todo -> Html.label [ Html.input [ attr ("type","checkbox") Bindings.bindAttr "checked" ((makePropertyStore todo "Done") <~| todos) ] text " " text todo.Description Html.button [ on "click" (fun _ -> remove(todo)) text "x" ] ] ) ]
I'm looking forward to seeing how much boilerplate we can remove with a compiler plugin.
Crossfade is now working. This animation is deliberately set to run slowly so that I could
check the behaviour. The final part of this example is the animate:flip
directive.
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK