1

我是 Yesod 的新手,似乎完全迷失了小部件、处理程序、Hamlets、WHamlets 以及你有什么!这是我正在尝试做的事情:

  • 我网站上的每个页面都需要有一个导航栏,这让我相信实现它的正确位置应该是defaultLayout
  • 现在,这个导航栏需要显示一些从 IO 操作中获得的信息(更具体地说,它是一个提供这些数据的 RPC 调用)。

Foundation.hs因此,我尝试在(代码布局是基本的yesod-sqlite脚手架模板)中编写以下函数:

nav = do
  globalStat <- handlerToWidget $ A2.getGlobalStat NWT.ariaRPCUrl
  $(whamletFile "templates/navbar.hamlet)

A2.getGlobalStat :: IO GlobalStatResponse

如下template/navbar.hamlet所示:

<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

如下default-layout-wrapper.hamlet所示:

<!-- SNIP -->
  <body>
    <div class="container">
      <header>
        ^{nav}
      <div id="main" role="main">
        ^{pageBody pc}
<!-- SNIP -->

如下defaultLayout所示:

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")

但是,代码拒绝编译一个又一个类型错误。我尝试了很多 , , ,的组合hametFile,甚至将 nav 功能放在里面,但似乎没有任何效果。根据我的说法,我当前的代码应该可以编译,但我显然不明白 Yesod-Core 类型是如何工作的。whamletFilehanderToWidgetliftIO defaultLayout

我怎样才能让它工作?更重要的是,我误解了什么概念?

编辑1:

已尝试将nav功能修改为以下内容:

nav :: Handler Html
nav = do
  globalStat  <- liftIO $ A2.getGlobalStat NWT.ariaRPCUrl
  $(hamletFile "templates/navbar.hamlet")

但是,它会导致以下类型不defaultLayout匹配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)

编辑2:

尝试将类型签名更改nav为:

nav :: Widget
nav = do
  globalStat  <- liftIO $ A2.getGlobalStat NWT.ariaRPCUrl
  $(hamletFile "templates/navbar.hamlet") 

但它会在同一行中导致新的类型不匹配:

 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)

编辑3:

这是来自的相关片段-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 }

(pageHead pc)和的类型(pageBody pc)HtmlUrl (Route App)

4

2 回答 2

1

看看这个 SO question的答案。基本上你不能在模板中执行 IO。

另请注意, is 的类型是defaultLayoutMonadIO一个实例,因此您可以使用.GHandler ...GHandlerdefaultLayoutliftIO

我会尝试:

defaultLayout = do
  ...
  globalStat <- liftIO $ handlerToWidget $ A2.getGlobalStat NWT.ariaRPCUrl
  uploadSpeed <- liftIO $ A2.glUploadSpeed globalStat
  downloadSpeed <- liftIO $ A2.glDownloadSpeed globalStat
  ...
  withUrlRenderer $(hamletFile "templates/default-layout-wrapper.hamlet")

并在templates/default-layout-wrapper.hamlet

...
^{nav uploadSpeed downloadSpeed}
...

nav变成这样:

nav uploadSpeed downloadSpeed =   $(whamletFile "templates/navbar.hamlet)

所以基本思路是:

  • 做你所有的 IOdefaultLayout使用liftIO
  • 将子模板所需的数据作为函数参数传递

更新

要在 Yesod 书中模拟这个例子,你需要这样写navbar

navbar :: Widget
navbar = do
    globalStat <- liftIO A2.getGlobalStat NWT.ariaRPCUrl
    downloadSpeed <- liftIO A2.glDownloadSpeed globalStat
    uploadSpeed <- liftIO A.glUploadSpeed
    $(whamletFile "templates/navbar.hamlet)

并在navbar.whamlet参考#{uploadSpeed}#{downloadSpeed}

您不能在 whamlet 文件中执行 IO。此外,您的 A2 函数是 IO 操作,但 handlerToWidget 需要 HandlerT 操作,因此您需要使用它liftIO来转换这些调用。

更新 2

有关在小部件中执行 IO 的工作示例,请参见http://lpaste.net/169497

于 2016-07-10T13:15:41.020 回答
0

这是我如何让它工作的。我实际上面临两个不同的问题:

  • 在 Widget 中执行 IO
  • 引用default-layout-wrapperhamletFile 中的 Widget。

这是在小部件内执行 IO 的解决方案:

nav :: Widget
nav = do
  globalStat <- liftIO $ A2.getGlobalStat NWT.ariaRPCUrl
  $(whamletFile "templates/navbar.hamlet")

注意:类型签名nav :: Widget似乎是必要的,否则类型推理引擎可能会混淆,并为liftIO操作推断出非常不同的类型(这最初发生在我身上)。

对于第二个问题,我真的找不到在default-layout-wrapperhamletFile 中引用 Widget 的解决方案。当这个特定的 hamletFile 被渲染时,Widget monad 已经被转换为一种PageContent类型,现在它需要一种Html url类型才能与withUrlRenderer函数一起渲染它。基本上,我无法获得WidgetPageContent作曲。但是,以下方法以不同的方式给了我想要的结果:

default-layout.hamlet:nav在此文件中添加了对小部件的调用。default-layout-wrapper将一些元素从该文件中移出:

<div .container>
  <header>
    ^{nav}
  <div #main role="main">
    $maybe msg <- mmsg
      <div #message>#{msg}
    ^{widget}

default-layout-wrapper.hamlet:将此文件中的一些 HTML 元素移动到default-layout

<!-- SNIP -->
  <body>
    <div class="container">
      ^{pageBody pc}
<!-- SNIP -->
于 2016-07-11T12:27:11.163 回答