class with html element property - javascript

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>

Related

How can i use variable inside Vanilla JavaScript class to make a function based on boolean

I have a JS Class which is making a function of giving a border to an element when i click to another element. That means when I click on .trigger class element, it will give a border to .content class element. But this function only should be happen based on a Boolean condition.
If Boolean is Yes it should be give border, otherwise it should not work. My code works when I set the method inside the constructor parentheses with this keyword and I can also declare variable there. But I need the method outside the constructor based on my other code.
So how can I possible declare variable outside the constructor and inside the class. I need this using Class approach based on my project.
My code is as follows.
class ParentSelector {
constructor(trigger, content, condition) {
this.trigger = document.querySelector(trigger);
this.content = document.querySelector(content);
}
let outline = condition;
makeOutline() {
this.trigger.addEventListener("click", (e) => {
if (condition) {
e.target.nextElementSibling.style.border = "2px solid red";
}
})
}
}
let a = new ParentSelector(".trigger", ".content", true);
a.makeOutline();
<div class="one">
<div class="trigger">Trigger</div>
<div class="content">Content</div>
</div>
First, why some onClick whose only use case is the if-condition? Shouldn't you be like, inside makeOutline() :
makeOutline() {
if (this.condition) {
this.trigger.addEventListener("click", (e) => {
e.target.nextElementSibling.style.border = "2px solid red";
})
}
}
?
Second, why are you trying to set local variables outside constructor/methods?
UPDATE: I see you're asking about declaring a class field outside of its constructor. In that case, you declare it at the top of the class. The following should work:
class ParentSelector {
condition = false;
constructor(trigger, content, condition) {
this.trigger = document.querySelector(trigger);
this.content = document.querySelector(content);
this.condition = condition;
}
// ...
}
Feel free to ask me any questions you have about this!
at last i able to find the solution myself after a deep thinking. Following is the solution. Following is the code.
class ParentSelector {
constructor(trigger, content, condition) {
this.trigger = document.querySelector(trigger);
this.content = document.querySelector(content);
this.outline = condition;
}
makeOutline(){
this.trigger.addEventListener("click", (e) => {
if (this.outline) {
e.target.nextElementSibling.style.border = "2px solid red";
}
})
}
}
let a = new ParentSelector(".trigger",".content", true);
a.makeOutline();
<div class="one">
<div class="trigger">Trigger</div>
<div class="content">Content</div>
</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>

What does the className attribute mean in JSX?

I'm trying to figure out some sample JavaScript/React/Enzyme code and getting totally confused on what className attribute means in the JSX part of ReactTestObj below.
I know className in JSX is used because class is a reserved keyword in JavaScript, but I thought the className/class attribute in JSX/HTML was a reserved keyword for referencing a CSS class? If there is no CSS as in my example, what is the legal use of class/className other than referencing CSS classes?
import React from 'react';
export class ReactTestObj extends React.Component<Props, State> {
constructor(props) {
super(props);
}
render() {
return (
<div className={'outer'}>
<div className={'inner'}>
<span className={'prop'}>prop</span>
<span className={'state'}>state</span>
<button
className="activate"
onClick={function() {
}}>
{this.props.value}
</button>
</div>
</div>
);
}
}
and the sample test code for context:
import { mount, React, expect } from '../specHelper';
import { ReactTestObj } from '../../src/components/ReactTest';
describe('ReactTest', () => {
it('should have an outer div', function() {
const wrapper = mount(<ReactTestObj />);
expect(wrapper.find('.outer')).to.exist;
});
it('should have an inner div', function() {
const wrapper = mount(<ReactTestObj />);
expect(wrapper.find('.inner')).to.exist;
});
it('should have a prop', function() {
const wrapper = mount(<ReactTestObj />);
expect(wrapper.find('.prop')).to.exist;
});
it('should have a state and it should be set to 10', function() {
const wrapper = mount(<ReactTestObj />);
expect(wrapper.find('.state')).to.exist;
expect(wrapper.find('.state')).value('state');
});
className is used instead of class in JSX because class is a JavaScript keyword.
All JSX gets turned into vanilla JavaScript. If you wrote class it would try to make a JavaScript class and not make an element that has a class.
So, when you write react it looks like this.
const name = 'Maddie';
const element = <h1 className="myName">Hello, {name}</h1>;
Then something like babel will take that code and turn it into vanilla JavaScript:
var name = 'Maddie';
var element = React.createElement("h1", {
className: "myName"
}, "Hello, ", name);
In vanilla JavaScript className is used to assign classes because the class keyword makes a different type of class.
className is the javascript handler to define and read the html class of a node
those are mostly used to search the site for element that are in the same group (like all elements that are part of a list etc) and to define style properties through css for that group of elements

Set content of custom HTML element

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.

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).

Categories

Resources