templating icon indicating copy to clipboard operation
templating copied to clipboard

@children Only Working in Chrome

Open TonyLugg opened this issue 6 years ago • 27 comments

Using @children in a custom element is only working in Chrome. Adding @useShadowDOM to the custom element got it working in FireFox but it does not work in Edge. My elements have slots and two types of custom elements can be nested within each other forming a multi-level menu.

@fkleuver This is a carry over from our discussion in the vNext space.

TonyLugg avatar Dec 20 '18 23:12 TonyLugg

@TonyLugg Thanks. Can you please post your markup and the piece of your typescript code that declares and consumes the @children decorator?

fkleuver avatar Dec 20 '18 23:12 fkleuver

I did some digging and it seems the implementation differs based on whether you're having @useShadowDOM or not, but that implementation does not account for whether the browser actually supports shadowDOM or not (which Edge doesn't).

So for starters, you should probably either remove that @useShadowDOM decorator (apologies for the wrong advice there) or, if you do actually need to use shadow dom, polyfill it in Edge.

fkleuver avatar Dec 20 '18 23:12 fkleuver

@bigopon you probably want to know about this too. Previous discussion at https://github.com/aurelia/aurelia/issues/331

fkleuver avatar Dec 20 '18 23:12 fkleuver

<menu-list>
	<div>This is a menu</div>
	<menu-list-item>List item number 1</menu-list-item>
	<menu-list-item>List item number 2</menu-list-item>
	<menu-list-item>Sub menu 3
		<menu-list>
			<menu-list-item>Sub item number 1</menu-list-item>
			<menu-list-item>Sub item number 2</menu-list-item>
		</menu-list>
	</menu-list-item>
	<menu-list-item>List item number 4</menu-list-item>
</menu-list>
export class MenuListCustomElement {
	el: Element;
	@children('menu-list-item') items: any[];
	parentId: number;

	constructor(el: Element) {
		this.el = el;
		this.parentId = Math.floor((Math.random() * 1000000) + 1);
	}

	attached() {
		console.log('items', this.items);
		if (this.items) {
			for (let loItem of this.items) {
				console.log('loItem:', loItem);
				if (loItem.el && loItem.el.au && loItem.el.au.controller && loItem.el.au.controller.viewModel) {
					let vm = <MenuListItemCustomElement>loItem.el.au.controller.viewModel;
					console.log('loItem vm:', vm);
					console.log('uniqueId', vm.uniqueId);
					console.log('submenu', vm.subMenu);
				}
			}
		}
	}
}	

items is undefined when logged in Edge

TonyLugg avatar Dec 20 '18 23:12 TonyLugg

In Chrome I actually don't need to reference el.au.controller.viewModel, as the vm's are right there in the array of children.

TonyLugg avatar Dec 20 '18 23:12 TonyLugg

  1. So the issue is with items that are initially present? Not ones added later on?
  2. Also the html markup is how you are using it? 2.1 <menu-list> has a <slot>?

StrahilKazlachev avatar Dec 21 '18 00:12 StrahilKazlachev

@StrahilKazlachev Correct. All items in View HMTL. Each custom element has a slot (menu-list and menu-list-item). menu-list can create multiple menu-list-item's, menu-list-item can contain visual content and a single menu-item (a sub menu).

TonyLugg avatar Dec 21 '18 00:12 TonyLugg

Yeah if you use slots then the view compiler enables ShadowDOM mode for the custom elements. @StrahilKazlachev you are on point then about what you said on Discord.

@TonyLugg You could either try polyfilling ShadowDOM in Edge with shadydom, or you can use replaceable instead of slots to ensure ShadowDOM is not being used.

That said, this is still a valid issue as I believe we should be able to detect whether something is really shadowDOM or not, and if not, use the appropriate initialization logic.

fkleuver avatar Dec 21 '18 00:12 fkleuver

For vCurrent, you can use slots without ShadowDOM. If you do, the framework attempts to emulate. If you specify @useShdadowDOM and your browser doesn't support it, it also tries to emulate it. But, perhaps specifying @useShdadowDOM when there's no native support causes the issue with @children. I'd either polyfill ShadowDOM or remove @useShadowDOM as quick fixes. Let me know if either of those solves the problem.

EisenbergEffect avatar Dec 21 '18 02:12 EisenbergEffect

@EisenbergEffect I had originally not used @useShadowDOM and only Chrome was working for my use case. I added @useShadowDOM and FF worked but still Edge did not work.

TonyLugg avatar Dec 21 '18 02:12 TonyLugg

Can you create a simple repro based on the Aurelia CLI and attach it here? I'd like to take a look myself. This seems pretty surprising to me so either there's something not quite right in your project or we've had a major regression at some point.

EisenbergEffect avatar Dec 21 '18 02:12 EisenbergEffect

OK, I'm tied up for a few days but will get something for you soon.

TonyLugg avatar Dec 21 '18 02:12 TonyLugg

@EisenbergEffect See https://github.com/aurelia/templating/issues/655#issuecomment-449188676 The fact that he's using slots causes the view compiler to turn on shadowDOM mode for the element.

This in turn causes the children observer to go into a different traversal mode which doesn't work. I'm fairly sure it has been like this for a long long time. I also recall issues with slots + @children in the past, that's in fact the reason why I stopped even trying to use them. I never laid this link before however.

fkleuver avatar Dec 21 '18 03:12 fkleuver

Using slots in vCurrent should never force shadow dom to turn on. That's the whole reason for having slot emulation to begin with. If that is what is happening, then this is some sort of regression. It's definitely not always been that way.

EisenbergEffect avatar Dec 21 '18 04:12 EisenbergEffect

I just created a little sample in Aurelia Typescript Sandbox and it works there in all browsers. My real test app is using CLI Webpack config. Also, the custom elements are global resources.

I'll create a new CLI project and see what happens.

TonyLugg avatar Dec 21 '18 15:12 TonyLugg

I created a new CLI app with Webpack and TypeScript and used the same files as the sandbox linked above. Now I get the original results, i.e. it works in Chrome but not in FF or Edge.

TonyLugg avatar Dec 21 '18 16:12 TonyLugg

Do you mind zipping that up and attaching it here?

EisenbergEffect avatar Dec 21 '18 21:12 EisenbergEffect

menu-test4Rob.zip

Here is the test project, sans node modules.

TonyLugg avatar Dec 21 '18 22:12 TonyLugg

Thanks! I'm on vacation and traveling a bit but I'll try to take a look over the next couple of days. I may have some more specific questions after I dig in more.

EisenbergEffect avatar Dec 21 '18 22:12 EisenbergEffect

OK, first, have your well deserved vacation, this can wait until you return. It is pretty simple. CLI new app, add the app.html. Add the two custom elements. Check the console in each browser. That's it for this crude sample.

TonyLugg avatar Dec 21 '18 22:12 TonyLugg

https://gist.run/?id=040775f06aba5e955afd362ee60863aa

davismj avatar Dec 22 '18 03:12 davismj

Firefox

image

Chrome

image

Edge

image

@TonyLugg The outputs in the console look identical to me. Can you help me understand what is different in Edge or Firefox in your local build, please?

davismj avatar Dec 22 '18 04:12 davismj

@davismj Correct. Per the sandbox sample I provided here it is working there too. However, it does not work with the CLI Webpack config that I attached for Rob above.

In FF and Edge, items is undefined in the log.

TonyLugg avatar Dec 22 '18 17:12 TonyLugg

I finished the multi-level menu using querySelectorAll and referencing el.au.controller.viewModel instead of @children. I did not need to build a tree as each menu-list and menu-list-item referenced their children and parent.

TonyLugg avatar Dec 31 '18 13:12 TonyLugg

@TonyLugg Did you get your issue resolved?

bigopon avatar Feb 04 '19 10:02 bigopon

@bigopon The issue with @child and @children was not resolved to my knowledge. I worked around it as mentioned above based on comments provided by @davismj.

TonyLugg avatar Feb 04 '19 13:02 TonyLugg

@TonyLugg I'm finally able to have a look at the cause of this. In short, this is a timing issue. It can be explained like this:

  • With native shadow on: every element is kept as is, what you see is what you get initially. And with this, the child observer is able to initialize the value of @child/@children during bind lifecycle, hence you get what you want in attached.
  • With native shadow off: content elements of a custom element will be removed during compilation, and added later during bind phase. This is to do the projection for <slot/> emulation, and it will trigger mutation observer and thus give you the value you want. The downside of this, is every mutation callback will be a tick later. And in your example, it's 3 layers of tick, while attached is only 1 tick after bind, hence undefined in attached as you saw. This, unfortunately is the only reliable way to have slot emulation work, so change handler should be encouraged in emulation scenario.

bigopon avatar May 17 '20 05:05 bigopon