Last active
December 16, 2018 22:47
-
-
Save alfonsogarciacaro/f15cd60eef96da4f9cd5924fef21ecc7 to your computer and use it in GitHub Desktop.
Created with Fable REPL
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
.card.is-avatar { | |
max-width: 350px; | |
margin: auto; | |
} | |
.card.is-avatar .card-image { | |
display: flex; | |
justify-content: center; | |
align-items: center; | |
padding: 25px 0 0; | |
} |
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 Thoth.RandomUser | |
(** | |
Small application showing how to use: | |
- Thoth.Json | |
- Fable.PowerPack (Promise, Fetch API) | |
*) | |
open System | |
open Fable.Helpers.React | |
open Fable.Helpers.React.Props | |
open Fable.SimpleHttp | |
open Fable.Import | |
open Elmish | |
open Elmish.React | |
open Thoth.Json | |
// MODEL | |
type Gender = | |
| Male | |
| Female | |
static member Decoder = | |
Decode.string | |
|> Decode.andThen ( | |
function | |
| "male" -> Decode.succeed Male | |
| "female" -> Decode.succeed Female | |
| invalid -> "`" + invalid + "` isn't a valid value for Gender" | |
|> Decode.fail | |
) | |
type User = | |
{ Gender : Gender | |
FullName : string | |
Email : string | |
CellPhone : string | |
OfficePhone : string | |
Age : int | |
Birthday : DateTime | |
Picture : string } | |
static member Decoder = | |
// When using Thoth.Json, you are not forced to do a 1 to 1 | |
// mapping between the JSON format and your types | |
// For example, in the next decoder we will access deep information | |
// and store it at the "root" of our type | |
Decode.object (fun get -> | |
// In object decoder, we can execute any F# | |
// So for example, we can use temporary variables | |
let firstname = get.Required.At [ "name"; "first" ] Decode.string | |
let lastname = get.Required.At [ "name"; "last" ] Decode.string | |
{ Gender = get.Required.Field "gender" Gender.Decoder | |
FullName = firstname + " " + lastname | |
Email = get.Required.Field "email" Decode.string | |
CellPhone = get.Required.Field "cell" Decode.string | |
OfficePhone = get.Required.Field "phone" Decode.string | |
Age = get.Required.At [ "dob"; "age" ] Decode.int | |
Birthday = get.Required.At [ "dob"; "date" ] Decode.datetime | |
Picture = get.Required.At [ "picture"; "large" ] Decode.string } | |
) | |
type Model = | |
/// Loading state | |
/// If user is None, then it's the initial loading | |
| Loading of User option | |
/// Loaded state | |
| Loaded of User | |
/// If last request results in an error | |
| Errored | |
type Msg = | |
| FetchRandomUser | |
| FetchResponse of Result<User, string> | |
| FetchError of exn | |
/// At first, we have no user to display | |
let init () = Loading None, Cmd.ofMsg FetchRandomUser | |
// UPDATE | |
let private getRandomUser () = async { | |
// We add a delay of 300ms so the button animation is more visible | |
do! Async.Sleep 300 | |
let! response = | |
Http.request "https://randomuser.me/api/" | |
|> Http.send | |
let resultDecoder = | |
Decode.field "results" (Decode.index 0 User.Decoder) | |
return Decode.fromString resultDecoder response.responseText | |
} | |
let update (msg:Msg) (model:Model) = | |
match msg with | |
| FetchRandomUser -> | |
let newModel = | |
match model with | |
// If we have a current user | |
// we keep it while waiting the new user | |
| Loaded user -> | |
Loading (Some user) | |
| _ -> Loading None | |
newModel, Cmd.ofAsync getRandomUser () FetchResponse FetchError | |
// We got a response and decoding succeded | |
| FetchResponse (Ok user) -> | |
Loaded user, Cmd.none | |
// We got a response and decoding failed | |
| FetchResponse (Error msg) -> | |
Browser.console.error msg | |
Errored, Cmd.none | |
// An error occured, when fetching the new user | |
| FetchError error -> | |
Browser.console.error error.Message | |
Errored, Cmd.none | |
// VIEW (rendered with React) | |
let inline private renderInfo iconClass value = | |
let iconClass = "fa " + iconClass | |
div [ ] | |
[ span [ Class "icon" ] | |
[ i [ Class iconClass ] | |
[ ] ] | |
str " " | |
str value ] | |
let inline private viewMessage color msg = | |
div [ Class ("message " + color) ] | |
[ div [ Class "message-body" ] | |
[ str msg ] ] | |
let private viewLoading = | |
viewMessage "is-info" "Rock'n Roll!" | |
let private viewErrored = | |
viewMessage "is-danger" "An error occured, please check the console for more information." | |
let private viewUser (user : User) = | |
let birthday = | |
user.Birthday.ToShortDateString() | |
div [ Class "card is-avatar" ] | |
[ div [ Class "card-image" ] | |
[ figure [ Class "image is-128x128" ] | |
[ img [ Class "is-rounded" | |
Src user.Picture ] ] ] | |
div [ Class "card-content" ] | |
[ div [ Class "content has-text-centered" ] | |
[ div [ Class "has-text-weight-semibold is-size-5" ] | |
[ str user.FullName ] | |
div [ Class "is-italic" ] | |
[ str birthday ] ] | |
div [ Class "content" ] | |
[ renderInfo "fa-phone" user.CellPhone | |
renderInfo "fa-phone" user.OfficePhone | |
renderInfo "fa-envelope" user.Email ] ] ] | |
let private viewGenerateButton isLoading dispatch = | |
let buttonClass = | |
if isLoading then | |
" is-loading" | |
else | |
"" | |
|> (+) "button is-primary " | |
div [ Class "has-text-centered" ] | |
[ div [ Class buttonClass | |
OnClick (fun _ -> | |
dispatch FetchRandomUser | |
) ] | |
[ str "Generate a new user" ] ] | |
let private center child = | |
div [ Class "columns is-mobile" ] | |
[ div [ Class "column is-3" ] [ ] | |
div [ Class "column" ] [ child ] | |
div [ Class "column is-3" ] [ ] ] | |
let view model dispatch = | |
let (isLoading, content) = | |
match model with | |
| Loading None -> | |
true, viewLoading | |
| Loading (Some user) -> | |
true, viewUser user | |
| Loaded user -> | |
false, viewUser user | |
| Errored -> | |
false, viewErrored | |
section [ Class "hero is-fullheight" ] | |
[ div [ Class "hero-body" ] | |
[ div [ Class "container" ] | |
[ center (viewGenerateButton isLoading dispatch) | |
center content ] ] ] | |
// App | |
Program.mkProgram init update view | |
|> Program.withReactSynchronous "elmish-app" | |
|> Program.run |
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
<html> | |
<head> | |
<meta http-equiv='Content-Type' content='text/html; charset=utf-8'> | |
<script src="__HOST__/libs/react.production.min.js"></script> | |
<script src="__HOST__/libs/react-dom.production.min.js"></script> | |
<link rel="stylesheet" href="__HOST__/libs/css/bulma.min.css" /> | |
<link rel="stylesheet" href="__HOST__/libs/css/font-awesome.min.css" /> | |
</head> | |
<body class="app-container"> | |
<div id="elmish-app" class="elmish-app"></div> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment