Trying to render fields in threepenny-gui with special behaviour

385 views Asked by At

What I want to do is set up fields which show detail when they're in focus, but summary when they're not. eg.

a). when it loses focus (gets blur?), I save the value in a (State?) Map and then change the value to a function of the old value (ie. the summary value)

b). when it gets focus - I replace the summary value with the old value that I saved in the Map

I cant' figure out how to do this, but I think I probably need a state monad and the UI monad. My try is:

renderField :: Map->Int->UI (Element, Map)
renderField vs ix = do
    input <- UI.input  
    on UI.blur input $ \_ -> void $ do
        fieldValue <- get value input
        let newVs = insert ix fieldValue vs
        return input # set UI.value (calcNewValue fieldValue)
    on UI.focus input $ \_ -> void $ do
        let savedValue = findWithDefault "" ix vs
        return input # set UI.value savedValue
    return (input, newVs)

but I can't get this map to work - because it needs to track all the calls.... I guess it should be State monad or something?

Thanks.

N

1

There are 1 answers

4
Heinrich Apfelmus On BEST ANSWER

Indeed, you need to keep track of state.

However, the usual pattern s -> (a,s) (the state monad) does not apply here, as you are working with callback functions. For these, you need a different pattern.

In the traditional imperative style, one would use a mutable variable here, for instance an IORef. Note that it is no longer necessary to keep track of indices -- you can think of an IORef as being an index in a large mutable map.

renderField :: UI Element
renderField = do
    input <- UI.input
    state <- liftIO $ newIORef

    on UI.blur input $ \_ -> do
        fieldValue <- get value input
        liftIO $ writeIORef state fieldValue    
        element input # set UI.value (calcNewValue fieldValue)

    on UI.focus input $ \_ -> do
        savedValue <- liftIO $ readIORef state
        element input # set UI.value savedValue

    return input

Alternatively, you can also use functional reactive programming (FRP) in Threepenny. Note that the API is still somewhat preliminary, the following code is specific to threepenny-gui version 0.4.*:

renderField :: UI Element
renderField = do
    input  <- UI.input

    bValueUser <- stepper "" $ UI.valueChange input
    bState     <- stepper "" $ bValueUser <@ UI.blur input
    bValue     <- stepper "" $ fmap head $ unions
        [ (calcNewValue <$> bValueUser) <@ UI.blur input
        , bState <@ UI.focus input
        ]
    element input # sink UI.value bValue 

Again, there are still a couple of subtleties and warts in this code, but this is the general direction I want to head in. Some preliminary information about FRP and how it applies to GUI development can be found in the documentation.

My recommendation is to use the familiar solution (IORef) when you need to get something done quickly, and explore the FRP solution when you have plenty of free time. The example code mainly uses the FRP style.

(Disclosure: I'm the author of Threepenny.)