I have a custom HTMLElement that's working as intended, but I cannot find a way to pass arguments to the constructor; instead I'm accessing passing the values in my custom tag, but then I have to set the values in connectedCallback(), which I'm not satisfied with.
Here's a basic version of what I'm doing now:
class TrackingPreview extends HTMLElement {
constructor() {
super();
const video = document.createElement('video');
video.setAttribute('controls', '');
video.setAttribute('width', '1920');
video.setAttribute('height', '1080');
shadow.appendChild(video);
this.video = video;
}
connectedCallback() {
const videoId = this.getAttribute('video-id');
this.video.id = videoId;
}
}
I'd rather pass the videoId directly to the constructor, something like this (which is NOT working):
JS:
class TrackingPreview extends HTMLElement {
constructor(videoId) {
super();
const video = document.createElement('video');
video.setAttribute('id', videoId);
video.setAttribute('controls', '');
video.setAttribute('width', '1920');
video.setAttribute('height', '1080');
shadow.appendChild(video);
}
connectedCallback() {
}
}
JS Script on HTML Page:
$(document).ready(function(){
const tracking_preview = document.createElement('tracking-preview','{{video_id}}');
tracking_preview.videoId = '{{video_id}}';
document.body.append(tracking_preview);
});
Is there a way to pass values to a custom constructor? The docs imply it is possible but aren't very helpful for how to do it.
Remember that none of the regular HTML elements come with constructor arguments either: don't rely on them. Instead, rely on attributes, with JS property backing, and hook those up according to the custom elements spec.
So, in the same way that you'd have to the following code for an Image:
let img = new Image();
img.src = "https://example.com/someimage.jpg";
img.width = ...
...
or a script element:
let script = document.createElement("script");
script.src = ...
// or script.textContent = ...
your component should not need constructor properties. You'd use it like this:
let preview = new TrackingPreview();
preview.width = 1920;
preview.height = 1080;
or in HTML form,
<track-preview width="100" height="200" ...></track-preview>
And have your custom element react to those properties getting set:
class TrackingPreview extends HTMLElement {
static get observedAttributes() {
return [`width`, `height`];
}
constructor() {
super();
const shadow = this.attachShadow({mode: 'open'});
const video = this.video = document.createElement('video');
video.setAttribute(`controls`, `controls`);
video.setAttribute(`width`, this.getAttribute(`width`) || 1920);
video.setAttribute(`height`, this.getAttribute(`height`) || 1080);
shadow.appendChild(video);
}
get width() {
return this.video.width;
}
set width(val) {
this.setAttribute(`width`, val);
}
get height() {
return this.video.height;
}
set height(val) {
this.setAttribuite(`height`, val);
}
attributeChangedCallback(name, oldValue, newValue) {
if (name === `width`) {
this.video.width = newValue;
}
if (name === `height`) {
this.video.height = newValue;
}
// ...
}
// ...
}
The rules of a constructor for a Web Component are pretty strict. One rule is that you are not allowed to pass in any arguments into the constructor.
https://w3c.github.io/webcomponents/spec/custom/#custom-element-conformance
Instead use both a property and an attribute
class TrackingPreview extends HTMLElement {
static get observedAttributes() {
return ['videoid'];
}
constructor() {
super();
const video = document.createElement('video');
video.setAttribute('controls', '');
video.width = 192;
video.height = 108;
this.video = video;
this.attachShadow({mode: 'open'}).appendChild(video);
}
attributeChangedCallback(attrName, oldVal, newVal) {
if (oldVal !== newVal) {
// Since we only watch videoid there is no need to check attrName
this.video.setAttribute('id', newVal);
}
}
get videoId() {
return this.video.getAttribute('id');
}
set videoId(val) {
if (val == null) { // check for null and undefined
this.removeAttribute('videoid');
}
else {
this.setAttribute('videoid', val);
}
}
}
customElements.define('tracking-preview', TrackingPreview);
setTimeout(function() {
let el = document.querySelector('tracking-preview');
if (el) {
console.log(el.videoId);
el.videoId = 99;
console.log(el.videoId);
}
}, 500);
<tracking-preview videoid="10"></tracking-preview>
When you attribute videoid is set then the attributeChangedCallback function is called and you pass the value down to your video element.
In my timeout function I both read and write the videoId property which read and writes the videoid attribute.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With