div
[]
[ button
[ onClick MyClickMsg
, focused
]
, ...
]
In terms of API it is referentially transparent. Now implementing such function is tricky though only options I can see are:
1. Using weak maps so `ref x` would either read value from map:
function ref(x) { return wm.has(x) ? wm.get(x) : wm.set(x, guid()) }
It is impure but referrentially transparent. That being said weak maps is probably no go.
2. Built object `guid` support it into Elm itself. In nutshell it's a same as with weak maps but using internal field instead:
guid : a -> string
function guid (x) {
if (x.__elm_guid == null) {
x.__elm_guid = ++nextGUID
}
return x.__elm_guid
}
Again impure but referrentially transparent, as long as nothing else can read __elm_guid field. But then I am not sure if this approach is acceptable.
3. Use some sort of object hashing algorithm. But that seems too expensive to be practical.
To be clear I'm not married about the proposed API. I just think there needs to be a way to referernce a DOM element that corresponds to specific `Html msg` element that wont require manual approach like css selectors.
Maybe Html.Ref similar to Html.Keyed is a better direction, but I have not managed to come up with a better API myself
I've raised some questions here https://gist.github.com/rtfeldman/5f015adbdfbba541c7e7e1409b6efeef#gistcomment-1829693.
To continue the discussion, I think there are some (hopefully not completely unsolvable) problems with generating Cmds based on node refs in Elm:
- you can only return a Cmd from an update function, but the update function (rightly so) has no knowledge of the view(s).
- you are unable to respond to Msgs in view functions, and you are unable to return Cmds from a view function. The problem is that these imperative commands are a view related concern, not a model/update related one.
- "firing a command" by way of an element attribute raises a lot of questions (as raised in the gist response)
- You might be able to generate a ref based on a model, but models are independent of views. What if a model is represented in multiple views (eg: my name+photo block might appear in the top right menu bar as well as next to a comment). The basic Elm Architecture is Model-Update-View, but it doesn't dictate that you can't have multiple views of a model. How would you know which view should be focused?
Also, should this conversation be moved to elm-discuss?
That is a valid concern. I wish I had a good answer for it but all I have is this:Say you have a `model` that is viewed as a `tab` and a `content` for it. Then using `ref model` on both views will be problematic due to conflict. What you could do instead (which is what I've being doing) is allocate a "handle" which is part of you `model`, you can think of it as an ID. In other words in such scenario you can use `ref model.tab` and `ref model.content` to refer to views instead of just using `ref model`. Arguably you'd want to have dedicated field on your model to be used with `ref` anyhow as `model` is likely will be changing and there for `ref model` will return different value even though identity will be the same. But if you allocate a field on the model only on `init` and never update it, you got yourself an identity which can be used in conjunction with a `ref`.
Also, should this conversation be moved to elm-discuss?The reason I started it here was because if this were to be implemented it would require some changes to Elm itself, but I'll leave this call up to Richard.
Yeah, the thing is once you're generating globally unique identifiers, you're one id attribute away from querySelector being a more flexible version of the same thing.
(If you want nicer type-checking around that, you can always write a wrapper around querySelector and/or id that only accepts your GUIDs so you'll get compiler errors if you accidentally try to use something invalid.)
That being said it could be that introducing identity to Elm is not desired. For example should `Ref.guid { x: 1 }` in module `Foo` and `Ref.guid { x: 1 }` in module `Bar` return same value ?
Alternative would be to let `Focus` module perform a task on `init` to generate a unique `id` for it. The problem with that approach is though what if happens with focus / blur commands in the meantime ? Do you queue them up and run once you have an ID ? That seems like an overkill, which is why I prefer `Ref` based solution, but it also could be that it's just fine.
That being said it could be that introducing identity to Elm is not desired. For example should `Ref.guid { x: 1 }` in module `Foo` and `Ref.guid { x: 1 }` in module `Bar` return same value ?I think it is undesirable, yeah.
Alternative would be to let `Focus` module perform a task on `init` to generate a unique `id` for it. The problem with that approach is though what if happens with focus / blur commands in the meantime ? Do you queue them up and run once you have an ID ? That seems like an overkill, which is why I prefer `Ref` based solution, but it also could be that it's just fine.Or to have it accept a hashing function allowing it to generate one from a `model`, yeah?
That's what I mean about it being decoupled - you can implement https://gist.github.com/Gozala/7f1e3f63f5711756a4d159c13b96cffd using `querySelector` (and maybe if you don't want to use `id`, a custom `attribute` like `data-ref-value` along with a `querySelector` that looks for that attribute) if that's the API you want to interact with.
I have being thinking more about the problem of CSS selectors as poor way to reference Elements in the DOM and Declarative VS Task based approach for updating certain DOM properties. And I had a thought that there could be a way to solve both problems in a neat way. What if something along this lines was added to elm-html:
taskAttribute : (DOMElement -> Task Never msg) -> Html.Attribute msg focus : DOMElement -> Task SelectorError msg blur : DOMElement -> Task SelectorError msg scrollLeft : Float -> DOMElement -> Task SelectorError Float
That way those task could be turned into attributes and elm-html can take care of running them with an appropriate DOM elements. Better yet it could actually recognize such tasks and run them in the same tick to avoid races we've talked about.
It not fully fleshed out idea, but I though I'd share it anyway to get other minds think about it.
To be totally clear I don not suggest that Elm should expose raw DOMElement
to Elm programs. It most definitely should not, but there could be a type like DOMElement
(or Ref
or whatever you want to call it) that is never given to an Elm program but can be used to connect different native APIs in this case connecting elm-html diff / patch process with with tasks like focus
/ blur
that also likely will be implemented in native.
So https://github.com/evancz/elm-todomvc currently sets focus through a port using a Cmd/querySelector interface.How would that code look if it used this API?
taskAttribute : (DOMElement -> Task Never msg) -> Html.Attribute msg
--
You received this message because you are subscribed to the Google Groups "elm-dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elm-dev+u...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/elm-dev/a68f4809-418b-4a3d-95b7-a94dba1b91b2%40googlegroups.com.
If and only if the purpose is to set/change focus, should we care much about high performance over, say, developer happiness?
|
To view this discussion on the web visit https://groups.google.com/d/msgid/elm-dev/CAF7GuPGYJQWbz-WXMSC_3PF-Fy86Lzw_poBhKjtofVq9jjc3yw%40mail.gmail.com.
For more options, visit https://groups.google.com/d/optout.
The following API has two problems:taskAttribute : (DOMElement -> Task Never msg) -> Html.Attribute msgFirst, this means you can only set focus in response to user events. Never based on servers or timers or anything else.
Second, that is not actually true because this allows you to get DOMElement values anywhere in a program. You can say (taskAttribute Task.succeed) and suddenly your message is a DOMElement. You can hold on to it for a million years. Do whatever you want with it. All while virtual DOM stuff is changing. Maybe that node does not even exist anymore.
var neverExposedInternalUnboxKey = {}
function box(thing) {
return function unbox(key) {
if (key === neverExposedInternalUnboxKey) {
return thing
}
}
}
-------------------Here's how I look at this whole thing. The DOM is not pure. Just like a server is not pure. We send it data. Complex data sometimes, but that does not guard us from the fact that it is managing state of its own. And because there are two versions of the same state, it can get out of sync. That's just a fact about duplicating state.In the case of servers, we acknowledge that it's not pure and send tasks. We don't want to refer to values on the server directly because those values may change in silly ways. We want their bad ideas to stay over there.
I personally do not like the query selector idea because you can't cache it easily. Getting things by ID, I think I could cache that in the virtual DOM implementation. I don't know how fast or slow getElementById or getQuerySelector are, but I think it's worth considering that getting the answer to a query a ton of times definitely costs more than getting IDs. I'd be curious to get more info on this aspect.