Set content of custom HTML element - javascript

I implemented a modal as a custom HTML tag.
class ModalDialog extends HTMLElement {
constructor() {
super();
this.shadow = this.attachShadow({
mode: 'open'
});
this.modal = document.createElement('div');
this.modal.className = 'modal';
this.modalWrapper = document.createElement('div');
this.modalWrapper.className = 'modal-wrapper';
this.modalHeader = document.createElement('div');
this.modalHeader.className = 'modal-header';
this.modalHeader.innerHTML = 'Oops, nothing found!';
...
}
Also, I implemented another class which inherits from HTMLElement. Let's call it A. Said class is trying to create a ModalDialog and should add it to the DOM so it will be displayed.
Now, my question is: How can I set the text of the modalHeader from class A?
I tried to set an attribute and read it in the ModalDialog class but at that time, the attribute is undefined.
class A extends HTMLElement {
...
this.modal.setAttribute('headerText', 'Blablabla');
...
}
Is there any good way to solve this?

Your class A should be able to just access the inner elements and set either their innerHTML or textContent like this:
class A extends HTMLElement {
...
this.modal.innerHTML = 'Blablabla';
...
}
Also, make sure you are placing this.modal into the shadowRoot:
this.shadowRoot.appendChild(this.modal);
On other thing to be aware of is that you do not need to save off the results of this.attachShadow:
this.shadow = this.attachShadow({mode: 'open'});
Since that is already available as this.shadowRoot.

Related

Is there a way to add custom attributes to custom elements

While messing around with custom elements I wondered if one could use custom attributes within the elements (and possibly within their children too). I know VueJS does something similar with attributes like v-bind, v-for, etc; and I know there's probably a lot more going on under the hood there than I realize. I've tried registering custom elements and attempting to retrieve them like so:
<new-element cool="Awesome!"> </new-element>
class NewElement extends HTMLElement {
constructor() {
super();
this.coolAttr = this.getAttribute("cool");
}
}
customElements.define("new-element", NewElement);
However, when loading the page (in Google Chrome for me) the "custom" attributes disappear, and any attempt at getting them retrieves null. Is there a way to "register" these custom attributes, or do I have to stick with data- attributes?
Attributes become available in the connectedCallback,
they are not available yet in the constructor
Unless the Custom Element is PARSED (in the DOM) BEFORE the Element is defined!!
Also be aware the attributeChangedCallback runs before the connectedCallback
for Observed attributes
Also see: https://andyogo.github.io/custom-element-reactions-diagram/
.as-console-row-code {
font: 12px Arial!important;
background:yellow;
color:darkred;
}
.as-console-row:after{ display:none!important }
<before-element cool="Awesome?">FOO</before-element>
<script>
class NewElement extends HTMLElement {
log( ...args ){
console.log(this.nodeName, `cool:${this.getAttribute("cool")}`,"\t\t\t",...args );
}
static get observedAttributes() {
return ["cool"];
}
constructor() {
const name = "constructor"; // CAN! run code BEFORE super()!
// super() sets AND returns the 'this' scope
super().log(name);
}
connectedCallback() {
this.log("connectedCallback", this.innerHTML || "innerHTML not parsed yet");
// be aware this.innerHTML is only available for PARSED elements
// use setTimeout(()=>{...},0) if you do need this.innerHTML
}
attributeChangedCallback(name, oldValue, newValue) {
this.log(`attributeChangedCallback name:${name}, old:${oldValue}, new:${newValue}`);
}
}
customElements.define("before-element", class extends NewElement {});
customElements.define("after-element", class extends NewElement {});
</script>
<after-element cool="Awesome!!">BAR</after-element>
It can be easily solved by adding the name of the attributes after "data-".
<New-element data-*="Anything you want" />
for example, you can have:
<element data-cool="value">
You can have as many custom attributes as you want.

class with html element property

I want to create a class which has the div element as a property
<div id="corpus"></div>
class ProtoDiv {
divElement: HTMLDivElement;
constructor(){
this.divElement = <HTMLDivElement>document.getElementById("corpus")!;
}
getDivElem(this:ProtoDiv){
console.log(this.divElement)
}
}
const myDiv = new ProtoDiv();
console.log(myDiv.divElement);
why does the property returns null instead of the html div element ?
Your code totally works as plain JavaScript (I've just pasted the result of TypeScript transpilation). You must be missing something like the script execution order (does the div exist when the code execute?).
"use strict";
class ProtoDiv {
constructor() {
this.divElement = document.getElementById("corpus");
}
getDivElem() {
console.log(this.divElement);
}
}
const myDiv = new ProtoDiv();
console.log(myDiv.divElement);
<div id="corpus"></div>

Custom element not picking up attributes

I am trying to have a look at custom elements and how they work and while the examples on MDN work fine I'm seemingly unable to replicate them myself.
The MDN article is here.
This is a working example from MDN.
My problem is that I can't ever seem to pass attributes into my component, they always come out as null instead of passing over the value of the parameter.
My JS is (test.js)
class PopUpInfo extends HTMLElement {
constructor() {
// Always call super first in constructor
super();
// Create a shadow root
const shadow = this.attachShadow({mode: 'open'});
// Create spans
const wrapper = document.createElement('span');
const info = document.createElement('span');
// Take attribute content and put it inside the info span
const text = this.getAttribute('foo'); // <-- this always returns null
info.textContent = `(${text})`;
shadow.appendChild(wrapper);
wrapper.appendChild(info);
}
}
// Define the new element
customElements.define('popup-info', PopUpInfo);
And my Html:
<html>
<head>
<script src="test.js"></script>
</head>
<body>
<hr>
<popup-info foo="Hello World"></popup-info>
<hr>
</body>
</html>
What I'm expecting to see on screen is the text
(Hello World)
but all I ever see is
(null)
When I debug I can see that this.attributes has a length of 0 so it's not being passed in.
Has anyone seen this before when creating custom elements?
Keep Emiel his answer as the correct one.
Just to show there are alternative and shorter notations possible:
customElements.define('popup-info', class extends HTMLElement {
static get observedAttributes() {
return ['foo'];
}
constructor() {
const wrapper = document.createElement('span');
super().attachShadow({mode:'open'})// both SETS and RETURNS this.shadowRoot
.append(wrapper);
this.wrapper = wrapper;
}
attributeChangedCallback(name, oldValue, newValue) {
switch(name) {
case 'foo':
this.wrapper.textContent = `(${newValue})`;
break;
}
}
});
<popup-info
foo="Hello World"
onclick="this.setAttribute('foo','Another world')"
>
</popup-info>
Although your example seems to run fine when I try to run it here in a snippet, I still want to make a suggestion to improve it.
Use the observedAttributes static getter to define a list of attributes which the component should keep an eye on. When the value of an attribute has been changed and the name of the attribute is in the list, then attributeChangedCallback callback is called. In there you can assert logic on what to do whenever you attribute value has been changed.
In this case you could build your string that you desire. This also has the side effect that whenever the attribute value is changed again, the string will be updated.
class PopUpInfo extends HTMLElement {
/**
* Observe the foo attribute for changes.
*/
static get observedAttributes() {
return ['foo'];
}
constructor() {
super();
const shadow = this.attachShadow({
mode: 'open'
});
const wrapper = document.createElement('span');
const info = document.createElement('span');
wrapper.classList.add('wrapper');
wrapper.appendChild(info);
shadow.appendChild(wrapper);
}
/**
* Returns the wrapper element from the shadowRoot.
*/
get wrapper() {
return this.shadowRoot.querySelector('.wrapper')
}
/**
* Is called when observed attributes have a changed value.
*/
attributeChangedCallback(attrName, oldValue, newValue) {
switch(attrName) {
case 'foo':
this.wrapper.textContent = `(${newValue})`;
break;
}
}
}
// Define the new element
customElements.define('popup-info', PopUpInfo);
<html>
<head>
<script src="test.js"></script>
</head>
<body>
<hr>
<popup-info foo="Hello World"></popup-info>
<hr>
</body>
</html>
You're missing a defer attribute in your script import within the HTML and it is not loading properly, thats the problem. The defer attribute allows the script to be executed after the page is parsed
class PopUpInfo extends HTMLElement {
constructor() {
// Always call super first in constructor
super()
// Create a shadow root
const shadow = this.attachShadow({ mode: 'open' })
// Create spans
const wrapper = document.createElement('span')
const info = document.createElement('span')
// Take attribute content and put it inside the info span
const text = this.getAttribute('foo') // <-- this always returns null
info.textContent = `(${text})`
shadow.appendChild(wrapper)
wrapper.appendChild(info)
}
}
// Define the new element
customElements.define('popup-info', PopUpInfo)
<html>
<head>
<script src="app.js" defer></script>
</head>
<body>
<hr />
<popup-info foo="Hello World"></popup-info>
<hr />
</body>
</html>

custom web component event call back function in tag

class UioKey extends HTMLElement {
...
eKey(){windows.alert('class eKey function')}
}
function eKey(){
eKey(){windows.alert('document eKey function')}
<template id="uio-key-temp">
<div class="uio-key">
<div class="i" onclick="eKey()"></div><span></span>
</div>
</template>
when clikcing on the .i div agot the document ekey that is firing, i want
the class ekey() to be fired
if i omit the dodument eKey() fuction i got function eKey() undefined
onclick will only work with globally defined functions.
Here is a very quick hack that allows you to use a class function.
// Class for `<uio-key>`
class UioKey extends HTMLElement {
constructor() {
super();
let shadow = this.attachShadow({mode: 'open'});
shadow.innerHTML = '<div><div on-click="eKey">div</div><span>span</span></div>';
let a = shadow.querySelectorAll('[on-click]');
a.forEach(
el => {
const handlerName = el.getAttribute('on-click');
el.addEventListener('click', this[handlerName]);
}
);
}
eKey() {
window.alert('class eKey function');
}
}
// Define our web component
customElements.define('uio-key', UioKey);
<hr/>
<uio-key></uio-key>
<hr/>
I use a custom attribute on-click as a way to grab all elements that want a click handler then I take the value of that attribute and use it as the class function name and pass it into the addEventListener function.
Alternatly to #Intervalia's answer, you could use the getRootNode() method and then the host property to access the Custom Element object from inside the Shadow DOM.
class UioKey extends HTMLElement {
constructor() {
super()
this.attachShadow( { mode: 'open' } )
.innerHTML = uio-key-temp.innerHTML
}
eKey(){
window.alert('class eKey function' )
}
}
customElements.define( 'uio-key', UioKey )
<template id="uioKeyTemp">
<style> .i { background: gray ; height: 10pt } </style>
<div class="uio-key">
<div class="i" onclick="this.getRootNode().host.eKey()"></div>
</div>
</template>
<uio-key></uio-key>
Note that it's always a good practice to use inline scripts because in some situations they can be disabled (for security reasons).

Add a class to a specific dom outside of Component in Angular2

I have a component that opens up a modal based on if the a variable called 'isVisible' is True or False. Once the modal is visible, I would like to add a class to the 'body' tag of the page and once it is closed, I'd like to remove the class from the 'body' tag.
Below is a snippet of my code and what I have tried.
import {Component, ElementRef} from '#angular/core';
#Component({
selector: 'modal',
template: '<div class="modal_div">
<div (click)="closeCard()"></div>
<div class="modal_body">blah</div>
</div>'
})
export class DailogComponent{
isVisible: boolean;
constructor(public element: ElementRef){
this.isVisible = false;
}
OpenModal(){
this.isVisible = true;
//add css class to body here which is where I am lost
console.log(this.element.nativeElement.parentNode.querySelector('body'));
}
closeModal(){
this.isVisible = false;
//remove css class to body here which is where I am lost
console.log(this.element.nativeElement.parentNode.querySelector('body'));
}
}
Someone correct me if this is wrong or an anti pattern in ng.
You can just use javascript for that. If I understood correctly you want to change the class of the body tag. So the body of the page. <body></body>
How do I toggle an element's class in pure JavaScript?
and getElementsByTagName() or yeah query selector as you did, I personally use getElementsByTagName for no good reason other than I'm used to it.
//don't capitalize function names.
toggleBodyClass(){
this.toggleClass(document.getElementsByTagName('body')[0], 'myclass');
}
//this function is taken from the Stackoverflow thread posted above.
toggleClass(ele, class1) {
let classes = ele.className;
let regex = new RegExp('\\b' + class1 + '\\b');
let hasOne = classes.match(regex);
class1 = class1.replace(/\s+/g, '');
if (hasOne)
ele.className = classes.replace(regex, '');
else
ele.className = classes + class1;
}
tested, works

Categories

Resources