Closed HeinrichApfelmus closed 11 years ago
Eliminating the Ji/TP monad sounds like a good idea. I'm often writing MonadTP constraints in my code and I'm not sure really sure why. I guess there is flexibility to implement your own monadic behaviors on top of the standard monad?
Could you elaborate on your proposed change a bit more? For instance, would the Element type change or would it remain a string?
My first impression is that this looks to make things more complicated. With the monad implementation, both getValue and getElementById work similarly - there is no need to draw a distinction between the two by having one take a Window argument and another one not. It sounds like in your proposal, functions will be split into two different groups.
Where do the Windows come from? Can I query them from an Element? If so, does that mean I either need to have an Element in scope in order to get a Window or manage the Windows myself?
Just throwing out questions. Heightened scrutiny is my style of review; repeatedly poke ideas to make sure they hold up. :)
Yep, the implementation of the Element
type would become more elaborate and include session context. But since this is an abstract data type, the change would be transparent for the library user.
The Window
is meant to represent the client / browser window. Maybe it should be called Document
, that would be the standard DOM concept. The idea is that threepenny passes the browser window whenever it starts a new session. This way, you can query various elements and so on when setting up the GUI. Example:
main = serve $ Config { ... tpWorker = \w -> setup w >> handleEvents ... }
setup :: Window -> IO ()
setup window = do
body <- getBody window
e1 <- new #+ body
e2 <- new #. "foo" #+ e1
Just e3 <- getElementById window "bar"
...
I think it's quite natural, it's essentially the document
part in document.getElementById
. It's also common in other GUI frameworks like wxHaskell or GTK.
One nice touch about making the session context / browser window explicit is that we can now talk about multiple browser windows. This would make it easier to implement things like the multi-user chat example.
I like the idea of getting a window from an Element
, this can be useful if we don't want to keep the window in scope all the time.
parent :: Element -> IO Window
Hm, but there is one problem I see. Namely, Element
s can exist before they are attached to a particular window. But that seems to be on semantically dubious grounds anyway. What does the following snippet mean?
div <- new #. "foo"
div #+ body
div #+ body
onClick div ...
This appends a single div
element twice to the body and registers an event handler on it. Does the event handler work on both now? Just one? What if we set the HTML context of div
, will that change the document in two places?
EDIT: I have done some tests with JavaScript, and it appears that the browser implements the following semantics: appending an existing element to some node in the DOM will actually remove the element from its previous position! This is somewhat unexpected, but makes sense in that every element has a unique identity. If you want to insert an element twice, you have to copy it first.
I think it's a redesign we should try. The multiple browser windows advantage makes sense to me. Opening new popup browser windows is attractive. I can see users needing that kind of thing. Let's try to iron out the wrinkles.
In your example above, is it true then that each browser window has its own session? My understanding is that each session is sandboxed, and if that's the case then I don't see how you can get multiple windows to interact with each other in a straight forward way. I guess one way to do it is have a standard root Window that is passed at the start of the session, and then spawn new Windows as desired. Something like
setup :: Window -> IO ()
setup window = do
body <- getBody window
el <- new #+ body
onClick el $ \(EventData a) -> do
sWindow <- makeSubWindow window
populatePopupBody sWindow
Bonus points if makeSubWindow generates the popup automatically. Technically each window/subwindow would still have its own session, but they could interact by having Elements (and their sessions) from other Windows within scope on the server code.
Yes, essentially the notion of "session" is entirely replaced by the notion of "browser window", which I find very tangible.
The server can access all browser windows that are in scope. For instance, the following code changes the value in one window when the user clicks in a different one.
setup :: Window -> IO ()
setup window = do
w1 <- makeSubWindow window
w2 <- makeSubWindow window
body1 <- getBody w1
e1 <- new #+ body1
body2 <- getBody w2
e2 <- new #+ body2
onClick e1 $ \(EventData a) -> do
setValue e2 =<< getValue e1
If it's in scope, you can use it.
I do have to mention that these multi-user/-window use cases are not my main motivation, though. Rather, I'm interested in using the plain IO
monad because that makes another modification -- event handlers -- I have in mind much easier.
Ok, sounds good. I think we should move forward. How can I help?
One thing I would like to avoid doing is guiding the development of threepenny-gui so that it more easily fits into the FRP scene (obviously reactive-banana being the primary candidate). I think any proposed redesigns should stand on their own merits, with the goal of making GUI's on Haskell natural and approachable. If in doing this, TPG becomes a better match with FRP, that's great but I hope incidental. That said, I do look forward to learning more about your event handler ideas because I am finding working on TPG very educational so far.
Ok, I have implemented the fairly mechanical change of adding session information to the Element
type and removing corresponding mentions of TP
and MonadTP
. Only the core modules so far, so no tests yet whether everything still works.
I completely agree that threepenny-gui should be able to stand by itself. The changes I have in mind (AddHandler
) are of course informed by FRP, but they are intended to improve usability in the imperative style. I'll open an issue for discussion soon.
The TP
did make a return in the form of the Dom
monad, but the purpose of the latter is now restricted to element creation. In contrast, manipulation of existing elements can be done in the IO
monad as usual. I'm still happy with the overall result, so this issue can be closed for now.
I would like to streamline the API by removing the
TP
monad and theMonadTP
class.The
TP
monad carries around some information about the session. However, the observation is that for functions likeappendTo
orgetValue
that operate on a particular element, the session information is essentially already encoded in the element argument. After all, theElement
type can be thought of as pointing to one particular element in one particular browser window; so it's not just an "abstract" DOM node, but a specific instance in a particular session. To make a long story short, we can change the type toby tracking the session context with every
Element
.This does not work for all functions. For instance, the
getHead
orgetElementById
functions need to refer to a particular session. However, I think it is natural to add an argument calledWindow
which represents a particular browser window (and associated session context).I think that these two changes would improve the API syntactically while keeping it conceptually simple.
Please let me know what you think.