lume / element-behaviors

An entity-component system for HTML elements.
GNU Lesser General Public License v3.0
102 stars 2 forks source link

Behaviors are not recognized for existing elements #2

Open bakura10 opened 3 years ago

bakura10 commented 3 years ago

Hi,

First of all, thanks for this library. I really love the concept and it is a nice, logical extensions of custom elements.

However, I found that the behaviors do not work if the element is added before the library load. The scripts we are using are automatically added as "defer" scripts to load in a non-blocking way (we do not have control over the loading mechanism of the scripts).

Is this a known issue of the library? Unfortunately if that's the case I likely won't be able to use the library, but I hope this will give some ideas to browser implementers :).

trusktr commented 2 years ago

First of all, thanks for this library. I really love the concept and it is a nice, logical extensions of custom elements.

Hey, sorry I am replying so late! Thanks! :)

behaviors do not work if the element is added before the library load

Thanks for pointing this out!

For now, the script needs to be loaded first. (We can imagine it is like a browser API, which is supposed to exist at the beginning of the page load already).

trusktr commented 2 years ago

This needs to run before anything else, because it does some monkey patching to be compatible with ShadowDOM:

https://github.com/lume/element-behaviors/blob/main/src/index.ts#L367

This should run before application code:

<script src="https://unpkg.com/element-behaviors@2.3.2/dist/global.js"></script>

If any application code makes custom elements (and ShadowDOM) before this is loaded, it won't work in those cases because the lib won't be able to track the has="" attributes in the ShadowRoots.

However, if elementBehaviors.define is not working after elements are already in the DOM, then that's a bug.

jon49 commented 2 years ago

Yes, so even putting your library first in the head and then later adding the custom local code after the html right before </body> elementBehaviors.define will not work. Everything has to be loaded before the HTML inside the body tag.

trusktr commented 2 years ago

Hmmm. :thinking: Got a sample HTML file to reproduce it? The latest version to try with is currently <script src="https://unpkg.com/element-behaviors@2.3.4/dist/global.js"></script>

Here is a complete HTML file with the script loaded in the head, working:

<html>
    <head>
        <script src="https://unpkg.com/element-behaviors@2.3.4/dist/global.js"></script>

        <script>
            class ClickCounter {
                constructor(element) {
                    this.element = element
                    this.count = 0
                }

                connectedCallback() {
                    this.render()

                    this.element.addEventListener('click', () => {
                        this.count++
                        this.render()
                    })
                }

                render() {
                    this.element.textContent = `count: ${this.count}`
                }
            }

            elementBehaviors.define('click-counter', ClickCounter)

            class ClickLogger {
                constructor(element) {
                    this.element = element
                }

                connectedCallback() {
                    this.element.addEventListener('click', () => {
                        console.log('clicked on element: ', this.element)
                    })
                }
            }

            elementBehaviors.define('click-logger', ClickLogger)
        </script>
    </head>
    <body>
        <button has="click-counter click-logger"></button>
    </body>
</html>
trusktr commented 2 years ago

@jon49 By chance are you using type=module with the script that isn't working? Maybe that's why. The behaviors need to be registered up front, and using type=module will cause the script to be deferred until later rather than executing before your content is parsed.

Working on a fix, so that any elementBehaviors.define calls make any pre-existing elements with has attributes come alive regardless of load order.

jon49 commented 2 years ago

Yes, I guess that is what I was trying to say. Is that only when I load the behaviors before the HTML elements will the behaviors work. Otherwise they are ignored. So, good to hear you are working on the fix. Granted the way this library works depending on what you are doing it is nice to do the behaviors ahead of time :-). But I could see wanting them after the fact too if an immediate visual component is not needed.

trusktr commented 2 years ago

Yeah, it should at least be like custom elements where we can define them later, and at the later point they will run on any pre-existing elements. A use case could be, for example, loading JS after a client-side page switch, and the JS comes in after the new elements are already in view.