Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Layout combinators to simplify GUI layout #24

Closed
HeinrichApfelmus opened this issue Apr 15, 2013 · 49 comments
Closed

Layout combinators to simplify GUI layout #24

HeinrichApfelmus opened this issue Apr 15, 2013 · 49 comments
Assignees
Milestone

Comments

@HeinrichApfelmus
Copy link
Owner

I would like to simplify the API for GUI layout.

Since our focus is on GUIs, I wonder if we should downplay the full HTML capability and instead provide a few combinators for aligning GUI elements. WxHaskell has a layout combinators like grid, row and column that make it very easy to align elements. For instance, the following screen layout

was created by the following code

do
    f           <- frame    [ text := "CRUD Example (Simple)" ]
    listBox     <- singleListBox f []
    createBtn   <- button f [ text := "Create" ]
    deleteBtn   <- button f [ text := "Delete" ]
    filterEntry <- entry  f [ ]

    firstname <- entry f [ ]
    lastname  <- entry f [ ]

    let dataItem = grid 10 10 [[label "First Name:", widget firstname]
                              ,[label "Last Name:" , widget lastname]]
    set f [layout := margin 10 $
            grid 10 5
                [[row 5 [label "Filter prefix:", widget filterEntry], glue]
                ,[minsize (sz 200 300) $ widget listBox, dataItem]
                ,[row 10 [widget createBtn, widget deleteBtn], glue]
                ]]

Even though the layout is fairly complex, it can be specified in just 18 lines of Haskell code, half of which are essentially just a list of widgets.

The thing is that HTML allows very rich and aesthetically appealing user interfaces, but the design tends to take a lot of time (also because HTML+CSS has an impoverished box model compared to, say, TeX). When writing a GUI application, I wager that programmers probably to prototype the functionality first and upgrade the design later. Hence, a couple combinators for quickly creating layouts might come in very handy.

What is your opinion of the combinators grid, row, column?


Concerning style, WxHaskell specifies attributes as

set x [ attr := value, attr2 := value ]

but I don't think that this is worth copying. I prefer the elegant Ji style that uses chains of # in a clever way.

However, I would like to reduce the number of variants of #, #+, #= and so on, and streamline this part of the API as well. Unfortunately, there are many possibilities for doing so, and I have a hard time making decisions without guidance from example GUI code.


@daniel-r-austin, I would would be very interested in a screenshot of the GUI program you mentioned. Also, did you create elements mostly with the Haskell code (new and so on), or did you specify the layout by writing a HTML page first? What about GUI elements that are created dynamically, like elements of, say, a shopping cart?

@fluffynukeit
Copy link
Contributor

Grid, row, and column make sense to me. In fact, the GUI I am working on (see below) uses a CSS grid with sizing and positioning tweaked using the CSS. It was indeed a pain but the CSS approach thankfully has tons of resources available online and is pretty complete, if messy. I am concerned that using these combinators will clash with the CSS itself, and the "philosophy" of HTML pages in that style/layout is defined in the CSS and content in the HTML. Adding the combinators as a quick-and-dirty alternative (but not replacement) to CSS sounds good to me.

I do prefer the Ji style to the wxhaskell you provided. I don't have a problem with the # variants, though, but I would be open to alternatives.

I have posted a short blog entry on the current status of my GUI here. This program is a game utility for the game Torchlight 2, which is a hack-n-slash action game. The game has a limited inventory space, and my goal with this program is to enable users to extract item data from the game's save files, store it separately, search it, and when desired import it back in. The Haskell source for the GUI is here. On the root level of the project is a GUI directory that has the starting HTML file as well as the CSS file.

Almost all the elements are created dynamically with new. My starter HTML file is essentially empty. The GUI has some tester elements still in it, like the "Hello!" message. Below the hello message is a status string that is updated as the program does stuff. I am following a process similar to the one you describe in that first I want it to work, and only after that will I strive to make it pretty.

What I want to do next GUI wise is add a search box and a list of database results in the bottom half that is currently empty. I want users to be able to select and drag one of these search results into the grid at the top, which would have the effect of injecting the item into the game's save file. I need to learn and implement some database Haskell stuff before then as well as implement/test writing to the game's save files.

@HeinrichApfelmus
Copy link
Owner Author

Wow, thanks a lot Daniel, this is exactly the kind of "beyond my imagination" GUI example that I was looking for. 😃 I would like to study it further. Since I don't have Torchlight2 installed, could you post a transcript of the final HTML rendering somewhere, for instance as a gist?

The background and icons obviously don't match the traditional system UI styles, but I definitely want to treat the "non-traditional" style on an equal footing. In wxHaskell, they would have required drawing primitives (hence issue #22), but it appears that regular HTML elements -- or maybe SVG elements -- can substitute for these nicely.

I will need some time to mull about a good design for combinators that offer quick-and-dirty layout but can also accommodate custom CSS styling with minimal code changes. The philosophy that HTML is completely free of style specifications is a bit of a lie (elements in sequence are laid out in sequentially, nested elements inherit styles), but hopefully I will come up with something satisfying. In any case, I would like to replace the tedious incantations of new #. "dummyClassNameForLayout" with something more crisp, using Haskell as a "slightly better HTML+CSS", so to speak. (You are kind of using it in this way by starting with an empty HTML page.)

@fluffynukeit
Copy link
Contributor

Here it is. I added a small clickable div to enable saving, which I'm working on now. Also I had to manually fix some tags (mostly all img tags) to get the formatter to work.

@HeinrichApfelmus
Copy link
Owner Author

Thanks, will take a look!

HeinrichApfelmus added a commit that referenced this issue Apr 19, 2013
…ayout combinators. This way, it is possible to style the layout differently after the fact. #24
@HeinrichApfelmus
Copy link
Owner Author

Ok, so I implemented the combinators grid, row, column for making cheap layouts. Example usage.

Essentially, these combinators just wrap everything in a couple of <div> tags and assign the appropriate CSS styles for tabular layout (display:table). Implementation here. The CSS styles are assigned in the form of classes, so it is possible to override their style after the fact. I hope that this gives the best of both worlds: easy layout for everyone who knows nothing about CSS and complete control over layout for CSS gurus. (I really like how simple this implementation turned out to be. Yay for Haskell as a HTML macro language!)


I also started to overhaul the mechanism for setting properties and the (#) combinator. These changes may be more controversial.

I would like to synchronize # with the diagrams library, so that it becomes plain reverse function application. The stateful nature of setting attributes is now moved to a new helper function set. The canonical usage looks like this:

new
    # set style [("color","#CCAABB")]
    # set draggable True
    # set children otherElements

Also, I would like to borrow an idea from wxHaskell where properties like style, draggable etc. are abstracted into a new datatype Property which supports both get and set operations. For instance, I would like the following to become possible

canDrag <- someElement # get draggrable
when canDrag $ do
    element otherElement # set draggable False

(The element is a synonym for return and is needed because set expects an IO Element as last argument.) Essentially, this is a systematic way to organize the different getters and setters like setClass, setAttr, allowDrag etc.

What do you think?

@fluffynukeit
Copy link
Contributor

I am liking the way this is going. What I'd like to do this weekend is pull in the latest updates and try to migrate my current GUI layout as a test case. Actually, I might wait until these set/get updates are made as that seems like a larger departure from the old framework, and thus more worthy of investigation. These ideas look good to me on paper so I'd like my opinion to be informed by some experience.

@HeinrichApfelmus
Copy link
Owner Author

Ok! I would also like to combine the get/set stuff with a large module reorganization, so it's a good idea to wait for a short while.

My motivation for a module reorganization is twofold:

  1. Many existing setters like setAttr or even emptyEl become obsolete and lots of new names will take their place, so most of the existing code will break anyway.

  2. By dropping prefixes, the properties eat up precious names like title, text, value and so on. By organizing the modules in a clever way, we can allow qualified names for the more specific combinators. Example:

    import qualified Graphics.UI.Threepenny as UI
    import Graphics.UI.Threepenny.Core
    
    main = do
        ...
        elOk <- UI.button
        element elOk
            # set UI.text "Add"            -- set      is unqualified
            # set UI.cssClass "shiny"      -- cssClass is qualified
        on UI.click elOk $ \_ -> do ...
        ...
    

    This way, everything is in scope with the module prefix UI. but the most fundamental combinators like set, #, etc. are also available vanilla.

@fluffynukeit
Copy link
Contributor

@HeinrichApfelmus , is the big update you want to do complete? Let me know when you think it's ready for me to try out, please. Thanks.

@HeinrichApfelmus
Copy link
Owner Author

@daniel-r-austin Oops, sorry, yes. The gist of it is ready by now. There are probably still a few combinators to be added as I convert the other examples, but it's ready to be tried out.

@HeinrichApfelmus
Copy link
Owner Author

Ok, I have now converted all the examples and as of commit 5c9b6b0, the introduction of get/set combinators and the module reorganization are "officially" done and ready to be tried out.


Converting the examples was very instructive for me. Here my experience with the "new" style.

Like:

  • Code feels more uniform to me, there is now a canonical way to set, say, the class attribute instead of many competing variants like #. or setClass. The code became a bit longer, but that didn't bother me.
  • I especially like the set (attr "src") (show number) syntax as it neatly decomposes everything ("I want to set something, and the thing I want to set is an attribute, and its name is src"). It does require parentheses, though.

Neutral:

  • Chains of # cannot be applied to an Element directly, it has to be lifted to an IO Element first. Example: element el # set cssClass "xy" .... It's not pretty, but didn't feel bad about it either.

Dislike:

  • The Window parameter is really annoying. The TP monad wasn't so bad, after all.
  • Building the DOM with appendTo combinator feels wrong to me. It's not functional in style as it relies on destructive updates to work. This reliance on destructive updates also forces us to distinguish between Element (a particular element that one can apply updates to) and IO Element (a recipe for creating an element) and we have to convert between the two all the time. Compare this to the grid combinator, which builds a parent element from a given list of child elements.

I will now try to rethink the design and fix the dislikes. Since the browser does DOM manipulation with destructive updates, the distinction between Element and IO Element cannot be avoided, but maybe the appendTo combinator can be discouraged. I'm envisioning a style similar to existing HTML combinator libraries like xhtml or blaze-html.

It's probably a good idea if I make a new branch for that and don't push everything onto master.

@fluffynukeit
Copy link
Contributor

I started to do my transition yesterday (very casually though). It's good to see that we have similar impressions. Here are mine so far:

  • The function types are much easier to deal with now. Many times I found myself needing to explicitly write the types using MonadTP just to get the compiler to infer something correctly. Not so with this new update.
  • The set function is more consistent than the old functions, as you mention. However, in my code at least, I found myself repeatedly using the same set text, set cssClass, etc that the old #x combinators facilitated. At some point I thought, "Why am I changing these? I am comfortable with those combinators and my code already uses them. They save keystrokes. I think I'll keep them," but the DOM module isn't exposed in the cabal file. :-/
  • I only started to do the transition to using Window, but then stopped because I felt as if I would need to thread a window parameters throughout a lot of functions. This would have been rather tedious so I decided to work on other things instead. Perhaps we can institute something like withWindow doStuff. Since I only use one window anyway, it would be pretty easy to add one or two withWindow calls where I need them, although I expect the actual implementation of withWindow to be challenging, perhaps more than it's worth.
  • I really like the new event handling. The one thing that I think could be improved, if possible, is to handle it in the same breath as using set. Something like img # set cssClass "item" # on hover $ \_ -> doSomething. I felt like my flow was interrupted having to pull out the element directly with on hover e $ ..., but this is no different than the old way.
  • appendTo doesn't leave the same kind of bad taste in my mouth as it does yours, but I'd welcome something more functionally styled.
  • I didn't grab the most recent commit (that you reference above), but I had trouble compiling the examples using the commit I did grab. I commented them out in the cabal file to get them to install.

This was a big redesign, and I really like the consistency improvements that were made. The nits I have are mostly related to convenience of style.

@HeinrichApfelmus
Copy link
Owner Author

Nice!

  • Concerning the #x combinators, I wasn't bothered by their absence because I've come to think that code readability (consistency, explicit patterns) trumps keystroke reduction. Still, I agree that less keystrokes would be better. Fortunately, they always appear in the same pattern, namely

    createElement
        # set cssClass "foo"
        # set text "bar"
        # appendTo otherElement
    

    and my hope is that this can be absorbed in the planned more functional set of combinators. For changing the text or the class after the fact, for instance in response to an event, the relative verbosity of set text should be fine.

  • Concerning the on, the only reason I didn't include it in the # chain is operator precedence and parentheses. The expression

    element e # set text "..." # on hover $ \_ -> ...
    

    will be parsed as

    (element e # set text "..." # on hover) $ \_ -> ...
    

    which is not what we want at all. Due to precedence and the lambda expression, the event handler has to be put into parentheses, which is only pleasant if the handler is just a single line. Otherwise, a separate on will be more convenient.

    The only way to break the lambda expression I can think of right now is to use list notation,

    set widget [on hover :~ \_ -> doSomething , attr "src" := show value ]
    

    This is how wxHaskell does it.

@HeinrichApfelmus
Copy link
Owner Author

Ok, I made a new branch html-combinators where we can experiment with creating Elements in a style reminiscent of the various HTML combinator libraries.

The Chat.hs example contains a very first attempt, I'm actually quite pleased. Apparently, the TP monad makes a triumphant return with the new name Dom, but I think that's ok.

@fluffynukeit
Copy link
Contributor

I'll try to test this guy out this weekend. I'm trying to track down a nasty bug that has otherwise eluded me.

HeinrichApfelmus added a commit that referenced this issue May 13, 2013
…tributes to be used with (#). Return to (#.) for setting classes. Change (#+) to accept a list of children instead. #24

Clean up some leftovers from the 'Property' -> 'Attr' rename.
@HeinrichApfelmus
Copy link
Owner Author

I tried to copy existing HTML combinator library designs, but that didn't work too well because it would have mean to duplicate the attribute/property system to get a nice syntax for setting element attributes on creation. In the end, I reverted the whole thing to the "chained applications of # style". However, the new twist is that #+ now has an entirely different semantics: it appends a list of children to the first argument. I think this makes the creation of document trees very pleasant.

I renamed the Property to Attribute to keep things in line with other GUI libraries like Gtk2Hs or wxHaskell. Unfortunately, that clashes with the notion of attributes for HTML elements. What do you think, is this too confusing?

@fluffynukeit
Copy link
Contributor

I'm just getting around to looking at the last two updates. I haven't tried them out on my own yet, but looking over the Chat.hs, I do like keeping the #. operator instead of using the !. operator for consistency. withWindow looks pretty convenient for both the general and my 1-window use cases, so that's a big plus for me.

Doing appending with a list of children elements looks great. At first I was skeptical because I didn't like the idea of needing to wrap single children in [] to do appending, but it's actually really great because they also serve as make-shift normal parenthesis (). Something like [text $ show timestamp] would normally be encased in parenthesis anyway, so it's cool to use list brackets and also enable appending a list. Very smart.

As for the Property vs Attribute naming, I'm undecided. When I use TPG, I often omit the type signatures, so one name vs the other doesn't have much consequence for me. I guess it depends on our target user demographic; are they people familiar with Haskell GUI toolkits, or are they familiar with html stuff? I know for me I have more experience with HTML, so I couldn't care less whether TPG is consistent with other Haskell GUI toolkits.

@HeinrichApfelmus
Copy link
Owner Author

Great!

I have now converted the Buttons example and I'm pleased with the results. The only two things that I still find ugly are

  • Need to carry around the Window parameter when appending new elements to existing ones, like the body.
  • Text nodes are not represented as Dom Element, instead they are created using set text.

Hopefully I'll figure something out there as well.

@HeinrichApfelmus
Copy link
Owner Author

Nice!

  1. Fair enough, I pushed a small patch that defines a new id_ attribute.

  2. Ok, that's a problem. The overall idea was that DOM is used solely for building elements, whereas IO is used mainly for everything else, like manipulating existing elements. The MissingWords example may be a good prototype: creating elements uses DOM while manipulating elements uses IO.

    I don't think it's possible to get rid of return and element, because they are needed for being able to chain the # operator. This was already true for Ji. Compared to Ji, we now need to give less names thanks to the (#+) combinator, but we also need to use element more often.

    So, the only difference between IO and Dom is that one has to use withWindow occasionally. How much trouble does this combinator give you?

    I would like to take a look at any particularly awkward code examples that you have.

  3. You can actually write getBody w # set children []. (The set operation works for both monads.) Does that help?

It is actually possible to get rid of the Dom monad entirely. The idea is that elements are first created in "limbo" and only then bound to a particular browser window. However, we would have to implement a DOM simulation on the server and a protocol for transferring DOM trees between client and server. I have refrained from doing so unless it's really a problem, but it may just be.

@fluffynukeit
Copy link
Contributor

OK, well that's what I get for pulling an example out of my ass. I guess it's just better to illustrate with code. Forgive the indentation errors due to copy/paste.

frontend messages w = do
set title "FNIStash" (return w)
body <- getBody w

(overlay, overlayMsg) <- withWindow w $ overlay
element body #+ [element overlay]
underlay <- withWindow w $ new # set (attr "id") "underlay"
element body #+ [element underlay]
frame <- withWindow w $ new # set (attr "id") "frame"
element underlay #+ [element frame]
msgWindow <- withWindow w $ controls messages (element frame)
msgList <- liftIO $ onlyBMessages messages

forM_ msgList $ \x -> do
    case x of
        Initializing AssetsComplete -> void $ withWindow w $ stash messages #+ [element frame]
        Initializing Complete -> do
            assignRandomBackground underlay
            crossFade overlay underlay 350               
        Initializing x -> void $ handleInit x overlayMsg               
        LocationContents locItemsList -> withLocVals w locItemsList (updateItem)
        Notice notice -> void $ withWindow w $ noticeDisplay notice # appendTo msgWindow >> return (scrollToBottom msgWindow)
        Visibility idStatusList -> withLocVals w idStatusList $ \e v _ -> setVis v (element e)

This was the result of my "naive" translation of the IO stuff I had before to the newest release. Lots of usages of element, withWindow, and void. It was difficult for me to get everything to type check, and if there was a type error I wasn't sure if it was something I was doing locally or due to type inference in some other piece of code I was calling. Like I said it might have been easier for me if I had partitioned my code more along "building elements" and "everything else" lines from the very beginning.

If you want to see more of the code, you can check out my FNIStash repo in the TPG-update branch. My GUI stuff is in src/FNIStash/UI (just pushed).

@HeinrichApfelmus
Copy link
Owner Author

Here's how I would write your example. Since the code is mainly concerned with building elements, I would put it in the Dom monad and slap a withWindow at the outermost level. This probably allows you to drop some of the w argument from functions like withLocVals as well.

Note that in general, it is a good idea to populate children at the time when the element is defined/created. This mirrors the functional style where you, say, define a list by declaring the elements it contains, rather than creating an empty list and updating it to add elements imperatively. This is the essential stylistic change made possible with the new (#+) combinator. Note that the treatment msgList seems to follow the imperative style.

frontend messages w = do
    return w # set title "FNIStash"
    body <- getBody w

    withWindow w $ do
        (overlay, overlayMsg) <- overlay
        frame    <- new # set (attr "id") "frame"
        underlay <- new # set (attr "id") "underlay" #+ [element frame]

        element body #+ [element overlay, element underlay]

        msgWindow <- controls messages (element frame)
        msgList   <- liftIO $ onlyBMessages messages

        forM_ msgList $ \x -> case x of
            Initializing AssetsComplete -> void $
               -- Frame will be moved to become a child element of stash messages . Is this really correct?
                stash messages #+ [element frame]
            Initializing Complete -> void $ liftIO $ do
                assignRandomBackground underlay
                crossFade overlay underlay 350
            Initializing x -> void $
                liftIO $ handleInit x overlayMsg               
            LocationContents locItemsList ->
                withLocVals w locItemsList (updateItem)
            Notice notice -> void $ do
                element msgWindow #+ [noticeDisplay notice]
                -- there seemed to be a mistake concerning  return  here
                liftIO $ scrollToBottom msgWindow
            Visibility idStatusList ->
                withLocVals w idStatusList $ \e v _ -> element e # setVis v

I'm inconclusive about what this code tells us about the distinction between Dom and IO. There is only call to withWindow remaining and the other arguments w can probably be removed. The liftIO are a little annoying, but interleaving the creation of elements and interactivity like crossFade seems odd to me.

It is clearly less noisy to remove Dom entirely, but as said, this incurs an implementation cost.

@fluffynukeit
Copy link
Contributor

Thanks for taking me to school. That code is much improved. I only had to put an additional liftIO before forM_ to get it to type check. And you are indeed correct about the errors in the AssetsComplete section; I made a mistake in the transcription when updating TPG. Unfortunately that didn't fix the issue I'm having with crossFade (which, if I can't figure it out, might ultimately be a problem with animate's complete argument getting invoked), but the new cleanness make it easier to inspect for issues.

WithLocVals makes a call to getElementsById so I'm not sure I can remove the w arguments. I tried using ask to get the window from within withLocVals, but GHC complained about no MonadReader instance. Maybe I'm doing the wrong thing.

As for the interleaving of element creation and interaction, I'm not sure how to do it otherwise but am open to suggestions. The way it is set up now is to process messages from the backend . When the frontend gets a new message, the GUI is updated. Definitely imperative-ish. In crossFade, this is an animation to transition between the loading screen (overlay) and the main GUI content (underlay). When a Notice is processed, this is adding the notice message to a scrolling status message window.

So let's say I make the processing of Notice more functional and use a lazy list like element msgWindow #+ map noticeDisplay myLazyListOfMessages, where noticeDisplay sets the class of the node based on the contructor used for each message. Wouldn't this mean that I I would have to collect all my messages into a single lazy list on the backend and issue a single Notice event that would process the list? I just want to know how big of a project I'll get myself into if I try to implement your style suggestion. Right now most of my high level backend code has access to the Chan since I need it to send events to the frontend, so issuing new Notices is pretty easy since the high level stuff is in IO anyway.

@HeinrichApfelmus
Copy link
Owner Author

Ah, concerning the element creation, I think that some imperative style cannot be avoided. In particular, the myLazyListOfMessages idea will not work: it would be equivalent to specifying the layout of the window when all messages have been delivered, but of course, what you actually want to do is to display all the intermediate states. To use functional style here, one would have to make time explicit, at which point we would be doing FRP.

But now I understand how your function works in the first place: apparently, you are reading the channel into a lazy list and the forM loop blocks when there is no element available. Clever! That's a really nice way to write an event loop. It do feel a little uneasy with it, however, because it involves unsaveInterleaveIO to control the order of side effects. I would probably write a small combinator

foreverChannel :: Chan a -> (a -> IO ()) -> IO ()

and use foreverChannel messages in lieu of your forM_ msgList invocation. Your implementation would be something like

foreverChannel chan f = mapM_ f =<< getChanContents chan

whereas a "more pure" implementation would be

foreverChannel chan f = forever $ readChan chan >>= f

Concerning the Dom vs IO issue, I have taken a look at the code and think that doing it properly (which implies being able to move elements between browser windows) would be best done by rewriting the whole backend. The main issue is that unique element IDs are created on the fly in the browser window, but keeping them unique across browser windows would require some communication with the server. At this point, it's probably easier to tackle this as part of issue #23.

However, I think that there is a cheap and mostly working implementation that can eliminate the Dom monad. The drawback is that most attributes will be forced to stay WriteAttr until a better implementation is in place. I will look into coding it soon.

@fluffynukeit
Copy link
Contributor

Thanks for the tips! I'll try them out when I get a chance.

The crossFade function that has been giving me trouble hasn't been resolved, but I think the issue is using set style to set the visibility while also making calls to fadeOut/In. The interaction between the IO and Dom in this case isn't clear to me, but I expect that the simplification you have in mind will make this easier to debug (if the issue still exists then).

@HeinrichApfelmus
Copy link
Owner Author

Concerning the weird bug you're encountering, I just noticed that my implementation of the Event stuff introduced a subtle bug.

Namely, I had assumed that each DOM element in the client window is referenced by a unique element ID elid. However, it turns out that a single DOM element on the client may be referenced by multiple elid. The server assumes a unique elid to manage event handlers, so something will go horribly wrong.

In particular, if you create an Element with an id_ attribute, then use getElementById to retrieve it and then register an event handler, the previous event handlers will be lost. This may or may not explain your problem.

I will try to fix this as soon as possible.

@HeinrichApfelmus
Copy link
Owner Author

Ok, I think I have the fixed the issue, so that elid are now in bijection with DOM elements. Does that magically improve your problem?

HeinrichApfelmus added a commit that referenced this issue Jun 30, 2013
…wser window in advance. Implementation of 'getValue' still missing. #24
@HeinrichApfelmus
Copy link
Owner Author

Alright! I have implemented and pushed a cheap and mostly working solution to remove the Dom monad entirely. Does it help with your code?

Internally, an Element can now have two states: an element can be Alive, which means that it lives as a JS object in a browser window, or it can be in Limbo, which means that we represent it as a function that asks for a browser window and creates the element in this browser window. Updating an element in limbo means to append the update action to the "queue" of actions that have to be executed on creation time.

One consequence of this approach is that some operations like scrollToBottom may be delayed until the element is actually created in a browser window, which may result in an unexpected order of execution. However, I think this is ok for now.

@fluffynukeit
Copy link
Contributor

Unfortunately, the bijection fix does not seem to fix my issue. I can work around it for now (ie settle for some acceptable but not ideal behavior). Going into it with more specificity might be worth a new issue as it seems a bit beyond the scope of the architecture redesigns here.

I must have cloned the repo just before you submitted the new Limbo updates, so trying those out might be next for me.

@HeinrichApfelmus
Copy link
Owner Author

Hope you had relaxing holidays, or are still having them, as the case may be. ⛺ 😄

I'm eager to make a first release, so if you have time to check out the Limbo changes and let me know whether they break anything unexpected, that would be awesome.

@fluffynukeit
Copy link
Contributor

Thanks for the reminder. I'll start the effort ASAP.

@fluffynukeit
Copy link
Contributor

I haven't made a lot of progress yet, but working on it so far has been pretty smooth. I have run into an issue though, which I hope is easy to resolve. With this candidate release, how does one get an instance to Window? Must I pass it around myself from my startGUI callback? There's a getWindow internal function that operates on an Element, but it's not exposed as far as I can tell.

Specifically, I have an element (call it "icon") that, when moused over, appends a new element to its window's body. Essentially it works like an info popup (there is a picture on my blog). The event handler is defined deep down with the icon definition itself, which is pretty far removed from the "top level" where the Window is readily accessible.

@HeinrichApfelmus
Copy link
Owner Author

Indeed, you would have to pass a Window from the startGUI way down to the callback. The reason is that Element are no longer associated with a Window, they may live in Limbo, after all. That's why I had to remove the getWindow function

However, I could change the Window type to include the limbo and implement a function

getWindow :: Element -> IO Window

Sometimes, this function will return a window corresponding to limbo. Trying to do anything interesting like get cookies on that would result in an empty result or even an exception, though.

Actually, I think this is probably a good idea, even if some functions will throw exceptions. After all, the API makes it look as if Element can be moved between different windows, so it's only natural that you can query which window an element is currently in.

@fluffynukeit
Copy link
Contributor

So Elements are in Limbo before they are attached to Window, and as I understand it Limbo is kind of like building up JS instructions for the driver before they are executed in a particular Window. So the idea is that calling getWindow on an unattached Element is nonsensical, but couldn't getWindow itself be one of those instructions that is built up? For instance, I could call getWindow on an Element in Limbo, then attach that Element to three separate Windows. Attaching causes getWindow to run and return each of the three Windows respectively. Until the Element becomes alive, getWindow just sits in the queue of driver instructions waiting to be executed.

Unrelated, but there are old data definitions in Internal/Types that should be removed before any release.

HeinrichApfelmus added a commit that referenced this issue Jul 16, 2013
@HeinrichApfelmus
Copy link
Owner Author

Ok, I took the easy way out and implemented the type signature Element -> IO (Maybe Window) instead. 😄 In your case, you can just pattern match on Just to get the desired window, because the event handler can only fire when the element is actually bound to a window.

The trick with building instructions works for things that do not return results, but functions like get cookies have the "problem" that the next IO operation may do a case analysis on the result, so you have to know the result before you can proceed with program execution. There's a good reason why most attributes are WriteAttr right now... I agree that it would be possible for some things like set title to delay the action on the window until the element is attached to a window, but I think the semantics would feel very weird. In contrast, setting the color of an element doesn't have any visible effect until it is created, so that's fine.

Unrelated, but there are old data definitions in Internal/Types that should be removed before any release.

Oh, which ones? It seemed to me that they are all used by the Internal.Core module. Note that the Core module reuses a couple of names.

@fluffynukeit
Copy link
Contributor

Ah, duplicate names, but both used. Never mind about that then.

I did a transition to this release (twice, actually, due to a git snafu). I can compile but am experience some weird behavior I have not yet been able to explain. Most of it is related to events - either callbacks not being fired or elements not being found. Still trying to diagnose these problems but they are taking a while.

@HeinrichApfelmus
Copy link
Owner Author

Ok, that doesn't sound too good. Chances are that I have introduced bugs in the code, and I suspect they have to do with the assignment of unique IDs to elements. Is it possible to boil it down to a minimal example?

Alternatively, could you make a mockup variant of FNIStash that runs on OS X and does not require an existing installation of Torchlight 2? That way, I could try to take a look as well! Maybe a couple of dummy icons and dummy item descriptions will do.

@fluffynukeit
Copy link
Contributor

I haven't tested whether the events work on the examples. That would be my first step toward a minimal example. Making a OS X mockup would be a lot harder.

@fluffynukeit
Copy link
Contributor

Unfortunately, I have not been able to reproduce the issue so far. Here are some other, unrelated comments in the mean time.

  1. It would be convenient if the #+ operator had precedence such that map after it did not require parentheses. Currently I find I'm having to use #+ (map ...
  2. Also convenient would be common attributes defined, like src instead of (attr "src").
  3. It's not clear to me why some events are in the JQuery file and not the Events file. I would have expected them all to be in Events.
  4. Ideally I would like Quasiquotes back into the release so my CSS file can be embedded into the executable, and I don't have to worry about paths or copying it to the correct place.
  5. #+ is versatile enough to handle single elements, but I still found I wanted a function to append individual elements.
  6. Is there an emptyEl function available? I wanted its functionality so I did set children [], which seems less explicit to me.

I'll give a good effort to resolve the still lingering event issues, but at some point I'll need to concede defeat.

@HeinrichApfelmus
Copy link
Owner Author

Thanks! Don't worry if you can't track down the remaining issue. Right now, I think it's more important to release whatever we have in order to let a larger audience play with it. The version numbering starts at 0.1 for a reason. 😄

Concering the other points:

  1. This should work already? The following is perfectly fine

    getBody w #+ map element [foo, bar]
    

    But you're probably using the $ operator? In this case, we have to adhere to the informal golden rule that all other operators must bind tighter than $, no exception for us.

  2. Adding a patch. Most of the HTML attributes are very obscure, though, we should probably only keep a few.

  3. Fair enough. I didn't want to mess with the JQuery module, though, and sendvalue is not a standard HTML event. For the moment, I would like to postpone this to version 0.2.

  4. I would like to have QuasiQuotes back, too, but unfortunately, there is a bug with Haddock. Also, it only applied to the standard CSS file, not custom ones. I suggest to postpone it to version 0.2. I've added a note to the corresponding issue.

  5. and 6. In these cases, I would like to have a single way of doing things to keep the API small. #+ is not as convenient for partial application, though, so example code would help.

@fluffynukeit
Copy link
Contributor

Ok, sounds good! Onward we go.

On Thu, Jul 18, 2013 at 12:45 PM, Heinrich Apfelmus <
notifications@github.com> wrote:

Thanks! Don't worry if you can't track down the remaining issue. Right
now, I think it's more important to release whatever we have in order to
let a larger audience play with it. The version numbering starts at 0.1for a reason. [image:
😄]

Concering the other points:

This should work already? The following is perfectly fine

getBody w #+ map element [foo, bar]

But you're probably using the $ operator? In this case, we have to
adhere to the informal golden rule that all other operators must bind
tighter than $, no exception for us.
2.

Adding a patch. Most of the HTML attributes are very obscure, though,
we should probably only keep a few.
3.

Fair enough. I didn't want to mess with the JQuery module, though, and
sendvalue is not a standard HTML event. For the moment, I would like
to postpone this to version 0.2.
4.

I would like to have QuasiQuotes back, too, but unfortunately, there
is a bug with Haddock. Also, it only applied to the standard CSS file, not
custom ones. I suggest to postpone it to version 0.2. I've added a note to
the corresponding issue.
5.

and 6. In these cases, I would like to have a single way of doing
things to keep the API small. #+ is not as convenient for partial
application, though, so example code would help.


Reply to this email directly or view it on GitHubhttps://github.com//issues/24#issuecomment-21197149
.

@HeinrichApfelmus
Copy link
Owner Author

Any luck with the event issue? If not, then let's release what we have!

@fluffynukeit
Copy link
Contributor

No such luck. I went back to focusing on FNIStash dev instead of testing TPG. Let's do the release and get it into new hands to see what holds up.

@HeinrichApfelmus
Copy link
Owner Author

Awesome, and thanks again for your help! It is alive, err, I mean, uploaded to hackage.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants