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
Thoughts on improvements #35
Comments
Hi @ondrap; these are really great suggestions.
Thanks for taking the time to try reflex out and provide such detailed feedback. This sort of thing is very helpful for figuring out the future direction of the libraries and making sure that it does what people need. |
Thanks for the response, these are great news. Regarding the 'hidden' class - it seems to behave the same way in both Chrome and Firefox. I think I really do have the Dynamics set to hidden initially. I have one big page (in production I'll probably trim it) with lots of records (using Could it be that my 'initial dynamic' is actually derived over dbRuleWidget = do
rulesLoaded <- runApiCheck apiDbRuleList postBuild -- Loads data from databse
rules <- holdDyn [] rulesLoaded
simpleList rules $ \rule -> do
dbRulePanel rule
dbRulePanel entrule =
elClass "div" "panel panel-primary" $ do
editbtn <- glyphButton "edit" "Edit"
editDyn <- holdDyn False (const True <$> editbtn)
showIf (== True) editable $ do
buttonClass "btn btn-primary" $ text "Save"
buttonClass "btn btn-default" $ text "Cancel"
showIf :: MonadWidget t m => (a -> Bool) -> Dynamic t a -> m b -> m b
showIf f dynvar code = do
let mkAttr True = ("class" =: "show")
mkAttr False = ("class" =: "hidden")
attr <- forDyn dynvar $ mkAttr . f
elDynAttr "div" attr code
|
Sorry to bother you with other thoughts... but I'm just in the process of creating some in-place editable form and it occurred to me that the requests for templating do make a lot of sense - but from a completely different view. We like to create components that have perfectly defined input and output. However, within the component, we do not generally want this separation. In particular, what I'm doing right now is: namedyn <- elClass "div" "panel-head" $ someInPlaceEditable ...
(priodyn, enableddyn, packagedyn) <- elClass "div" "panel"body" $
(priodyn, enabledyn) <- elClass "..."
packagedyn <- elClass "..."
return (priodyn, enableddyn, packagedyn) That really seems to me quite hairy. The following is just a sketch of an idea - it is possible to rip an element out of DOM and put it somewhere else (or better yet, just create it and insert later). So, today, one could probably write something like: (nameddynE, nameddyn) <- input' ...
(priodynE, priodyn) <- input.a.
...
elClass "div" "panel-head" $ implant nameddynE
elClass "div" "panel-body" $ implant priodynE
...
implant n = do
parent <- askParent
performEvent_ $ const (appendChild parent (Just _el_element n)) <$> getPostBuild It wouldn't work if it was implanted more than once, but it seems to me this would lead to very easy templating and would greatly reduce the 'pushing-up' of the events/variables. |
@ondrap FYI, we have an editInPlace widget in reflex-dom-contrib here. Your hide point is interesting. I think the reason it's not as composable as you would like is multi-faceted. First, the point Ryan mentioned of making mapDyn pure would certainly help. But on top of that, there are quite a few different ways to do it. Here are a few of them off the top of my head:
So I think we're suffering from the paradox of choice a bit here. It's not that it can't be done, but rather that nobody has decided to be opinionated enough to choose one option. |
I tried to refactor my 'slightly complex' page using: implant :: MonadWidget t m => El t -> m ()
implant n = do
parent <- askParent
void $ liftIO $ appendChild parent (Just $ _el_element n)
rip :: MonadWidget t m => m a -> m (El t, a)
rip = el' "span" (the (cancelEditN, cancelEdit) <- buttonClass' "btn btn-default" $ text "Cancel"
-- +logic around other buttons etc.
-- ...
divClass "panel-body" $ elClass "div" "pull-right" $ implant cancelEditN I just like it much better this way. It would be even better, if I could abstract the 'Event' from the button in the first part and 'attach' it to the button later with |
The flickering problem: the problem is that elInitDynAttr' :: MonadWidget t m => String -> Map.Map String String -> Dynamic t (Map.Map String String) -> m a -> m (El t, a)
elInitDynAttr' n attr dynattr code = do
(e, res) <- elAttr' n attr code
addAttributes dynattr (_el_element e)
return (e, res) |
Yes, I think you're right about the attributes. I'll see what I can do about this. I definitely see what you're saying about how clean the code looks. The issue of decoupling the logic structure from the layout structure is one I've thought about a lot, and I would love to have some more tools for doing that. However, for me, the cost of using something like implant would be too high. In the limited use case you mentioned, in a single function scope and with a single developer writing code over a limited span of time, I think it would probably work out alright. However, with more developers working on the code, losing the once-and-only-once placement guarantee would become a much more serious problem. For example, as a widget becomes more complex, with helper functions, etc., it will become more and more difficult for a developer to determine whether and where an element declared in the logic is actually placed into the DOM—and therefore easier and easier to delete the use of an element or create an extra use of it. Basically, this means that developers need to always diligently track things down when changing the code. Nobody can be diligent 100% of the time, when deadlines, distractions, and life get in the way. Furthermore, it's not just that this requires developers to diligently hunt things down whenever they make a change—it opens the door to situations where it's not even possible to statically determine whether an element is placed. For example: w = do
(theElement, _) <- buildTheElement'sLogic
x <- getSomeInput
divClass "a" $ do
when (x < 5) $ implant theElement
divClass "b" $ do
when (x > 5) $ implant theElement Whoops! Now the widget works for x /= 5, but fails—during testing, if you're lucky—when x == 5. Of course, in a real application, the conditions are likely to be far, far more complex, and the failure modes far more subtle. Imagine, for example, a situation where elements are placed into a datastructure of some kind, passed around the application (or even a complex widget), then placed later. This is something I would not be surprised to see in the implementation, for example, of a sortable table widget. I would be surprised, however, if it were bug-free. There is a "Right Way" to do this: linear types. With linear types, we could specify, at the type level, that each element must be placed exactly once. However, linear types aren't available in Haskell (yet?), so we have a difficult trade-off to manage. One way we might be able to have a solution that preserves the placed-once constraint while also allowing more elegant DOM layout would be to use Template Haskell to turn a layout into an expression that draws it and a pattern that binds all of the elements inside. Ideally, I'd like the result to look something like this: myWidget = do
rec [layout|
some layout that places a button and names it x
and displays the dynamic value y
|]
y <- count $ domEvent Click x Unfortunately, I'm not sure how this could be achieved with the current Template Haskell, because it doesn't have a way of splicing in a bind statement (e.g. do { x <- asdf }). If we could splice in a bind, the above would desugar to something like: myWidget = do
rec x <- do
var1 <- button "Click me"
display y
return var1
y <- count $ domEvent Click x I'm not completely sure what it will take to make this happen, but I'd love to hear your (and others') ideas. |
What about leveraging HList? Something along the lines: {-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE ViewPatterns #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE KindSignatures #-}
import Data.HList
let okbutton = Label :: Label "okbutton"
nameinp = Label :: Label "nameinp"
[pun | okbutton nameinp|] <- runWriterT $ do
el "div" $ el "something" $ tell (okbutton .=. button "ok")
el "div" $ el "something" $ tell (nameinp .=. input def) |
That could be an interesting approach. How would over- and under-placement be dealt with? |
I just thought about it a littlemore and the runWriterT won't work - the type of the Monoid is changing. However using the HList may still help, I'll try to explore this a little. |
I think he means ensuring that each element is placed into the DOM exactly once. |
Hi guys Just to weigh in with my two cents, I am slightly surprised no one has mentioned widgetHold - it is designed Regarding flickering with attributes, I think ultimately the solution has Oliver On Mon, Nov 23, 2015 at 12:14 PM, Ali Abrar notifications@github.com
|
regarding the The reason I did that was because of modals. I have a single modal div at the toplevel of my DOM, and I use that function to overwrite the modal content before displaying the modal. Before I had that, I had two options: modal div at the toplevel, complex |
After some playing with forms, I tried a different approach that seems to work nicely for forms. It is slightly incompatibile (when some non-trivial default is used) with 'dyn/widgetHold', I will probably do some pull request when I get time. I have added instance of
this results in |
Hi @ryantrinkle it's already more than a half a year since you were talking about pure |
@qrilka Sure! It's in testing. Check out the functor-dynamic branch of reflex, and the refactor branch of reflex-dom. Once I'm confident that everything is good to go, both will be merged to develop and released to hackage. |
@ryantrinkle I was looking at this last night. Could you put together a branch on reflex-platform that integrates these? I tried, but it wasn't obvious to me everything that I needed to do. |
Yes, it's the new-reflex-dom branch.
|
Perfect! |
@ondrap I think we've gotten everything mentioned here integrated into develop at this point; if there's anything you're still looking for, please let me know! Thanks for the in-depth analysis. |
I started developing an application with reflex-dom about a week ago, so maybe some of my thoughts might be wrong. My experience is so far quite good, but here are some thoughts...
servant
with structures frompersistent
, which compiles (at least for the datatypes) directly into ghcjs. It works wonderfully (I absolutely recommend it), however my server structures are inData.Text
. Ghcjs seems to work correctly withData.Text
(probably bettern than withString
). Are there any thoughts about converting the whole reflex-dom toText
? It seems to me there actually is no reason to useString
at all, especially with theOverloadedStrings
extension. The thing is that just changing types in the reflex-dom files fromString
toData.Text.Text
probably does the trick.Dynamic
variables are applied (e.g. for the class=hidden attribute). Is there a simple way to evade the problem?div
to be shown or hidden on some slightly more complex condition and I just have to wire the code withmapDyn
s, 'combineDyn's,<=<
(I finally found use for this operator) - while I just wanted to sayhide if a ==0 && b && !c
.E.g. I had to change the
button
tobuttonClass
, but had to change the signature to accomodate the simple fact, that I needed to put a glyphicon into the label text.buttonClass :: MonadWidget t m => String -> m () -> m (Event t ())
I am developing with the CSS bootstrap framework that relies quite a lot on
class
attribute. What might cleanup the code may be making aMonoid
(e.g.newtype ElClass
) for the class parameter.Anyway, so for it's not a bad experience, it just works :)
The text was updated successfully, but these errors were encountered: