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-wrapperhamletFile.Here's the solution for doing IO inside the widget:
Note: The type signature
nav :: Widgetseems to be necessary, else the type inference engine might get confused, and infer a very different type for theliftIOoperation (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-wrapperhamletFile. By the time this particular hamletFile is being rendered, the Widget monad has been converted to aPageContenttype and now it needs anHtml urltype to be able to render it in conjunction with thewithUrlRendererfunction. Basically, I wasn't able to getWidgetandPageContentto compose. However, the following approach gave me my desired result, in a different way:default-layout.hamlet: Added the invocation to
navwidget in this file. Moved some elements fromdefault-layout-wrapperto this file:default-layout-wrapper.hamlet: Moved a few HTML element from this file to
default-layout: