从文本输入更新记录

Updating a record from a text input

相对Haskell和反射菜鸟在这里。决定用 real-world 应用程序弄湿我的脚。

一旦用户在 textInput.

中输入文本,我就无法触发包含我的记录的动态更新

代码在 GHCJS 中编译,但是一旦我打开网页,它就会显示空白。 如果我删除标记为有问题的行(创建更新事件)它工作正常(即从 eClient 和清除按钮设置记录)。

data Client = Client
        { _clientName :: Text
        , _contacts :: [Text] -- TODO make a new type for this
        , _balance :: Int -- this is calculated
        , _notes :: [Text] -- free text notes, might come in handy
        } deriving (Show, Eq)

updateFieldFromTextInput :: Reflex t =>
                            (Client -> T.Text -> Client) ->
                            Dynamic t Client ->
                            Event t T.Text ->
                            Event t Client
updateFieldFromTextInput setter dynClient evInput = attachPromptlyDynWith setter dynClient evInput

-- the input event is the one to set a client on the widget
-- the output event is when a client is saved
clientEditWidget :: MonadWidget t m => Event t Client -> m (Event t Client)
clientEditWidget eClient = mdo
  (editClient, eSaveButton) <- elClass "div" "client-edit" $ mdo

    -- fires an Event t Client when the input field is changed
    let eNameInput = (nameInput ^. textInput_input)
        nameSetter = flip (clientName .~)
        eNameUpdate = updateFieldFromTextInput nameSetter editClient eNameInput
        eClear = mkClient "" <$ eClearButton
        eClientReplaced = leftmost [eClient, eClear]
        eClientModified = leftmost [eNameUpdate]

    -- the currently edited client
    -- using eClientModified causes a blank screen
    -- editClient <- holdDyn (mkClient "") eClientModified
    editClient <- holdDyn (mkClient "") eClientReplaced

    -- lay out the widgets
    text "edit client"
    nameInput <- textInput $
                 def & setValue .~
                 ((view clientName) <$> eClientReplaced)

    contactsInput <- textArea $
                     def & setValue .~
                     ((T.concat . view contacts) <$> eClientReplaced)
    eSaveButton <- button "Save"
    eClearButton <- button "Clear"
    dynText =<< holdDyn "updated client will appear here" (T.pack . show <$> eClientModified)
    return (editClient, eSaveButton)
  return $ tagPromptlyDyn editClient eSaveButton

编辑:我想我可能会在某处引入无限循环,所以尝试了几件事:

不过,无限循环很可能是问题所在。

编辑:添加了另一个 dynText,它表明事件 eClientModified 触发了一个非常好的 Client。所以它确实在更新 editClient 动态时失败了。

tagDyn 的文档中找到了我的问题的原因,最终:"Additionally, this means that the output Event may not be used to directly change the input Dynamic, because that would mean its value depends on itself. When creating cyclic data flows, generally tag (current d) e is preferred."

不知何故,我希望这会神奇地起作用...

因此,对更新事件使用 Behavior 而不是 Dynamic(以及 attachWith 而不是 attachPromptlyDynWith)效果很好。

这是工作代码:

updateFieldFromTextInput :: Reflex t =>
                            (Client -> T.Text -> Client) ->
                            Behavior t Client ->
                            Event t T.Text ->
                            Event t Client
updateFieldFromTextInput setter bClient evInput = attachWith setter bClient evInput

-- the input event is the one to set a client on the widget
-- the output event is when a client is saved
clientEditWidget :: MonadWidget t m => Event t Client -> m (Event t Client)
clientEditWidget eClient = mdo
  (editClient, eSaveButton) <- elClass "div" "client-edit" $ mdo

    -- fires an Event t Client when the input field is changed
    let eNameInput = (nameInput ^. textInput_input)
        nameSetter = flip (clientName .~)
        eNameUpdate = updateFieldFromTextInput nameSetter (current editClient) eNameInput
        eClear = mkClient "" <$ eClearButton
        eClientReplaced = leftmost [eClient, eClear]
        eClientModified = leftmost [eNameUpdate]

    -- the currently edited client
    editClient <- holdDyn (mkClient "") eClientModified

    -- lay out the widgets
    text "edit client"
    nameInput <- textInput $
                 def & setValue .~
                 ((view clientName) <$> eClientReplaced)

    contactsInput <- textArea $
                     def & setValue .~
                     ((T.concat . view contacts) <$> eClientReplaced)
    eSaveButton <- button "Save"
    eClearButton <- button "Clear"
    dynText =<< holdDyn "updated client will appear here" (T.pack . show <$> eClientModified)
    return (editClient, eSaveButton)
  return $ tagPromptlyDyn editClient eSaveButton