Javascript Class Inheritance not working as it should - javascript

Trying to create some Javascript classes and parent classes, and not sure if I'm doing this correctly, but super() in the child class isn't working as it should. Trying to get content in DivElement to work, but it keeps returning undefined.
Code:
class HTMLElement{
constructor(tag, content){
this.tag = tag;
this.content = content;
}
render(){
return `<${this.tag}>${this.content}</${this.tag}>`;
}
class DivElement extends HTMLElement{
constructor(content){
super(content);
this.tag = 'div';
}
}
let div1 = new DivElement('test');
console.log(div1);
console.log(div1.render());

The super call should match the signature of the target method. It should read super('div', content);:
class HTMLElement{
constructor(tag, content){
this.tag = tag;
this.content = content;
}
render(){
return `<${this.tag}>${this.content}</${this.tag}>`;
}
}
class DivElement extends HTMLElement{
constructor(content){
super('div', content); // changed here
this.tag = 'div';
}
}
let div1 = new DivElement('test');
console.log(div1);
console.log(div1.render());
// <div>test</div>

The constructor of the HTMLElement class is called with two parameters (tag & content). The extended class calls the constructor with only one parameter and assigns content to the tag parameter of the parent class. Note that JS does not allow constructor overloading.
See the answer of Glycerine.

Related

WebComponent Layout is in reverse order

Can anyone help me to understand why this layout is being rendered?
customElements.define(
"my-resume",
class myresume extends HTMLElement {
constructor() {
super();
let h1 = document.createElement("h1");
h1.textContent="My Resume Name";
this.appendChild(h1);
}
}
);
customElements.define(
"job-experience",
class jobexperience extends HTMLElement {
constructor() {
super();
let h1 = document.createElement("h1");
h1.textContent="Job Experience";
this.appendChild(h1);
}
}
);
customElements.define(
"company-name",
class extends HTMLElement {
constructor() {
super();
let div = document.createElement("div");
div.textContent = this.getAttribute("name");
this.appendChild(div);
}
}
);
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>My Resume Template</title>
<script src="myresume.js" defer></script>
<link rel="stylesheet" href="myresume.css" />
</head>
<body>
<my-resume>
<job-experience>
<company-name name="The Last Company Worked"></company-name>
</job-experience>
</my-resume>
</body>
</html>
The element view:
I can understand why the entire layout is reverse that of the HTML mark up. If for example I move the H1 elements up to the top of the parent container things are better. But I don't understand why each new custom element acts like a stack (each new element is pushed on the top) the last element added is always on rendered above other elements.
Custom Element creation logic determines the order of creation. It starts at each parent element and works its way down. But append child reverses the order because the browser will always render the last child appended first.
As mentioned by Patric Evans, your layout is rendering that way because you are using [appendChild] which inserts your node after the last child. If you use [prepend] your layout will render the way you expect.
As mentioned by Danny Engleman, don't do DOM manipulation in the constructor.
I believe this is more like what you are wanting..
customElements.define(
"my-resume",
class myresume extends HTMLElement {
constructor() {
super();
}
connectedCallback() {
let h1 = document.createElement("h1");
h1.textContent="My Resume Name";
this.prepend(h1);
// this.appendChild(h1);
}
}
);
customElements.define(
"job-experience",
class jobexperience extends HTMLElement {
constructor() {
super();
}
connectedCallback() {
let h1 = document.createElement("h1");
h1.textContent="Job Experience";
// this.appendChild(h1);
this.prepend(h1);
}
}
);
customElements.define(
"company-name",
class extends HTMLElement {
constructor() {
super();
}
connectedCallback() {
let div = document.createElement("div");
div.textContent = this.getAttribute("name");
// this.appendChild(div);
this.prepend(div);
}
}
);
<my-resume>
<job-experience>
<company-name name="The Last Company Worked"></company-name>
</job-experience>
</my-resume>

Changing shadowRoot.innerHTML stop inherited behavior for attributes and events

Below ake-class2 inherits from/extends ake-class1.
Adding <select> element to ake-class2.shadowRoot.
console.log this.clickme button to make sure it's inherited correctly.
clickme button doesn't work without adding again lines after comment These 3 lines in ake-class2.
I couldn't understand why this behavior happen.
why this happpens ?
<html>
<head>
<title>AKE Front</title>
<script>
class1_html = `
<div class="container">
<button class="clickme">Click Me</button>
</div>
`
class2_html = `
<select></select>
`
/*--------------------------------------------------------------------------------*/
class AKEclass1 extends HTMLElement { //custom-component class
constructor() {
super(); // always call super() first in the constructor.
//const root = this.createShadowRoot(); //chrome only - deprecated
const root = this.attachShadow({mode: 'open'}); //By calling attachShadow with mode: 'open', we are telling our element to save a reference to the shadow root on the element.shadowRoot property
this.shadowRoot.innerHTML = class1_html;
// These 3 lines
this.container = this.shadowRoot.querySelector("div.container");
this.clickme = this.container.querySelector("button.clickme");
this.clickme.addEventListener("click", this.clickMe.bind(this));
}
clickMe() {
alert("Hello !");
}
}
customElements.define('ake-class1', AKEclass1);
/*--------------------------------------------------------------------------------*/
class AKEclass2 extends AKEclass1 { //custom-component class
constructor() {
super(); // always call super() first in the constructor.
this.shadowRoot.innerHTML += class2_html;
// These 3 lines
//this.container = this.shadowRoot.querySelector("div.container");
//this.clickme = this.container.querySelector("button.clickme");
//this.clickme.addEventListener("click", this.clickMe.bind(this));
}
}
customElements.define('ake-class2', AKEclass2);
/*--------------------------------------------------------------------------------*/
</script>
</head>
<body>
<ake-class2 class="ake_window"></ake-class2>
</body>
</html>
As mentioned in the comments .innerHTML += is the culprit.
What it does:
Create a NEW string by concatening .innerHTML + NEWString
delete the innerHTML DOM tree
and then Garbage Collection (GC) kicks in:
Delete all existing DOM elements, thus remove all connected listeners
set the NEW String as innerHTML
Some 'gurus' say this makes innerHTML evil, I say you need to understand what it does.
In the SO snippet below you see the listener being connected twice, but only executed once when clicked
<script>
class BaseClass extends HTMLElement {
constructor() {
super().attachShadow({mode:'open'})
.innerHTML = `<button>Click ${this.nodeName}</button>`;
this.listen();// but removed by GC
}
listen(){
console.log("add listener on", this.nodeName);
this.shadowRoot
.querySelector("button")
.onclick = (evt) => this.clicked(evt);
}
clicked(evt){
console.log("clicked", this.nodeName)
}
}
//customElements.define('element-1', BaseClass);
customElements.define('element-2', class extends BaseClass {
connectedCallback(){
this.shadowRoot.innerHTML += ` with concatenated HTML`;
this.listen();
}
});
</script>
<element-2></element-2>
Notes:
Using the inline onclick handler, it only allows for one handler where addEventListener can add more (you can use it here if you like)
No need for oldskool .bind(this) by defining lexical scope with a arrow function, not a function reference
all can be chained because
super() sets AND returns the this scope
attachShadow sets AND returns this.shadowRoot

how can i select an element in a template within a web component using vanilla javascript?

I'm having the hardest time selecting the ".recipe-type-menu-container" within the template in this component I built. I've tried using:
const container = typeofRecipe_template.content.querySelector(".recipe-type-menu-container");
but I keep getting an undefined error message. I'm trying to do all this with vanilla JS.
export class TypeofRecipe extends HTMLElement{
constructor(){
super();
} // end of constructor()
connectedCallback(){
var container;
var typeofRecipe_template = document.createElement('template');
typeofRecipe_template.innerHTML = `
<div class="recipe-type-menu-container" >
<div class="recipe-type-menu-row"></div>
<div class="recipe-type-menu-row"></div>
</div> <!---recipe-type-menu-container ->
`;
const container = typeofRecipe_template.content.querySelector(".recipe-type-menu-container");
this.parentNode.appendChild(typeofRecipe_template.content.cloneNode(true));
};
}

Create HTML Element with ES6

For an application i want to create Object-Based components in ES6.
On the normal way, you can create Elements as follow:
var element = document.createElement('YourElement');
element.innerHTML = 'Content';
document.querySelector('body').appendChild(element);
How i can create these in ES6 like:
export default class Container extends HTMLDivElement {
constructor() {
super();
this.innerHTML = 'Content';
}
}
With these example?
var container = new Container();
document.querySelector('body').appendChild(container);
My idea is, to create an -only JavaScript- UI Framework, without using "native" HTML snippets...
<div class='body'>
</div>
<script>
class Container extends HTMLElement {
constructor() {
super();
console.log( 'Constructed' )
}
connectedCallback() {
console.log('Callback');
this.innerHTML = "Content";
}
}
customElements.define('my-contain', Container);
let container = new Container();
document.querySelector('.body').appendChild(container);
</script>
You need to register your Component with the CustomElementRegistry below your Class definition and utilize the connectedCallback().
export default class Container extends HTMLDivElement {
constructor() {
super();
this.innerHTML = 'Content'; // Does Nothing
}
connectedCallback() { // Fires when attached
console.log('Callback');
this.innerHTML = "Content";
}
}
customElements.define('my-contain', Container, { extends: "div" });
LIFECYCLE HOOKS OF CUSTOM COMPONENTS
More info on CustomElementRegistry here: MDN CustomElementRegistry
More info on implementation of such here: MDN Using Custom Elements

Extending HTML elements in Web components

From the custom elements page, I see that to extend an element you do:
var XFooButtonPrototype = Object.create(HTMLButtonElement.prototype);
XFooButtonPrototype.createdCallback = function() {
this.textContent = "I'm an x-foo button!";
};
var XFooButton = document.registerElement('x-foo-button', {
prototype: XFooButtonPrototype,
extends: 'button'
});
Then later in the guide it says that you can make an element by writing either:
<x-foo></x-foo>
Or:
<button is="x-foo-button"></button>
Questions:
Why is it important to specify extends: 'button' when the element is obviously_ inheriting from HTMLButtonElement (since it has HTMLButtonElement.prototype in its proto chain)
How is the link between button and x-foo-button established? Does x-foo-button become a possible option of button in terms of is="x-foo-button" thanks to that extends: 'button' ? What happens "internally", so to speak?
Why would you pick <button is="x-foo-button"></button> over <x-foo></x-foo>...?
[ADDENDUM]
Polymer saves us from this duplication:
MyInput = Polymer({
is: 'my-input',
extends: 'input',
created: function() {
this.style.border = '1px solid red';
}
});
If extends is there, Polymer will put the right prototype in the chain with Object.getPrototypeOf(document.createElement(tag));.
So, corollary question:
Why the duplication in the first place? If there is an extends, shouldn't the browser automatically do this?
You totally misunderstood how extending web components work.
Create simple elements
First of all, this is how you register a new element:
var XFoo = document.registerElement('x-foo', {
prototype: Object.create(HTMLElement.prototype)
});
To create an element you can do one of these:
<x-foo></x-foo>
var xFoo = new XFoo();
document.body.appendChild(xFoo);
var xFoo = document.createElement( 'x-foo')
document.body.appendChild(xFoo);
Create extended elements
This is how you extend an existing element:
var XFooButton = document.registerElement('x-foo-button', {
prototype: Object.create(HTMLButtonElement.prototype),
extends: 'button'
});
To create one you can do one of these:
<button is="x-foo-button"></button>
var xFooButton = new XFooButton();
document.body.appendChild(xFoo);
var xFooButton = document.createElement('button', 'x-foo-button');
document.body.appendChild(xFooButton);
Note that in case of extended custom elements, when registering them you have to specify both the prototype (set to HTMLButtonElement.prototype rather than HTMLElement.prototype), and the extended tag's name (extends: 'button').
Also, when you create an extended element using markup or createElement(), you need to also specify the basic element (button) and the extended one (x-foo-button),
(Note: I am aware I am answering myself)
I think its Importent to Say here:
WARNING DEPRECATED Browser API METHOD
Here in this Question a .registerElement is Used it got Replaced by .defineElement and the Api has changed
current way to define a element
class AppDrawer extends HTMLElement {
constructor() {
super()
this.innerHTML = '<h1>UH</h1>'
}
}
window.customElements.define('app-drawer', AppDrawer);
// Or use an anonymous class if you don't want a named constructor in current scope.
window.customElements.define('app-drawer-noname', class extends HTMLElement {
constructor() {
super()
this.innerHTML = '<h1>UH AH</h1>'
}
});
Example - defining a mobile drawer panel, < app - drawer >:
Example usage:
<app-drawer></app-drawer>
<app-drawer-noname></app-drawer-noname>
```

Categories