WebReflection / uhtml

A micro HTML/SVG render

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Unable to set attributes in customElements using variables

web3d0 opened this issue · comments

The following is an example of something that works, where eh-cardbutton is a customElement:

	render(this, html`
				<eh-cardbutton status="Booked" location="Blibbity Blob">
				22nd December 2022 | 0800 - 1400
				</eh-cardbutton>
		`);

However this doesn't work:

	const fred = "Blibbity Blob";
	render(this, html`
				<eh-cardbutton status="Booked" location=${fred}>
				22nd December 2022 | 0800 - 1400
				</eh-cardbutton>
		`);

Looking at the HTML in the browser, it does appear to be generating the correct HTML however it has added comments which may be confusing the browser?

no comments in attributes, not sure what you are talking about ... but everything I do on the front end rotates around Custom Elements and is tested, so I don't know what is the issue here but for sure, if you want magic (i.e. using accessors or having mutable attributes) you need to pass interpolations there ... anyway, uhtml works with custom elements out of the box, so I am pretty confident I can close this, but feel fre to provide a full example I can test in case you need my help.

P.S. if that location is an accessor, the syntax to trigger that is .location=${fred} not just location=${fred} because the latter sets the attribute, the former use ce.location = value

I think it's because I'm trying to nest custom elements, eg. "eh-table" containing a list of "eh-card"s. If I put the HTML for the cards into eh-table then that works. I will work on it more and write up a short example if I can't get it going.

minimal code that shows any error would help ... at the parsing level if you use html as prefix everything is defined by DOM standard ... at the library level, everything is either an attribute or an accessor ... there's no mistake there, I assure you, but I'd be happy to be proven wrong, after so many years of using uhtml for everything custom elements related.

OK, here is a minimal example. Following is main HTML:

<eh-next status="Booked" location="Blibbity Blob">22nd December 2022 | 0800 - 1400 testing eh-next</eh-next> <br> <eh-test></eh-test>

And the class definitions:

class EHNext extends HTMLElement {
_text;
_status;
_location;
get text() { return this._text; }
set text(v) { this._text = v; this.render(); }
constructor() {
super();
this._text = this.innerText;
this._status = this.getAttribute("status");
this._location = this.getAttribute("location");
this.render();
}
render() {
render(this, htmlStatus:${this._status},<br>Location:${this._location},<br>Text:${this.text});
}
}
customElements.define('eh-next', EHNext);

class EHTest extends HTMLElement {
constructor() {
super();
this.render();
}
render() {
const fred = "Blibbity Blob"; // <=- this is the problem
render(this, html<eh-next status="Booked" location=${fred}>22nd December 2022 | 0800 - 1400 testing eh-test with variable</eh-next>);
}
}
customElements.define('eh-test', EHTest);

And the output:

`
Status:Booked,
Location:Blibbity Blob,
Text:22nd December 2022 | 0800 - 1400 testing eh-next

Status:Booked,
Location:,
Text:22nd December 2022 | 0800 - 1400 testing eh-test with variable
`

The problem is Location is blank in the second set of lines, as it seems unable to extrapolate variables in nested custom classes. There has to be a better way to paste in code with backticks...

There has to be a better way to paste in code with backticks...

moar backticks ...

this`is fine`

but I don't see any error https://codepen.io/WebReflection/pen/dyqPrwR?editors=0010

Look at the second Location:... where is Blibbity Blob?

where it's supposed to be?

Screenshot from 2023-02-16 15-02-22

Same as the block above, both of them should read "Location: Blibbity Blob". You can see the problem in the Elements pane:

First one: <eh-next status="Booked" location="Blibbity Blob">Status:Booked<!--isµ0-->,<br>Location:Blibbity Blob<!--isµ1-->,<br>Text:22nd December 2022 | 0800 - 1400 testing eh-next<!--isµ2--></eh-next>

Second one: <eh-next status="Booked" location="Blibbity Blob">Status:Booked<!--isµ0-->,<br>Location:<!--isµ1-->,<br>Text:22nd December 2022 | 0800 - 1400 testing eh-test with variable<!--isµ2--></eh-next>

So it set the attribute, but it didn't set the actual HTML. If I just type in plain text, then it works. If I use ${} variable, then it doesn't.

in that case, you are declaring a dynamic field in your template, so you should define it at the component level as done in here: https://codepen.io/WebReflection/pen/KKxwYMK?editors=0010

the main take is:

static observedAttributes = ['location'];

to define dynamic fields in the layout

and

attributeChangedCallback(name, oldValue, newValue) {
  this['_' + name] = newValue;
  this.render();
}

to update internal fields you want to render with updates ... "holes" in uhtml and all others are dynamic, meaning these can react to changes in the top/parent render, when attributes or content is different ... the sooner you prepare your components to be reactive, the more you'll enjoy template literal based solutions such as uhtml

edit that simply means: never use a render only in the constructor, or you won't ever be able to benefit reactivity

Technically speaking, the template is rendered with instructions of what needs to be updated, but once observed, the attribute change callback inevitably triggers after the constructor, but the render there won't change layout or anything, it will simply update the attribute value as atomic operation: perf win, your component is now reactive => everyone wins 🥳

OK, this is something I didn't know about. Thank you for your time and help, and I will go away and study this now. :)

I have updated my previous comment with more details, but the thing is that what's being cached as template is the one without any dynamic field value, which then gets updated right away on render ... if you expect that field on the first render, it means every other class that inherits from the definition would find foreign (first come/first serve) fields values that are unintended ... I hope this makes sense, but the long story short is that this is how you should write components, if any of their fields can be arbitrary. If you use accessors, like you do with text, the correct layout within html templates is via .text=${value} <== see the . dot prefix, to be explicit on the (accessor) intent 👋

Looking through this, it seems that if there isn't any existing "isµ" prefix, then it is being initialised and shouldn't use the cache. That would fix up the first time behaviour, and then the cache can be applied for subsequent usage? I understand that template strings are unchanging within the code itself so it would be the isµ comments which are needed for a distinction.

I'm wondering if this is the same problem React has, which is why they're now moving towards mounting everything, then un-mounting, then re-mounting so now people are having problems with constructors triggering twice?