I'm new to Yesod and seem to be completely lost with Widgets, Handlers, Hamlets, WHamlets, and what have you! Here's what I'm trying to do:
- Every page on my site needs to have a navbar, which leads me to believe that the correct place for implementing this should be
defaultLayout
- Now, this navbar needs to display some information that is obtained from an IO action (it's an RPC call which gives this data, to be more specific).
Therefore, I tried writing the following function in Foundation.hs
(the code layout is the basic yesod-sqlite
scaffolding template):
nav = do
globalStat <- handlerToWidget $ A2.getGlobalStat NWT.ariaRPCUrl
$(whamletFile "templates/navbar.hamlet)
A2.getGlobalStat :: IO GlobalStatResponse
Here's what template/navbar.hamlet
looks like:
<nav .navbar .navbar-default>
<div .container-fluid>
<p .navbar-right .navbar-text>
<span>
#{A2.glDownloadSpeed globalStat}
<i .glyphicon .glyphicon-arrow-down>
<span>
#{A2.glUploadSpeed globalStat}
<i .glyphicon .glyphicon-arrow-up>
<span .label .label-success>
On-the-watch
Here's what default-layout-wrapper.hamlet
looks like:
<!-- SNIP -->
<body>
<div class="container">
<header>
^{nav}
<div id="main" role="main">
^{pageBody pc}
<!-- SNIP -->
Here's what defaultLayout
looks like:
defaultLayout widget = do
master <- getYesod
mmsg <- getMessage
pc <- widgetToPageContent $ do
addStylesheet $ StaticR css_bootstrap_css
$(widgetFile "default-layout")
withUrlRenderer $(hamletFile "templates/default-layout-wrapper.hamlet")
However, the code refuses to compile with one type-error after another. I've tried a lot of combinations of hametFile
, whamletFile
, handerToWidget
, liftIO
, even placing the nav function inside defaultLayout
, but nothing seems to work. According to me my current code should compile, but I've obviously not understood how the Yesod-Core types are working.
How do I get this to work? And more importantly, what concept have I misunderstood?
Edit 1:
Have tried modifying the nav
function to the following:
nav :: Handler Html
nav = do
globalStat <- liftIO $ A2.getGlobalStat NWT.ariaRPCUrl
$(hamletFile "templates/navbar.hamlet")
But, it results in the following type mismatch in defaultLayout
on the line with withUrlRenderer
:
Couldn't match type ‘HandlerT App IO Html’
with ‘Text.Hamlet.Render (Route App) -> Html’
Expected type: HtmlUrl (Route App)
Actual type: Handler Html
In the first argument of ‘Text.Hamlet.asHtmlUrl’, namely ‘nav’
In a stmt of a 'do' block: Text.Hamlet.asHtmlUrl nav _render_a2ZY0 (intero)
Edit 2:
Tried changing the type signature of nav
to:
nav :: Widget
nav = do
globalStat <- liftIO $ A2.getGlobalStat NWT.ariaRPCUrl
$(hamletFile "templates/navbar.hamlet")
But it results in a new type-mismatch, in the same line:
Couldn't match type ‘WidgetT App IO ()’
with ‘Text.Hamlet.Render (Route App) -> Html’
Expected type: HtmlUrl (Route App)
Actual type: Widget
In the first argument of ‘Text.Hamlet.asHtmlUrl’, namely ‘nav’
In a stmt of a 'do' block: Text.Hamlet.asHtmlUrl nav _render_a350l (intero)
Edit 3:
Here's a relevant snippet from -ddump-splices
:
\ _render_a28TE
-> do { asHtmlUrl (pageHead pc) _render_a28TE;
id ((Text.Blaze.Internal.preEscapedText . Data.Text.pack) "\n");
asHtmlUrl (pageBody pc) _render_a28TE;
id ((Text.Blaze.Internal.preEscapedText . Data.Text.pack) "\n");
asHtmlUrl testWidget2 _render_a28TE }
The type of (pageHead pc)
and (pageBody pc)
is HtmlUrl (Route App)
Here's how I got this to work. There were actually two different problems I was facing:
default-layout-wrapper
hamletFile.Here's the solution for doing IO inside the widget:
Note: The type signature
nav :: Widget
seems to be necessary, else the type inference engine might get confused, and infer a very different type for theliftIO
operation (which was originally happening with me).For the second problem, I couldn't really find a solution for referencing a Widget within the
default-layout-wrapper
hamletFile. By the time this particular hamletFile is being rendered, the Widget monad has been converted to aPageContent
type and now it needs anHtml url
type to be able to render it in conjunction with thewithUrlRenderer
function. Basically, I wasn't able to getWidget
andPageContent
to compose. However, the following approach gave me my desired result, in a different way:default-layout.hamlet: Added the invocation to
nav
widget in this file. Moved some elements fromdefault-layout-wrapper
to this file:default-layout-wrapper.hamlet: Moved a few HTML element from this file to
default-layout
: