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:
- don't hook up
setEvent
of the input field and thetextInput_input
event to the sameDynamic
. This did not help - set
setValue
toeClient
instead ofeUpdatedClient
- this is theEvent Client
that we are receiving from outside (e.g. when a row in a table is clicked). Did not help. - trigger the
Dynamic
update fromtextInput_keypress
instead oftextInput_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, generallytag (current d) e
is preferred."Somehow I expected this to magically work...
So, using the
Behavior
for the update event instead of theDynamic
(andattachWith
instead ofattachPromptlyDynWith
) works fine.Here is the working code: