Changes in other elements based on listbox selections in threepenny-gui

351 views Asked by At

So I have a simple example layout with a listbox, a button and a textarea, where clicking the button changes the text in the textarea:

import Control.Applicative
import Control.Monad
import Data.Maybe

import qualified Graphics.UI.Threepenny as UI
import Graphics.UI.Threepenny.Core

main :: IO ()
main = startGUI defaultConfig setup

setup :: Window -> UI ()
setup w = do
    return w # set UI.title "Simple example"

    listBox     <- UI.listBox   (pure ["First", "Second"]) (pure Nothing) ((UI.string .) <$> (pure id))
    button      <- UI.button    # set UI.text "Button"
    display     <- UI.textarea  # set UI.text "Initial value"

    element listBox # set (attr "size") "10"    

    getBody w   #+ [element listBox, element button, element display]

    on UI.click button $ const $ do
        element display # set UI.text "new text"

What I wanted to do is have the change be dependent on the listbox selection (for example have the "new text" be "First" or "Second" based on the selection).

I can quite easily get the selection by combining userSelection and facts as

facts . userSelection :: ListBox a -> Behavior (Maybe a)

but because setting the value for the textarea is done with

set text :: String -> UI Element -> UI Element

I don't know how to work around the fact that the selection is a Behavior.

All this seems a bit unidiomatic to me and I was wondering what would be the correct way to do this. Maybe I should do something already when the listbox selection is done or changed and not only when the button is pressed.

3

There are 3 answers

1
duplode On BEST ANSWER

First of all, there was a regression which affected the code here. That issue is now solved. Threepenny 0.6.0.3 has a temporary fix, and the definitive one will be included in the release after that.


The code in the pastebin you provided is almost correct. The only needed change is that you don't need to use sink within the button click callback - in your case, sink should establish a permanent connection between a behavior and the content of the text area, with the behavior value changing in response to the button click events.

For the sake of completeness, here is a full solution:

{-# LANGUAGE RecursiveDo #-}
module Main where

import Control.Applicative
import Control.Monad
import Data.Maybe

import qualified Graphics.UI.Threepenny as UI
import Graphics.UI.Threepenny.Core

main :: IO ()
main = startGUI defaultConfig setup

setup :: Window -> UI ()
setup w = void $ mdo
    return w # set UI.title "Simple example"

    listBox <- UI.listBox
        (pure ["First", "Second"]) bSelected (pure $ UI.string)
    button  <- UI.button # set UI.text "Button"
    display <- UI.textarea

    element listBox # set (attr "size") "10"

    getBody w #+ [element listBox, element button, element display]

    bSelected <- stepper Nothing $ rumors (UI.userSelection listBox)
    let eClick = UI.click button
        eValue = fromMaybe "No selection" <$> bSelected <@ eClick
    bValue    <- stepper "Initial value" eValue

    element display # sink UI.text bValue

The two key things to take away are:

  • The Behavior (Maybe a) argument to listBox does not set just the initial selected value, but determines the evolution of the value throughout the lifetime of the application. In this example, facts $ UI.userSelection listBox is just bSelected, as can be verified through the source code of the Widgets module.
  • The typical way to sample a behavior on event occurrences is through (<@) (or <@> if the event carries data you wish to make use of).
2
John F. Miller On

Caution: I am not familiar with ThreePenny, I'm just reading the documentation.

I think you need to sink your listbox into your text area:

element display # sink UI.text ((maybe "new text" id) <$> (facts $ userSelection listBox)) 
3
Massudaw On

Try creating a stepper for the listbox and then just sink to display

listB <- stepper Nothing (userSelection listBox)
element display # sink UI.text ((maybe "new text" id) <$> (listB) 

Then if you want to sample the behavior with the button

listB <- stepper Nothing (userSelection listBox)
button      <- UI.button    # set UI.text "Button"
cutedListB <- stepper Nothing (listB <@ UI.click button)
element display # sink UI.text ((maybe "new text" id) <$> (listB)