relative Haskell and reflex noob here. Decided to get my feet wet with a real-world application.
I am having a problem with triggering an update to a Dynamic containing my record once the user enters text in a textInput
.
The code compiles in GHCJS, but once I open up the web page it shows up blank.
If I remove the line marked as problematic (which creates the update event) it works fine (i.e. setting the record from eClient
and from the clear button works).
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: I thought I might be introducing an infinite loop somewhere, so tried a couple of things:
setEvent
of the input field and the textInput_input
event to the same Dynamic
. This did not helpsetValue
to eClient
instead of eUpdatedClient
- this is the Event Client
that we are receiving from outside (e.g. when a row in a table is clicked). Did not help.Dynamic
update from textInput_keypress
instead of textInput_input
again to avoid a potential loop (although I think this is not the case here. Did not help.An infinite loop may well be the problem, though.
Edit: Added another dynText
which shows that the event eClientModified
fires a perfectly good Client
. So it is really in updating the editClient
Dynamic that it fails.
Found the cause of my problem in the docs of tagDyn
, ultimately: "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."
Somehow I expected this to magically work...
So, using the Behavior
for the update event instead of the Dynamic
(and attachWith
instead of attachPromptlyDynWith
) works fine.
Here is the working code:
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