Как сделать IO в виджете / гамлете, на который ссылается defaultLayout?

Я новичок в Йесод и, похоже, полностью потерялся с виджетами, хендлерами, хамлетами, хамлетами и тем, что у тебя есть! Вот что я пытаюсь сделать:

  • Каждая страница на моем сайте должна иметь навигационную панель, что заставляет меня полагать, что правильное место для реализации этого должно быть defaultLayout
  • Теперь эта панель навигации должна отображать некоторую информацию, полученную от действия ввода-вывода (это вызов 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, whamletFile, handerToWidget, liftIO, даже поместив функцию навигации внутри defaultLayout, но ничего не работает. По моему мнению мой текущий код должен компилироваться, но я, очевидно, не понял, как работают типы Yesod-Core.

Как мне заставить это работать? И что еще более важно, какую концепцию я неправильно понял?

Изменить 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)

2 ответа

Посмотрите на ответ на этот ТАК вопрос. По сути, вы не можете выполнить IO в шаблоне.

Также обратите внимание, что тип defaultLayout является GHandler ... а также GHandler является экземпляром MonadIO, поэтому вы можете выполнить IO в defaultLayout используя liftIO,

Я бы попробовал:

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)

Итак, основные идеи:

  • Сделайте все свои IO в defaultLayout с помощью 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},

Вы не можете сделать IO в файле whamlet. Более того, ваши функции A2 являются IO-действиями, но handlerToWidget требует действия HandlerT, поэтому вам нужно использовать liftIO конвертировать эти звонки.

Обновление 2

См. http://lpaste.net/169497 для рабочего примера, который делает IO в Виджете.

Вот как я получил это на работу. На самом деле я столкнулся с двумя разными проблемами:

  • Делать ввод-вывод внутри виджета
  • Ссылка на виджет в default-layout-wrapper hamletFile.

Вот решение для ввода-вывода внутри виджета:

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

Примечание: подпись типа nav :: Widget кажется необходимым, иначе механизм вывода типов может запутаться и вывести совершенно другой тип для liftIO операция (которая изначально происходила со мной).

Что касается второй проблемы, я не мог найти решение для ссылки на виджет в default-layout-wrapper hamletFile. К тому времени, когда этот конкретный hamletFile рендерится, монада Widget была преобразована в PageContent типа и теперь ему нужно Html url тип, чтобы иметь возможность визуализировать его в сочетании с withUrlRenderer функция. По сути, я не смог получить Widget а также PageContent сочинять. Однако следующий подход дал мне желаемый результат по-другому:

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 -->
Другие вопросы по тегам