4

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.

655321
  • 195
  • 6

3 Answers3

4

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).
duplode
  • 33,731
  • 7
  • 79
  • 150
  • Thank you so much! I ended up trying many different things, but with the same (non-existent) results. Your example code didn't indeed work with Threepenny 0.6, but going back to Threepenny 0.5 has solved this problem. – 655321 Jun 20 '15 at 12:42
1

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)) 
John F. Miller
  • 26,961
  • 10
  • 71
  • 121
  • Thanks, now that i found `sink`, the whole question looks a bit stupid. Got the program to work though (although the listbox selection doesn't actually change and I'm only reading the `facts`-part of `userSelection`). – 655321 Jun 09 '15 at 17:27
  • I don't know how many people will read this anymore, but I'm still having a hard time making the actual change happen. I'm pretty sure the `rumors` of `userSelection` should be read somewhere and the `facts` changed accordingly but I don't know how to do this. – 655321 Jun 09 '15 at 20:58
1

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) 
Massudaw
  • 31
  • 5
  • Well, first the types in `stepper Nothing (userSelection listBox)` don't match (I tried `stepper Nothing (rumors $ userSelection listBox)`). I still can't get anything to happen, even with just http://pastebin.com/D97sFUuA (the value changes when clicking the button, but always to "new text"). – 655321 Jun 17 '15 at 22:39
  • There really is something I don't understand about these `Tidings` and `Behavior` -things as I can't get this to work. – 655321 Jun 17 '15 at 22:49
  • Tidings is just a combination of behavior and event. It's usefull when you need to sample two or more events and behaviors using the applicative instance defined for tidings . Your fix adding rumors is right. Maybe you are hitting the bug that i reported on github and duplode suggests in his answer. – Massudaw Jun 19 '15 at 19:59