Skip to content
This repository was archived by the owner on Oct 11, 2025. It is now read-only.

Commit 1e9cdf1

Browse files
amesgenTerrorJack
authored andcommitted
Add xhr example
1 parent eb7168e commit 1e9cdf1

File tree

5 files changed

+167
-0
lines changed

5 files changed

+167
-0
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ under the hood.
1414
## Live demo
1515

1616
- [2048](https://tweag.github.io/ghc-wasm-miso-examples/2048.html)
17+
- [xhr](https://tweag.github.io/ghc-wasm-miso-examples/xhr.html)
1718
- [snake](https://tweag.github.io/ghc-wasm-miso-examples/snake.html)
1819
- [todomvc](https://tweag.github.io/ghc-wasm-miso-examples/todomvc.html)
1920

@@ -59,5 +60,6 @@ cd frontend
5960
The examples are vendored and modified from the following projects:
6061

6162
- 2048: based on https://github.com/ptigwe/hs2048
63+
- xhr: based on https://github.com/dmjio/miso/blob/master/examples/xhr/Main.hs
6264
- snake: based on https://github.com/lbonn/miso-snake/blob/master/Main.hs
6365
- todomvc: based on https://github.com/dmjio/miso/blob/master/examples/todo-mvc/Main.hs

app/App.hs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,14 @@ import SimpleCounter qualified
66
import Snake qualified
77
import TodoMVC qualified
88
import TwoZeroFourEight qualified
9+
import XHR qualified
910

1011
start :: JSString -> JSM ()
1112
start e =
1213
case fromJSString e of
1314
"simplecounter" -> SimpleCounter.start
1415
"snake" -> Snake.start
1516
"todomvc" -> TodoMVC.start
17+
"xhr" -> XHR.start
1618
"2048" -> TwoZeroFourEight.start
1719
_ -> fail "unknown example"

app/XHR.hs

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
{-# LANGUAGE DataKinds #-}
2+
{-# LANGUAGE DeriveGeneric #-}
3+
{-# LANGUAGE RecordWildCards #-}
4+
{-# LANGUAGE ScopedTypeVariables #-}
5+
{-# LANGUAGE TypeFamilies #-}
6+
{-# LANGUAGE TypeOperators #-}
7+
8+
module XHR (start) where
9+
10+
-- slightly adapted from https://github.com/dmjio/miso/blob/master/examples/xhr/Main.hs
11+
12+
import Control.Monad.IO.Class
13+
import Data.Aeson
14+
import qualified Data.Map as M
15+
import Data.Maybe
16+
import qualified Data.Text as T
17+
import GHC.Generics
18+
import GHC.Wasm.Prim
19+
20+
import Miso hiding (defaultOptions)
21+
import Miso.String
22+
23+
-- | Model
24+
data Model
25+
= Model
26+
{ info :: Maybe APIInfo
27+
} deriving (Eq, Show)
28+
29+
-- | Action
30+
data Action
31+
= FetchGitHub
32+
| SetGitHub APIInfo
33+
| NoOp
34+
deriving (Show, Eq)
35+
36+
-- | Main entry point
37+
start :: JSM ()
38+
start = do
39+
startApp App { model = Model Nothing
40+
, initialAction = NoOp
41+
, mountPoint = Nothing
42+
, ..
43+
}
44+
where
45+
update = updateModel
46+
events = defaultEvents
47+
subs = []
48+
view = viewModel
49+
logLevel = Off
50+
51+
-- | Update your model
52+
updateModel :: Action -> Model -> Effect Action Model
53+
updateModel FetchGitHub m = m <# do
54+
SetGitHub <$> getGitHubAPIInfo
55+
updateModel (SetGitHub apiInfo) m =
56+
noEff m { info = Just apiInfo }
57+
updateModel NoOp m = noEff m
58+
59+
-- | View function, with routing
60+
viewModel :: Model -> View Action
61+
viewModel Model {..} = view
62+
where
63+
view = div_ [ style_ $ M.fromList [
64+
(pack "text-align", pack "center")
65+
, (pack "margin", pack "200px")
66+
]
67+
] [
68+
h1_ [class_ $ pack "title" ] [ text $ pack "Miso XHR Example" ]
69+
, button_ attrs [
70+
text $ pack "Fetch JSON from https://api.github.com via XHR"
71+
]
72+
, case info of
73+
Nothing -> div_ [] [ text $ pack "No data" ]
74+
Just APIInfo{..} ->
75+
table_ [ class_ $ pack "table is-striped" ] [
76+
thead_ [] [
77+
tr_ [] [
78+
th_ [] [ text $ pack "URLs"]
79+
]
80+
]
81+
, tbody_ [] [
82+
tr_ [] [ td_ [] [ text current_user_url ] ]
83+
, tr_ [] [ td_ [] [ text emojis_url ] ]
84+
, tr_ [] [ td_ [] [ text emails_url ] ]
85+
, tr_ [] [ td_ [] [ text events_url ] ]
86+
, tr_ [] [ td_ [] [ text gists_url ] ]
87+
, tr_ [] [ td_ [] [ text feeds_url ] ]
88+
, tr_ [] [ td_ [] [ text followers_url ] ]
89+
, tr_ [] [ td_ [] [ text following_url ] ]
90+
]
91+
]
92+
]
93+
where
94+
attrs = [ onClick FetchGitHub
95+
, class_ $ pack "button is-large"
96+
] ++ [ disabled_ True | isJust info ]
97+
98+
data APIInfo
99+
= APIInfo
100+
{ current_user_url :: MisoString
101+
, current_user_authorizations_html_url :: MisoString
102+
, authorizations_url :: MisoString
103+
, code_search_url :: MisoString
104+
, commit_search_url :: MisoString
105+
, emails_url :: MisoString
106+
, emojis_url :: MisoString
107+
, events_url :: MisoString
108+
, feeds_url :: MisoString
109+
, followers_url :: MisoString
110+
, following_url :: MisoString
111+
, gists_url :: MisoString
112+
, hub_url :: MisoString
113+
, issue_search_url :: MisoString
114+
, issues_url :: MisoString
115+
, keys_url :: MisoString
116+
, notifications_url :: MisoString
117+
, organization_repositories_url :: MisoString
118+
, organization_url :: MisoString
119+
, public_gists_url :: MisoString
120+
, rate_limit_url :: MisoString
121+
, repository_url :: MisoString
122+
, repository_search_url :: MisoString
123+
, current_user_repositories_url :: MisoString
124+
, starred_url :: MisoString
125+
, starred_gists_url :: MisoString
126+
, user_url :: MisoString
127+
, user_organizations_url :: MisoString
128+
, user_repositories_url :: MisoString
129+
, user_search_url :: MisoString
130+
} deriving (Show, Eq, Generic)
131+
132+
instance FromJSON APIInfo where
133+
parseJSON = genericParseJSON defaultOptions { fieldLabelModifier = camelTo2 '_' }
134+
135+
getGitHubAPIInfo :: JSM APIInfo
136+
getGitHubAPIInfo = do
137+
resp <- liftIO $
138+
T.pack . fromJSString <$> js_fetch (toJSString "https://api.github.com")
139+
case eitherDecodeStrictText resp :: Either String APIInfo of
140+
Left s -> error s
141+
Right j -> pure j
142+
143+
-- We use the WASM JS FFI here to access the more modern fetch API. If you want
144+
-- your code to eg also work when compiling with non-cross GHC and using
145+
-- jsaddle-warp, you can use fetch or XMLHttpRequest via JSaddle, for example
146+
-- via ghcjs-dom, servant-jsaddle or servant-client-js.
147+
foreign import javascript safe "const r = await fetch($1); return r.text();"
148+
js_fetch :: JSString -> IO JSString

frontend/xhr.html

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1">
6+
<link rel='stylesheet' href="https://cdn.jsdelivr.net/npm/[email protected]/css/bulma.min.css"/>
7+
<title>XHR | Miso example via GHC WASM</title>
8+
</head>
9+
<body>
10+
<script>globalThis.example = "xhr";</script>
11+
<script src="index.js" type="module"></script>
12+
</body>
13+
</html>

ghc-wasm-miso-examples.cabal

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ executable ghc-wasm-miso-examples
1515
, miso
1616
, mtl
1717
, random
18+
, text
1819
hs-source-dirs: app
1920
default-language: GHC2021
2021
default-extensions: BlockArguments LambdaCase LexicalNegation OverloadedStrings RecordWildCards
@@ -24,4 +25,5 @@ executable ghc-wasm-miso-examples
2425
SimpleCounter
2526
Snake
2627
TodoMVC
28+
XHR
2729
ghc-options: -no-hs-main -optl-mexec-model=reactor "-optl-Wl,--export=hs_start"

0 commit comments

Comments
 (0)