5

这个小程序的目的是显示三个按钮,第三个按钮的标签最初是“0”,然后是最后点击按钮的索引。现在按钮的数量和其他按钮的标签是不变的。

当我用 ghcjs 编译这个自包含文件并在浏览器中加载 Main.jsexe/index.html 时,我可以看到两个 traceDyns 在一个循环中触发,两者的值始终为 0。据我所知,什么都不应该发生直到单击按钮,因为 _el_clicked 为系统的其余部分提供信息。

另外,请注意,我使用mapDyn (fst . head . Map.toList)它是为了提取所选按钮的索引 - 我不确定这是否正确,但无论哪种方式,我都不知道是什么导致了无限循环。

{-# LANGUAGE RecursiveDo #-}

module Main where

import Reflex
import Reflex.Dom

import qualified Data.Map as Map

dynButton
  :: MonadWidget t m
  => Dynamic t String
  -> m (Event t ())
dynButton s = do
  (e, _) <- el' "button" $ dynText s
  return $ _el_clicked e

-- widget that takes dynamic list of strings
-- and displays a button for each, returning
-- an event of chosen button's index
listChoiceWidget
  :: MonadWidget t m
  => Dynamic t [String]
  -> m (Event t Int)
listChoiceWidget choices = el "div" $ do
  asMap <- mapDyn (Map.fromList . zip [(0::Int)..]) choices
  evs <- listWithKey asMap (\_ s -> dynButton s)
  k <- mapDyn (fst . head . Map.toList) evs
  return $ updated (traceDyn "k" k)

options :: MonadWidget t m => Dynamic t Int -> m (Dynamic t [String])
options foo = do
  mapDyn (\x -> ["a", "b", show x]) foo

main :: IO ()
main = mainWidget $ el "div" $ do
  rec n <- listChoiceWidget o
      o <- options foo
      foo <- holdDyn 0 n
  display (traceDyn "foo" foo)
4

1 回答 1

5

看起来您的 listChoiceWidget 代码正在丢弃由 dynButton 构造的点击事件。

listWithKey返回m (Dynamic t (Map k a))。在您的情况下,键是类型Int,值是Event t ()(由 dynButton 生成)。

在这条线上:

k <- mapDyn (fst . head . Map.toList) evs

你正在把它Dynamic t (Map Int (Event t ()))变成一个Dynamic t Int,但至关重要的是,当点击事件触发时你并没有这样做。此行映射evs并生成一个 Dynamic ,该 Dynamic 始终包含 Ints 到事件的 Map 中的第一个键,无论事件是否已触发。它总是一个包含 Int 0 的 Dynamic。

您看到循环的原因是:

  1. mainfoo初始值为 0 的形式输入options
  2. 构建了新的选项
  3. listChoiceWidget接收新选项并更新列表
  4. 生成的整数到事件映射的第一个键被更新
  5. foo接收来自的密钥更新事件listChoiceWidget
  6. 无限返回第 2 步

您需要某种方法来确定最后一个按钮单击事件,而不是从 Map 中检索第一个键。您的地图已经包含显示的每个按钮的点击事件。现在这些事件有 type Event t (),但你真正需要的是Event t Int,这样当一个事件触发时,你可以知道它来自哪个按钮。

evs' <- mapDyn (Map.mapWithKey (\k e -> fmap (const k) e)) evs

evs'有类型Dynamic t (Map Int (Event t Int))。接下来,我们需要某种方式来组合我们的事件,以便我们有一个与最近单击按钮的键一起触发的事件。

dynEv <- mapDyn (leftmost . Map.elems . Map.mapWithKey (\k e -> fmap (const k) e)) evs

dynEv现在有类型Dynamic t (Event t Int)。Map 的键已经被烘焙到事件中,所以我们不再需要它们了。Map.elems将我们的事件地图转换为事件列表,并leftmost允许您将事件列表合并为一个事件。

来自文档:“如果列表中至少有一个事件发生,leftmost则创建一个新事件。如果同时发生多个事件,则使用给定函数从左侧折叠它们。”

最后,我们需要将您Dynamic t (Event t Int)的转换为Event t Int. 我们将使用switch,它接受 aBehavior t (Event t a)并返回一个Event t a。因此,以下行将导致Event t Int.

switch (current dynEv)

current提取Behaviora Dynamic,并switch创建“一个在当前选择的输入事件发生时将发生的事件”。

这是修改后的listChoiceWidget代码。我已包含内联类型注释,因此您需要ScopedTypeVariables启用语言扩展来编译此代码(或者您可以删除注释)。

listChoiceWidget
  :: forall t m. MonadWidget t m
  => Dynamic t [String]
  -> m (Event t Int)
listChoiceWidget choices = el "div" $ do
  asMap <- mapDyn (Map.fromList . zip [(0::Int)..]) choices
  evs :: Dynamic t (Map.Map Int (Event t ())) <- listWithKey asMap (\_ s -> dynButton s)
  dynEv :: Dynamic t (Event t Int) <- mapDyn (leftmost . Map.elems . Map.mapWithKey (\k e -> fmap (const k) e)) evs
  return $ switch (current dynEv)

这是完整文件的要点。

于 2015-07-28T19:02:59.607 回答