HeinrichApfelmus / threepenny-gui

GUI framework that uses the web browser as a display.
https://heinrichapfelmus.github.io/threepenny-gui/
Other
437 stars 77 forks source link

Can we trigger click event without actual clicking it(program clicks it for me)? #263

Closed lllssskkk closed 2 years ago

lllssskkk commented 2 years ago

Im working on a table of button elements. I'm trying to write a function which recursively and virtually clicks its surrounding buttons if i manually click any of them. Manually clicking is easy peazy. But how to let the program click rest of buttons for me?

some naive example code illustrate my problem ` text <- UI.string "Unclicked" testButton <- UI.button # set UI.style [("height", "100px"), ("width", "100px")] # set UI.children [text]

getBody window #+ [row [grid board,return textButton]]

on UI.click testButton $ _ -> do return text # set UI.style [("color", "lime")] # set UI.text "Clicked"

---In this region, some magic codes would automatically click the button -- blabla bla --When i open up the tab in browser, the text color should already been set to lime `

I know this is achievable in js, like described in the web https://tutorial.eyehunts.com/js/how-to-trigger-click-event-without-clicking-javascript-example-code/.

Im wondering if such trigger exists in threepenny ui?

I know we can call javascript code from Haskell through js ffi. But the document is kinda lacking. In case of callFunction, the second parameter is a value with the type of JSFunction a. How to declare such value? Can someone elaborate on this?

lllssskkk commented 2 years ago

It looks that we can call following functions autoClick :: JSFunction () autoClick = ffi "document.getElementById('test').click()" and callFunction autoClick to perform the same action as described in the link.

However, some errors pop out as doing this. Foreign.JavaScript: Browser window disconnected. Foreign.JavaScript: Browser <-> Server communication broken.

This posts says this occurs when i try to reload the page. But i just click buttons. Any thoughts?

bradrn commented 2 years ago

The FRP way to think about this would be to forget about ‘clicking’ entirely. We have a bunch of events here: one which triggers when any of buttons 1–2 are pressed, one which triggers when any of buttons 1–3 are pressed, one which triggers when any of buttons 2–4 are pressed, and so on. We can make these using unions like so:

do
    btn1 <- UI.button
    btn2 <- UI.button
    btn3 <- UI.button
    btn4 <- UI.button

    let clicked1 = unions [btn1, btn2]
        clicked2 = unions [btn1, btn2, btn3]
        clicked3 = unions [btn2, btn3, btn4]
        clicked4 = unions [btn3, btn4]

And then you can do stuff depending on the events clicked1, clicked2, clicked3, clicked4.

Of course, this becomes a bit clumsy when you have more than about five or so buttons, but it’s easy to encapsulate this in a function:

surroundingClicked :: Int -> [Event a] -> Event a
surroundingClicked 0 es = unions [head es, es !! 1]
surroundingClicked n es
    | n == length es-1 = unions [es !! (length es-2), last es]
    | otherwise = unions [es !! (n-1), es !! n, es !! (n+1)]

And then you can handle the event for e.g. button 5 by attaching an action to surroundingClicked 5 buttonEvents.

lllssskkk commented 2 years ago

OK nvm. I fixes it. I shouldn't place # set UI.id_ "placeholder" right after # set UI.children [xs]. It looks like it creates some unwanted behaviour.

lllssskkk commented 2 years ago

The FRP way to think about this would be to forget about ‘clicking’ entirely. We have a bunch of events here: one which triggers when any of buttons 1–2 are pressed, one which triggers when any of buttons 1–3 are pressed, one which triggers when any of buttons 2–4 are pressed, and so on. We can make these using unions like so:

do
    btn1 <- UI.button
    btn2 <- UI.button
    btn3 <- UI.button
    btn4 <- UI.button

    let clicked1 = unions [btn1, btn2]
        clicked2 = unions [btn1, btn2, btn3]
        clicked3 = unions [btn2, btn3, btn4]
        clicked4 = unions [btn3, btn4]

And then you can do stuff depending on the events clicked1, clicked2, clicked3, clicked4.

Of course, this becomes a bit clumsy when you have more than about five or so buttons, but it’s easy to encapsulate this in a function:

surroundingClicked :: Int -> [Event a] -> Event a
surroundingClicked 0 es = unions [head es, es !! 1]
surroundingClicked n es
    | n == length es-1 = unions [es !! (length es-2), last es]
    | otherwise = unions [es !! (n-1), es !! n, es !! (n+1)]

And then you can handle the event for e.g. button 5 by attaching an action to surroundingClicked 5 buttonEvents.

Wow, this code definitely looks much more tidier. I will try to refactor my code this way. Thx for your reply. I didn't anticipate any response during xmas and enjoy your holiday. :)

bradrn commented 2 years ago

You’re very welcome!

(If this solves your issue, could you close the issue please?)