data EchoMessage = EchoMessage -- The messages we echo
{ path :: Text -- The route that was hit
, message :: Text -- The message to echo
, timeStamp :: UTCTime -- The server side time stamp
} deriving (Show, Eq, Generic)
instance ToJSON EchoMessage -- Turn messages into JSON
instance FromJSON EchoMessage -- Turn JSON into a message
-- This is our API as a type - all endpoints are returning JSON derived from our type EchoMessage
-- /echo/path - capturing no part of the path and no query param
type Api = "echo" :> "path" :> Get '[JSON] EchoMessage
-- /echo/:hello?message=xyz - capturing part of the path naming it as hello and taking a query parameter
:<|> "echo" :> Capture "hello" Text :> QueryParam "message" Text :> Get '[JSON] EchoMessage
-- /echo?message=xyz - capturing no part of the path and taking a query parameter
:<|> "echo" :> QueryParam "message" Text :> Get '[JSON] EchoMessage
-- handle /echo/:hello?message=
echoHello :: Text -> Maybe Text -> EitherT ServantErr IO EchoMessage
echoHello p m = EchoMessage ("echo/" <> p) ("hello your message was \"" <> fromMaybe "" m <> "\"") <$> liftIO getCurrentTime
-- handle /echo?message=
echo :: Maybe Text -> EitherT ServantErr IO EchoMessage
echo m = EchoMessage "echo" (fromMaybe "" m) <$> liftIO getCurrentTime
-- handle /echo/path
echoPath :: EitherT ServantErr IO EchoMessage
echoPath = EchoMessage "echo/path" <$> (T.pack <$> liftIO getCurrentDirectory) <*> liftIO getCurrentTime
api :: Proxy Api
api = Proxy
-- compose our server from handlers to satisfy the type of our API
server :: Server Api
server = echoPath :<|> echoHello :<|> echo
app :: Application
app = serve api server
runApp :: Int -> IO ()
runApp port = run port app -- run our
-- /settings.js - our dynamic JS giving settings back to the client
type Api = "settings.js" :> Get '[PlainText] Text
-- / - serve static files (must be last)
:<|> Raw
data Settings = Settings -- The settings we communicate to the client
{ port :: Int -- The port we are running on, silly I know
, host :: Text -- The name of the host, silly I know
, clientApp :: Text -- The function to execute on the client (actually useful)
} deriving (Show)
-- Our settings handler type actually differs from the standard Servant handler type - Servant is that flexible
type Handler = ReaderT Settings (EitherT ServantErr IO)
settings :: Handler Text
settings = do
Settings {..} <- ask
return $ T.unlines [ "SettingsETFWA = {};" , "SettingsETFWA.port = " <> (T.pack . show $ port) <> ";" , "SettingsETFWA.host = '" <> host <> "';" , "SettingsETFWA.app = '" <> clientApp <> "';" ]
-- Use built in servant handler to serve static content from the data folder
content :: Server Raw
content = serveDirectory "../data"
-- Natural transformation that lets our special handler be run by servant
handlerToServant :: Settings -> Handler :~> EitherT ServantErr IO
handlerToServant = runReaderTNat
-- Construct the server
server :: Settings -> Server Api
server s = enter (handlerToServant s) settings -- Lift our special handler to run alongside normal ones
:<|> content
api :: Proxy Api
api = Proxy
app :: Settings -> Application
app s = serve api (server s)
runApp :: Int -> IO ()
runApp port = run port (app Settings{port = port, host = "localhost", clientApp = "CounterExample"})
-- Import our two servers
import qualified Client
import qualified Echo
-- Our API is the composition of the two other APIs
type Api = Echo.Api
:<|> Client.Api
-- Our server is the composition of the two servers
server :: Client.Settings -> Server Api
server s = Echo.server
:<|> Client.server s
api :: Proxy Api
api = Proxy
app :: Client.Settings -> Application
app s = serve api (server s)
runApp :: Int -> Text -> IO ()
runApp port clientApp = run port (app Client.Settings{Client.port = port, Client.host = "localhost", Client.clientApp = clientApp})
cabal fetch purescript-0.7.6.1 && cd purescript-0.7.6.1 && stack install
npm init
npm install webpack css-loader html-webpack-plugin style-loader --save-dev
npm install webpack-dev-server purs-loader@0.4.0 virtual-dom --save-dev
npm install -g pulp
pulp init
pulp dep install purescript-console purescript-lists purescript-halogen purescript-affjax --save
pulp dep install purescript-tuples purescript-debug purescript-routing --save
npm install virtual-dom --save
require()
s that it sees and compile them into one bundle.js
index.html
require('./main.css'); // here we include CSS
var settings = require("./settings"); // here we include our seetins.js file
// Depending on our settings we include different purescript apps and run them
if (settings.app === 'RoutesExample')
{
var app = require('./RoutesExample.purs');
app.runApp();
}
else if (settings.app === 'EchoOnly')
{
var app = require('./EchoOnly.purs');
app.runApp();
}
else if (settings.app === 'CounterExample')
{
var app = require('./CounterExample.purs');
app.runApp();
}
else
{
console.error("blah");
}
From https://github.com/slamdata/purescript-halogen
-- | The state of the component
type State = { on :: Boolean }
-- | The query algebra for the component
data Query a
= ToggleState a
| GetState (Boolean -> a)
-- | The component definition
myComponent :: forall g. (Functor g) => Component State Query g
myComponent = component render eval
where
render :: State -> ComponentHTML Query
render state =
H.div_
[ H.h1_
[ H.text "Toggle Button" ]
, H.button
[ E.onClick (E.input_ ToggleState) ]
[ H.text (if state.on then "On" else "Off") ]
]
eval :: Natural Query (ComponentDSL State Query g)
eval (ToggleState next) = do
modify (\state -> { on: not state.on })
pure next
eval (GetState continue) = do
value <- gets _.on
pure (continue value)
From https://github.com/slamdata/purescript-halogen
component :: forall s f g. Render s f -> Eval f s f g -> Component s f g
type Render s f = s -> ComponentHTML f
type ComponentHTML f = HTML Void (f Unit)
From https://github.com/slamdata/purescript-halogen
newtype State = State Int
initialState :: State
initialState = State 0
data Query a = Tick a
ui :: forall g. (Functor g) => Component State Query g
ui = component render eval
where
render :: State -> ComponentHTML Query
render (State n) =
H.div_
[ H.h1
[ P.id_ "header" ]
[ H.text "counter" ]
, H.p_
[ H.text (show n) ]
]
eval :: Natural Query (ComponentDSL State Query g)
eval (Tick next) = do
modify (\(State n) -> State (n + 1))
pure next
-- | Run the app
runApp :: Eff (HalogenEffects ()) Unit
runApp = runAff throwException (const (pure unit)) $ do
{ node: node, driver: driver } <- runUI ui initialState
onLoad $ appendToBody node
setInterval 1000 $ driver (action Tick)
setInterval :: forall e a. Int -> Aff e a -> Aff e Unit
setInterval ms a = later' ms $ a *> setInterval ms a
type EchoResponse =
{ path :: String
, message :: String
, timeStamp :: String
}
data MessageType
= HelloMessage {message :: String, response :: Maybe EchoResponse}
| EchoMessage {message :: String, response :: Maybe EchoResponse}
| PathMessage {response :: Maybe EchoResponse}
type State = MessageType
-- The type of effects we can use
type Effects eff = HalogenEffects (console :: CONSOLE, ajax :: Ajax.AJAX | eff)
-- Our query algebra (ways changes can be triggered)
data Query a
= SelectHello a
| SelectEcho a
| SelectPath a
| SetHello String a
| SetEcho String a
| SendHello State a
| SendEcho State a
| SendPath State a
ui :: forall eff. Component State Query (Aff (Effects eff))
ui = component render eval
where
-- .....
-- .... leaving out some code
eval :: Natural Query (ComponentDSL State Query (Aff (Effects eff)))
eval (SelectHello next) = pure next <* modify \_ -> (HelloMessage {message: "", response: Nothing})
eval (SetHello m next) = pure next <* modify
\state -> case state of
HelloMessage d -> HelloMessage d{message=m, response=Nothing}
_ -> state
eval (SendHello s next) = pure next <* case s of
HelloMessage d -> do
r <- liftAff' $ sendHello d.message
modify \_ -> HelloMessage d{response = Just r}
_ -> unsafeThrow "Never"
eval (SelectEcho next) = modify (\state -> EchoMessage {message: "", response: Nothing}) *> pure next
eval (SetEcho m next) = pure next <* modify
\state -> case state of
EchoMessage d -> EchoMessage d{message=m, response=Nothing}
_ -> state
eval (SendEcho s next) = pure next <* case s of
EchoMessage d -> do
r <- liftAff' $ sendEcho d.message
modify \_ -> EchoMessage d{response = Just r}
_ -> unsafeThrow "Never"
eval (SelectPath next) = modify (\state -> PathMessage {response: Nothing}) *> pure next
eval (SendPath s next) = pure next <* case s of
PathMessage d -> do
r <- liftAff' $ sendPath
modify \_ -> PathMessage d{response = Just r}
_ -> unsafeThrow "Never"
newtype EchoResponseR = EchoResponseR EchoResponse
instance respondableEchoResponseR :: Respondable EchoResponseR where
fromResponse r' = do
r <- read r' >>= readJSON
p <- readProp "path" r
m <- readProp "message" r
t <- readProp "timeStamp" r
pure $ EchoResponseR {path: p, message: m, timeStamp: t}
responseType = Tuple (Just applicationJSON) JSONResponse
sendHello :: forall eff. String -> Aff (ajax :: Ajax.AJAX | eff) EchoResponse
sendHello s = Ajax.get ("http://localhost:8086/echo/hello?message=" ++ s) <#> \a -> case a.response of EchoResponseR r -> r
sendEcho :: forall eff. String -> Aff (ajax :: Ajax.AJAX | eff) EchoResponse
sendEcho s = Ajax.get ("http://localhost:8086/echo?message=" ++ s) <#> \a -> case a.response of EchoResponseR r -> r
sendPath :: forall eff. Aff (ajax :: Ajax.AJAX | eff) EchoResponse
sendPath = Ajax.get ("http://localhost:8086/echo/path") <#> \a -> case a.response of EchoResponseR r -> r
ui :: forall eff. Component State Query (Aff (Effects eff))
ui = component render eval
where
render :: State -> ComponentHTML Query
render s = case renderOption s of
Tuple option child ->
H.div_
[ H.select [E.onValueChange (E.input selectType)]
[ H.option [P.value "Hello", P.selected (option == "Hello")] [H.text "Hello"]
, H.option [P.value "Echo", P.selected (option == "Echo")] [H.text "Echo"]
, H.option [P.value "Path", P.selected (option == "Path")] [H.text "Path"]
]
, child
]
_ -> unsafeThrow "Never"
selectType s
| s == "Hello" = SelectHello
| s == "Echo" = SelectEcho
| s == "Path" = SelectPath
-- ...
-- ... leaving out some code
ui :: forall eff. Component State Query (Aff (Effects eff))
ui = component render eval
where
-- ...
-- ... leaving out some code
renderOption s = case s of
HelloMessage {message: m, response: r} -> Tuple "Hello" $ H.div_
[ H.div_
[ H.label_ [H.text "message"]
, H.input [P.inputType P.InputText, E.onValueChange (E.input SetHello)]
]
, H.div_
[ H.label_ [H.text ("Hello response was:" ++ fromMaybe "" (_.message <$> r))]
]
, H.div_
[ H.button [E.onClick (E.input_ $ SendHello s)] [H.text "echo friendly"]
]
]
EchoMessage {message: m, response: r} -> Tuple "Echo" $ H.div_
[ H.div_
[ H.label_ [H.text "message"]
, H.input [P.inputType P.InputText, E.onValueChange (E.input SetEcho)]
]
, H.div_
[ H.label_ [H.text ("Response was:" ++ fromMaybe "" (_.message <$> r))]
]
, H.div_
[ H.button [E.onClick (E.input_ $ SendEcho s)] [H.text "echo"]
]
]
PathMessage {response: r} -> Tuple "Path" $ H.div_
[ H.div_
[ H.label_ [H.text ("Server path is:" ++ fromMaybe "" (_.message <$> r))]
]
, H.div_
[ H.button [E.onClick (E.input_ $ SendPath s)] [H.text "request path"]
]
]
Generally positive
Generally positive