Last active
September 28, 2023 11:32
-
-
Save sheridanchris/76fe752043e9ec2961558a6a5b15ed25 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module Elmish | |
open System | |
open Sutil | |
open Sutil.CoreElements | |
type Article = { | |
Title: string | |
TagLine: string | |
Link: string | |
} | |
// This is the current state of our application. | |
type Model = { | |
Articles: Article list | |
SearchQuery: string option | |
} | |
// These messages will be published as a result of UI interaction or commands. | |
// Messages can be published by clicking a button, typing in an input field, or navigating to a page. | |
// They are instrumental for calculating the new state of the application and updating the UI. | |
type Msg = | |
| GetArticles | |
| SetSearchQuery of string | |
| SetArticles of Article list | |
let getArticles searchQuery = | |
async { | |
// ... perform asynchronous api call here ... | |
// (fake articles for demo) | |
return [ | |
{ | |
Title = "Oh no! OCaml is Trending!" | |
TagLine = "Why couldn't it be F# :(" | |
Link = "https://twitter.com/everywhere" | |
} | |
] | |
} | |
// commands are additional instructors or side-effects that ~may~ produce one or more messages. | |
// this command will perform an asynchronous operation | |
// and when it's complete the `SetArticles` message will be published with the result. | |
let getArticlesCmd searchQuery = | |
Cmd.OfAsync.perform getArticles searchQuery SetArticles // <- the message we want to publish (with data)! | |
// ^^^^^^^^^^^ | |
// this is the input parameter to the asynchronous operation | |
let initialState () = | |
let initialSearchQuery = None | |
{ | |
Articles = [] | |
SearchQuery = initialSearchQuery | |
}, | |
getArticlesCmd initialSearchQuery | |
let optionOfString value = | |
if String.IsNullOrWhiteSpace value then None else Some value | |
let stringFromOption value = value |> Option.defaultValue "" | |
// This function will recieve a message and the current state of the application | |
// it's responsibility is to calculate the new state of the application based on these parameters | |
// and specify any additional instructions via a command. | |
let update msg model = | |
match msg with | |
| GetArticles -> model, getArticlesCmd model.SearchQuery | |
| SetArticles articles -> { model with Articles = articles }, Cmd.none | |
| SetSearchQuery query -> | |
let query = optionOfString query | |
{ model with SearchQuery = query }, Cmd.none | |
let articleView newsArticle = | |
Html.tr [ | |
Html.td (Html.a [ Attr.href newsArticle.Link; Attr.text newsArticle.Title ]) | |
Html.td newsArticle.TagLine | |
] | |
let view () = | |
let dispose model = () | |
// `model` here is an observable value. you can subscribe to changes and perform side-effects to you liking. | |
// `dispatch` is used to publish messages to the update loop and abstracts away state & command handling. | |
let model, dispatch = () |> Store.makeElmish initialState update dispose | |
Html.main [ | |
disposeOnUnmount [ model ] | |
// The bind functions will subscribe to any changes and perform an update on the DOM when it does. | |
Html.input [ | |
Attr.placeholder "Search for a news article" | |
Attr.value (model |> Store.map (fun model -> stringFromOption model.SearchQuery), SetSearchQuery >> dispatch) | |
// ^^^^^^^^^^^^^^^^^^^^^^^^^^ | |
// this will dispatch a `SetSearchQuery` message to the update loop whenever the user types. | |
] | |
Html.button [ Attr.text "Search!"; onClick (fun _ -> dispatch GetArticles) [] ] | |
Html.table [ | |
Html.tr [ Html.th "Title"; Html.th "Tag line" ] | |
// we use Store.map because we only care about a specific value (we don't want to subscribe to ~any~ change) | |
Bind.each (model |> Store.map (fun model -> model.Articles), articleView) | |
] | |
] |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment