element-behaviors icon indicating copy to clipboard operation
element-behaviors copied to clipboard

Behaviors are not recognized for existing elements

Open bakura10 opened this issue 4 years ago • 7 comments

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 :).

bakura10 avatar Aug 23 '21 12:08 bakura10

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 avatar Feb 21 '22 18:02 trusktr

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/[email protected]/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.

trusktr avatar Feb 21 '22 18:02 trusktr

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.

jon49 avatar Jul 25 '22 21:07 jon49

Hmmm. :thinking: Got a sample HTML file to reproduce it? The latest version to try with is currently <script src="https://unpkg.com/[email protected]/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/[email protected]/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 avatar Jul 25 '22 22:07 trusktr

@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.

trusktr avatar Jul 25 '22 23:07 trusktr

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.

jon49 avatar Jul 26 '22 16:07 jon49

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.

trusktr avatar Jul 26 '22 22:07 trusktr