Обновление записи из текстового ввода

Относительный Хаскелл и рефлекс нуб здесь. Решили намочить ноги с помощью реального приложения.

У меня проблема с запуском обновления в Dynamic, содержащее мою запись, когда пользователь вводит текст в 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

Edit: я думал, что я мог бы ввести бесконечный цикл где-то, поэтому попробовал пару вещей:

  • не подключай setEvent поля ввода и textInput_input событие к тому же Dynamic, Это не помогло
  • задавать setValue в eClient вместо eUpdatedClient - это Event Client что мы получаем извне (например, при щелчке строки в таблице). Не помогло.
  • вызвать Dynamic обновление от textInput_keypress вместо textInput_input снова, чтобы избежать потенциальной петли (хотя я думаю, что это не так здесь. Не помогло.

Однако бесконечная петля вполне может быть проблемой.

Изменить: добавлен еще один dynText который показывает, что событие eClientModified стреляет отлично Client, Так что это действительно в обновлении editClient Динамично, что это не удается.

1 ответ

Решение

Нашел причину моей проблемы в документах tagDynв конечном счете: "Кроме того, это означает, что выходное событие не может быть использовано для непосредственного изменения входной динамики, поскольку это будет означать, что его значение зависит от самого себя. При создании циклических потоков данных обычно tag (current d) e является предпочтительным ".

Каким-то образом я ожидал, что это волшебным образом сработает...

Итак, используя 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
Другие вопросы по тегам