I'm looking for a way to update an arbitrary number of UI.inputs based on a valueChange in any of the inputs.
Here is a toy example with just two inputs:
import qualified Graphics.UI.Threepenny as UI
import Graphics.UI.Threepenny.Core
import Graphics.UI.Threepenny.JQuery
main :: IO ()
main = do
startGUI defaultConfig setup
return ()
setup :: Window -> UI ()
setup w = do
textboxes <- do
tb1 <- UI.input
tb2 <- UI.input
update1 <- stepper "red" $ UI.valueChange tb1
update2 <- stepper "green" $ UI.valueChange tb2
element tb1 # sink value (fmap reverse update2)
element tb2 # sink value (fmap reverse update1)
return $ column [return tb1, return tb2]
getBody w #+ [textboxes]
return ()
Whatever is written in one of the textboxes is copied (reversed) into the other text box.
Now, what if I wanted to have a list of an arbitrary length of input UIs, and any thing written into any of the inputs is copied into all of the other ones? I can create a list of UIs easily enough, but how do I read them all, apply a function to their input (like reverse) and then sink the change into all the other ones?
Any thoughts?
I'm not familiar with pretty much anything you mentioned, but would you be able to make use of the monadic structure and some standard functions to do something like this:
textboxes <- do
tbs <- replicateM 3 UI.input
forM_ tbs $ \tbX -> do
update <- stepper "_" $ UI.valueChange tbX
forM_ tbs $ \tbY -> do
element tbY # sink value (fmap reverse update)
return $ column (map return tbs)
The stepper argument is static and I think you would need a way to skip the triggering control when updating, but in terms of generalizing from two controls to a list of controls I think this might be the right direction?
Related
Starting out with Haskell and Yesod, probably getting a bit too far with Yesod relative to Haskell :)
I build entities using Persistent via
share [mkPersist sqlSettings, mkMigrate "migrateAll"][persistLowerCase|
Game
title String
company String
UniqueTitle title
deriving Show
Tag
label String
description String Maybe
UniqueLabel label
deriving Show
GameTag
gameId GameId
tagId TagId
UniqueGameTag gameId tagId
|]
-- Yesod related code ...
In main I have
main :: IO ()
main = do
let taggings = fromFile :: [(Game, Tag)] -- fromFile code not included
runStderrLoggingT $ withSqlitePool ":inmemory:" 10 $ λpool → liftIO $ do
runResourceT $ flip runSqlPool pool $ do
runMigration migrateAll
let (g, t) = head taggings
gid ← insert g
tid ← insert t
insert (GameTag gid tid)
warp 3000 $ App pool
Doing this I get the first relation into the database, and by selecting elements from the list I can add more 'by hand', but I can't figure out how to get all the relations into the database by somehow iterating over taggings. How do i define a function that I can map over taggings ::[(Game, Tag)] and inserts the game tags of the type GameTag constructed
by Persistent?
The main trick here isn't in pulling out the function, that is easy:
f (g, t) = do
gid <- insert g
tid <- insert t
insert (GameTag gid tid)
The trick is knowing how to use this.... Standard map won't work alone, because the function is defined in a monad (you can use it, it will just give you a list of actions back without running them).
map f taggings -- returns just a list, type [ResourceT IO a], doesn't run anything
Here are two ways to actually run the actions from within main.
sequence (map f taggings) --sequentially runs the actions in the list
or, the more readable
forM taggings f
or, in the slightly more verbose
forM taggings $ \tagging -> do
f tagging
You also might want to look at mapM. Also also should learn about forM_ and sequence_ to supress the (often useless) return values.
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.
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).
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))
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)
I am trying to figure out how to use Haskell threepenny-gui with its reactive functionality to write a program that lets
the user select an item from a listBox
send the selection to an external server
get back a list of results from the server
populate the listBox with the results
repeat
It seems I will need to use Handler, newEvent and register to do the above. If someone could point me to some existing code that does something like the above that would be great.
The closest I have found is GameThing.hs in the threepenny-gui samples directory (but it doesn't use register).
UPDATE:
I am asking whether I should be using Handler, newEvent and register --- and, if so, some clarification or example of those functions.
Also, to be clear, the big picture is:
browser --> threepenny-gui (on localhost) --> backend server (anywhere on network)
<-- <--
In other words, I need to do some IO (based on user selection) then display the results of that IO.
UPDATE: here is my solution (based on #Taldykin's response): https://github.com/haroldcarr/rdf-triple-browser/tree/master/haskell/src
Here is a piece of code. I will add description a bit later.
{-# LANGUAGE LambdaCase #-}
import Graphics.UI.Threepenny as UI
main :: IO ()
main = do
(evFillList, doFillList) <- newEvent
initialList <- valuesSupply ""
behFillList <- stepper initialList evFillList
startGUI defaultConfig $ \win -> do
list <- ul
sel <- listBox
behFillList
(pure Nothing)
(pure $ \it -> UI.span # set text it)
getBody win #+ [grid [[element list, element sel]]]
setFocus $ getElement sel
on selectionChange (getElement sel) $ \case
Nothing -> return ()
Just ix -> do
items <- currentValue behFillList
let it = items !! ix
liftIO $ valuesSupply it >>= doFillList
element list #+ [li # set html it]
setFocus $ getElement sel
valuesSupply :: String -> IO [String]
valuesSupply x = return [x ++ show i | i <- [0..9]]
I have a question on the interaction of Threepenny-Gui with StateT.
Consider this toy program that, every time the button is clicked, adds a "Hi" item in the list:
import Control.Monad
import Control.Monad.State
import qualified Graphics.UI.Threepenny as UI
import Graphics.UI.Threepenny.Core hiding (get)
main :: IO ()
main = startGUI defaultConfig setup
setup :: Window -> UI ()
setup w = void $ do
return w # set title "Ciao"
buttonAndList <- mkButtonAndList
getBody w #+ map element buttonAndList
mkButtonAndList :: UI [Element]
mkButtonAndList = do
myButton <- UI.button # set text "Click me!"
myList <- UI.ul
on UI.click myButton $ \_ -> element myList #+ [UI.li # set text "Hi"]
return [myButton, myList]
Now, instead of "Hi", I'd like it to print the natural numbers. I know that I could use the fact that the UI monad is a wrapper around IO, and read/write the number I reached so far in a database, but, for educational purposes, I'd like to know if I can do it using StateT, or otherwise accessing the content of the list via Threepenny-gui interface.
StateT won't work in this case. The problem is that you need the state of your counter to persist between invocations of the button callback. Since the callback (and startGUI as well) produce UI actions, any StateT computation to be ran using them has to be self-contained, so that you can call runStateT and make use of the resulting UI action.
There are two main ways to keep persistent state with Threepenny. The first and most immediate is using an IORef (which is just a mutable variable which lives in IO) to hold the counter state. That results in code much like that written with conventional event-callback GUI libraries.
import Data.IORef
import Control.Monad.Trans (liftIO)
-- etc.
mkButtonAndList :: UI [Element]
mkButtonAndList = do
myButton <- UI.button # set text "Click me!"
myList <- UI.ul
counter <- liftIO $ newIORef (0 :: Int) -- Mutable cell initialization.
on UI.click myButton $ \_ -> do
count <- liftIO $ readIORef counter -- Reads the current value.
element myList #+ [UI.li # set text (show count)]
lift IO $ modifyIORef counter (+1) -- Increments the counter.
return [myButton, myList]
The second way is switching from the imperative callback interface to the declarative FRP interface provided by Reactive.Threepenny.
mkButtonAndList :: UI [Element]
mkButtonAndList = do
myButton <- UI.button # set text "Click me!"
myList <- UI.ul
let eClick = UI.click myButton -- Event fired by button clicks.
eIncrement = (+1) <$ eClick -- The (+1) function is carried as event data.
bCounter <- accumB 0 eIncrement -- Accumulates the increments into a counter.
-- A separate event will carry the current value of the counter.
let eCount = bCounter <# eClick
-- Registers a callback.
onEvent eCount $ \count ->
element myList #+ [UI.li # set text (show count)]
return [myButton, myList]
Typical usage of Reactive.Threepenny goes like this:
First, you get hold of an Event from user input through Graphics.UI.Threepenny.Events (or domEvent, if your chosen event is not covered by that module). Here, the "raw" input event is eClick.
Then, you massage event data using Control.Applicative and Reactive.Threepenny combinators. In our example, we forward eClick as eIncrement and eCount, setting different event data in each case.
Finally, you make use of the event data, by building either a Behavior (like bCounter) or a callback (by using onEvent) out of it. A behavior is somewhat like a mutable variable, except that changes to it are specified in a principled way by your network of events, and not by arbitrary updates strewn through your code base. An useful function for handling behaviors not shown here is sink function, which allows you to bind an attribute in the DOM to the value of a behavior.
An additional example, plus some more commentary on the two approaches, is provided in this question and Apfelmus' answer to it.
Minutiae: one thing you might be concerned about in the FRP version is whether eCount will get the value in bCounter before or after the update triggered by eIncrement. The answer is that the value will surely be the old one, as intended, because, as mentioned by the Reactive.Threepenny documentation, Behavior updates and callback firing have a notional delay that does not happen with other Event manipulation.
I would like to retrieve the widget value.
In the following, pressing the button b retrieve s_in and print it , in native wxhaskell.
b <- button f [text:= "print text in console",
on command := textCtrlGetValue s_in >>= putStrLn]
I like to do the same on reactive-banana , but in the following, I get "ff" and not the textCtrlGetValue of s_in2
s_in <- textCtrl f []
s_in2 <- textCtrl f []
b <- button f [text:= "print text in console",
on command := textCtrlGetValue s_in >>= putStrLn]
let networkDescription :: forall t. Frameworks t => Moment t ()
networkDescription = do
b_in <- behaviorText s_in "init"
b_in2 <- behaviorText s_in2 "ff"
e_butt <- event0 b command
-- I need an event, triggered by the button, and filled by the b_in2,
sink s_in2 [text :== id <$> b_in]
reactimate $ (\x -> putStrLn x) <$> b_in2 <# e_butt
the sink updates well sin_2 after s_in .
but the following reactimate line does not get me the textCtrlGetValue of s_in/ b_in I wish to get . how can I "get" it ?
The behavior obtained with the behaviorText function will only react to changes that the user made to the edit box. It does not include programmatic changes, like those performed with the sink function.
Distinguishing between user events and programmatic events is essential for writing responsive UI elements that have bidirectional data flow. See the CurrencyConverter example for a demonstration.
If you want to keep track of programmatic changes, I recommend to stay "within the FRP world", i.e. to use the behavior b_out = id <$> b_in instead of trying to read the text from the widget.
(By the way, id <$> x = x.)