-
-
Notifications
You must be signed in to change notification settings - Fork 408
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
Allow modifiers to interact with the element before it is insert into the DOM #652
Comments
Thanks for writing this up @jelhan! A few of us (@wycats, @krisselden, @chancancode, @pzuraq, @dgeb, and myself) discussed this (along with other GlimmerVM / Rendering issues) last night, and we are all generally in favor of solving this problem. I don't think we have a "for sure 100%" solution yet, but it likely involves both migrating to "attributes first" and providing more fine grained hooks in the modifier APIs. |
I'm closing this due to inactivity. This doesn't mean that the idea presented here is invalid, but that, unfortunately, nobody has taken the effort to spearhead it and bring it to completion. Please feel free to advocate for it if you believe that this is still worth pursuing. Thanks! |
It's not inactive. There has been recent discussion between different framework team members on Discord: https://discord.com/channels/480462759797063690/500803406676492298/966402401693229116 Sadly I haven't had time to add a summary (or at least that link) to this issue. In that discussion we identified 4 different requirements for timing of modifiers:
The first use case is supported by RFC #373 today. The other three are not supported yet. @wycats referenced the first option as idle and the second one as before paint in that Discord thread. I would add before insert for third option and in order for fourth option. I think that terms may help for further discussion. @chancancode pointed out that third and fourth options could be a "performance hazard" and should be "avoided as much as possible" as both would need "to be synchronous". @wycats also raised way less concerns with option 2 than with option 3 and 4:
To move forward, I would recommend to focus on supporting option 2 (before paint) as next step. It seems to be less complex than option 3 and 4. And unblock relevant use cases like ember-style-modifier. Before committing to support option 3 (before insert) and 4 (in order) we may want to investigate if the target use cases could be achieved in another way:
|
I’m reopening for further discussion. Thanks for the update @jelhan! |
@jelhan I've made decent progress on the Starbeam work I was doing that helped me clarify my own thoughts on this originally. I think where I'm at right now is that we (definitely) want two different possible timings for modifiers:
I agree that using modifiers as a registration pattern to gain access to DOM elements is a problem. It is especially problematic because that one use-case is largely responsible for the situations in which the timing between modifiers is important. This is one of the primary use-cases that then leads to the assumption that we need an earlier bucket. In my opinion, we want syntax that provides access to DOM elements. This is the straw-man I've been kicking around for a few years: <video #player src={{@url}}></video>
<button {{on "click" (fn this.toggle #player)}}>⏯️</button> The basic idea is that you could attach a tag to any HTML element, and that name (in this case You could then turn the play/pause button into a component pretty easily. In this design, I think you'd make it an error to attempt to access the The key consequence is that by making element tagging declarative, Ember becomes responsible for ensuring that the variable is initialized before any modifier behavior, just like other syntactic variables. In my opinion, treating this use-case like variable initialization sidesteps z-index-style problems that are created when we attempt to treat it as an imperative lifecycle phase. @jelhan What are your thoughts? Assuming we actually do implement a solution along these lines for getting a reference to an element, what are use-cases are still left unsolved? |
It's important to consider how to access the tags attached to the HTML elements from outside the component. It sounds a lot like forwarded reference in React: we pass tags when invoking components, and the component responsible for delivering these tags to the target elements. It's handy when developing complex structured UI components where we often need to hold multiple tags in the top-level root component. <video #player={{@playerTag}} /> <!-- assign to passing arguments -->
<video #player={{this.setPlayerTag}} /> <!-- accessible by passing functions --> I also like the idea in the https://github.com/lifeart/ember-ref-bucket, in which we can create locally or globally tags and use them in whatever ways we use other values. |
@wycats Thanks a lot for sharing that detailed thoughts. Looks good to me. But haven't thought about the details yet. Especially how such a reference could be shared with templates outside current scope. Sometimes the element one what to target is created and consumed in different components. Modals and other overlays are typical example for this.
Using custom elements is not addressed yet. For these is might be important if something is set as attribute or property. Glimmer VM does not allow yet to reliable set attributes or properties. It depends on implementation details of the custom element. At the same time it might be important for custom elements to set attributes and properties before inserting it into DOM. Again this depends on implementation details of the custom element. Custom elements have a lifecycle hook called But as said above, I think this would be better solved by improving template syntax to reliable set attributes and properties. In the meantime the element can be created manually (e.g. using a template helper), setting attributes and properties on it, and using that one in the template. It's not blocking custom element usage. It "only" affects developer ergonomics. I would recommend that going forward we move discussion about 1) element reference and 2) reliable setting attributes and properties out to separate issues. Both seem to be complex enough to deserve their own issues. I think main questions regarding before paint timing have been resolved. Next step might be drafting a RFC for that one. Or did I missed something important? |
There seems to be similar limitations die to timing of Not sure if both should be addressed in the same RFC. But it might be good to at least consider it. |
In general, I think we should introduce a narrow kind of (declarative) modifier that can only set attributes or properties on the element. A rough sketch:
Among other things, this would make it possible to implement a reliable Any thoughts? |
I had a pairing session with @wycats on modifier capabilities and timing this week. We discussed the following steps forward:
We haven't had the time to discuss the use case of getting a reference to an element. Please have a look at @wycats post from July 28 for that topic. |
I published a RFC addressing some of the limitations of modifiers in Ember: #883 I'm working on another RFC server-side rendering capability to modifiers. I expect that it won't be the last one. 🙃 |
Modifier Manager should provide a hook, which has access to the DOM element but is executed before the element is insert into the DOM.
The
installModifier
hook is executed after the element is insert into the DOM:The
createModifier
hook does not have access to the DOM element. Accordingly to Modifier Manager RFC "called as discovered during DOM construction". Not sure if the element is created yet and just not passed in or does not even exist yet.I have two use cases for a hook, which has access to the DOM element but is executed before it's insert into the DOM:
set-attribute
andset-property
modifier, which sets values as attributes or properties on the element before it's insert into the DOM.ember-style-modifier
, which manipulates the CSS styles of an element using CSSOM.For both of them running before the element is insert into the DOM is important from a performance perspective. If executed after the element has been insert into the DOM the browser may do an unnecessary layout calculation in between. E.g. if
Element.getBoundingClientRect()
or a similar function is called in between.It's even worse as there isn't any garantuee if the modifier is executed in the same tick as the DOM insertion:
If it's executed in the next queue the Browser paint the state before the modifiers has been run. This may be visible to the end-user as an inconsistent and flickering UI.
Especially for
set-attribute
andset-property
the timing is also important if considering custom elements. Custom elements are part of the web components feature set. Similar to@ember/component
they have lifecycle hooks. One of them is theconnectedCallback
. It's executed when the element is insert into the DOM.As modifiers are called only after the DOM element has been insert into the DOM manipulation attributes or properties set on the element using them are not available yet in
connectedCallback
. I have created a JSFiddle to illustrate the timing relevance: https://jsfiddle.net/jv0Lk3sb/ It's creating a custom element, which implements theconnectedCallback
. It sets two attributes on the custom element. One before and one after it has been insert into the DOM. Only the first one is available inconnectedCallback
.The limitation for custom elements is especially relevant as one can not rely on Glimmer VM always setting values passed as attributes as attributes. Depending on the custom element it may be set as an attribute or a property. It may even change between first and subsequent runs. See this issue for more details. Modifier would be a good solution to get full control if the values are set as an attribute or a property on the element. But due to their timing issue with
connectedCallback
this is not possible in many cases.While going this route we may want to consider allowing to run code with a modifier on destruction before the element is removed from DOM.
Thanks a lot to @rwjblue for the input on Discord, which helped creating this issue. I hope I got everything right.
The text was updated successfully, but these errors were encountered: