I want to create a custom HTML element that behaves exactly like the built-in <div> element. I'm trying to prevent a <div> soup. I would for example want to have a <currency-list> element. This element should behave exactly like the <div> element. The only difference is the name. How can I achieve this?
Thanks,
Yosemite
a DIV (HTMLDivElement) is a block element.
But you don't even need a defined Custom Element/Web Component to make a block element
customElements.define("currency-list", class extends HTMLElement {
connectedCallback() {
this.style.display = "block";
}
});
another-list {
display: block;
}
body>*:defined {
background: green;
color: beige;
}
body>*:not(:defined) {
background: lightgreen;
}
Line 1
<currency-list>Hello Web Component</currency-list>
Line 3
<div>Line 4</div>
Line 5
<another-list onclick="alert(this.constructor.name)">Line 6</another-list>
Line 7
Notes:
<currency-list> is an "Autonomous Custom Element" (extends HTMLElement)
You can extend HTMLDivElement in Chromium and FireFox, but Apple has stated they will never implement "Customized Built-In Elements"
From: https://github.com/WICG/webcomponents/issues/509
<another-list> is an HTMLUnknownElement; nothing wrong with using it, its constructor is an HTMLElement, so can do everything an HTMLElement can do.
For more on the value of Unknown Elements see my Dev.to post
You can set any CSS display value on a DIV, you can't on your own Elements, as it will destroy the display:block setting.
PS. tag your SO questions web-component and/or custom-element
Related
Let's say I have some code like this:
class MyElem extends HTMLElement {
constructor() {
super();
let templateContent = document.getElementById('template-elem').content;
this.innerHTML = templateContent.cloneNode(true);
}
}
window.customElements.define('my-elem', MyElem);
<template id="template-elem">
<div class="a">
<div class="b">b</div>
<div class="c">c</div>
</div>
</template>
<my-elem></my-elem>
Why doesn't this work? In the Chrome inspector, the custom element has no HTML inside of it. I've also tried doing:
this.append(templateContent.cloneNode(true));
but that also resulted in an empty HTML tree.
All the tutorials mention using the shadow DOM like the following:
this.attachShadow({mode: 'open'}).appendChild(templateContent.cloneNode(true));
and while that works, it forces you to use the Shadow DOM for your custom element. Is there no way of just appending the template's HTML to your custom element without being required to use the Shadow DOM? I'd prefer to just use global CSS styling in my small use-case.
You are falling into multiple traps, like everyone in their first Component adventures.
Custom Elements (strictly speaking only Elements with shadowDOM are Web Components) have lifecycle phases and Callbacks.
This diagram: https://andyogo.github.io/custom-element-reactions-diagram/ is a MUST to understand.
You want to add DOM content in the constructor phase; but there is no DOM Element yet in this phase.
Only in the connectedCallback can DOM content be added.
With shadowDOM this is another story, its "DocumentFragment" is available in the constructor, you can set content, But it is not a DOM Element yet! The connectedCallback tells you when your Custom Element was attached to the DOM.
Templates content is a DocumentFragment, but your .innerHTML expects a string.
Since (in your usage) <template> is a DOM element, you can read its innerHTML (see below)
So, yes Custom Elements without shadowDOM are possible:
You will see the <template> content twice, demonstrating the 2 ways of adding content.
<script>
customElements.define("my-element", class extends HTMLElement {
connectedCallback() {
let template = document.getElementById(this.nodeName);
this.innerHTML = template.innerHTML;
this.append(template.content.cloneNode(true))
}
})
</script>
<template id="MY-ELEMENT">
Hello, I am an Element!
</template>
<my-element></my-element>
The constructor is where you prepare your Element
This constructor also runs when you do document.createElement("my-element").
The connectedCallback runs when your Element is added to the DOM
If you do not specify a method, the method from its Class parent runs, so in the above code the (default) constructor from HTMLElement is executed.
That is why you need super() in your own constructor... to execute the constructor from HTMLElement.
Note:
constructor(){
let template = document.getElementById("MY-ELEMENT").content.cloneNode(true);
super().attachShadow({mode:"open").append(template);
}
is totally valid code; Google Documentation that says "super needs to run first" is wrong.
You need to run super() before you can access the Elements own scope with this
That is why I prefer:
constructor(){
// do anything you want here, but you can not use 'this'
super() // Sets AND Returns 'this'
.attachShadow({mode:"open") // both Sets AND Returns this.shadowRoot
.append(document.getElementById(this.nodeName).content.cloneNode(true));
}
Note append() was not available in IE; so oldskool programmers won't know about its versatility: https://developer.mozilla.org/en-US/docs/Web/API/Element/append
When your Component adventures are going to involve Class inheritance;
you call parent methods with:
connectedCallback(){
super.connectedCallback()
}
The most simple implementation of a custom element would be:
class MyComponent extends HTMLElement {
connectedCallback() {
this.innerHTML = `<div>Hello world</div>`
}
}
customElements.define('my-component', MyComponent)
my-component {
display: block;
border: 1px dotted #900
}
<my-component></my-component>
However, if you don’t use Shadow DOM, you cannot encapsulate the CSS, but have to style the component through an external style sheet.
The most simple way to write a component with Shadow DOM would look like this:
class MyOtherComponent extends HTMLElement {
constructor() {
super()
this.shadow = this.attachShadow({ mode: "open" })
}
connectedCallback() {
this.shadow.innerHTML = `
<style>
:host {
display: block;
border: 1px dotted #900
}
</style>
<div class="component">Hello World!</div>
`
}
}
customElements.define('my-other-component', MyOtherComponent)
<my-other-component></my-other-component>
This way, you have a bit more overhead, but the component is truly encapsulated.
I am developing a game that glitches at some point through using the CSS filter: invert(1); property. However, when you use that property on body, it makes everything position: absolute;. This is not good because I need most elements to be fixed, and everything goes to a negative top and not visible. How can I effectively get all elements in a list that isn't a parent to any other elements, but included if it has text? Any answers or other stack overflow topics would be nice!
Here is some of my code:
// In a working loop called Repeat()
if(Glitch == 1) {
document.querySelector(".ChangableStyles").innerHTML = "* {filter: invert(1)}"
} else {
document.querySelector(".ChangableStyles").innerHTML = ""
}
Edit: Since all of you are asking, the .ChangableStyles tag is a style element. The filter on everything applies when I change the innerHTML of that style tag to valid CSS styles. I don't want to be rude, but I have the .ChangableStyles thing figured out. Thank you.
You mention you already have a list of elements, but it's not clear how you're generating that list. I've gone ahead on the assumption you're wanting to "select" all elements in <body></body> that don't have any children.
You can use a combination of Array.from(), your pre-existing selection logic, and a filter() using node.childElementCount === 0 to accomplish what you describe. However on higher-complexity DOMs this will be computationally expensive, so I would implore you to re-consider your design instead of opting for this route. To be clear, this will meet your requirement of selecting ANY Node in the DOM which has no child elements ("isn't a parent to any other elements"), which includes any script, style or other "user-invisible" nodes in the body.
document.getElementById('get-elements-button').addEventListener('click', function () {
console.log(Array.from(document.body.getElementsByTagName("*")).filter(function (node) {
return node.childElementCount === 0;
}));
});
<div class="has-child-elements">
This is a child element
</div>
<div class="has-no-child-elements">
</div>
<div class="has-child-elements">
This is also a child element
</div>
<button id='get-elements-button'>Get elements with child elements →</button>
I am using Angular Material's Tab. And i am suppose to add and remove class using:
const classotherClustersSelection = document.getElementsByClassName('others');
Array.from(classotherClustersSelection).forEach(item => {
item.classList.remove('others');
})
On change of a particular data i am suppose to remove class from the tabs section, from all tabs. But unfortunately other tab that contains 'others' class is hidden with ng-if, so i am not able to manipulate the DOM directly. Whats are the approaches possible?
The issue is that *ngIf not hide the element, it removes it completle from the dom.
That's why you are not able to access the element.
If you want to hide something but still access it, you should remove the element by style.
You can use class or style binding for that:
style binding:
<div [style.display]="conditionForHide ? 'none' : 'initial'">
class binding:
<div [class.removeClass]="conditionForHide">
For the second option you need to create the class in you style sheet. An example to remove the element without display:
.conditionForHide {
opacity: 0;
pointer-events: none;
cursor: default;
width: 0px;
height: 0px;
position: absolute;
}
Edit
You should not manipulate the material code. And angular offers you a couple of options to manipulate your code next to plane javascript (getElementsByClassName).
The fastest way is when the data change is triggered, you manipulate the class via class binding:
Controller:
public showClassOthers = true;
onDataChange() {
// do some tasks
this.showClassOthers = false;
}
HTML:
<mat-tab-group>
<mat-tab label="First" [class.others]="showClassOthers"> Content 1 </mat-tab>
<mat-tab label="Second" [class.others]="showClassOthers"> Content 2 </mat-tab>
</mat-tab-group>
Add ngClass property to your div like I did below:
<div [ngClass]="{others: boolClass}" *ngIf="data==='requiredValue'"></div>
Then change the value of boolClass variable where the data gets changed:
this.data = 'someOtherValue';
this.boolCass = (this.boolClass)?(this.data==='requiredValue'): false;
Now the class will be removed from the element.
You can refer from my example here https://stackblitz.com/edit/angular-cc6tru
You can use ngClass to Conditionally add/remove the class from the DOM element.
demo
In Demo, I am changing the data on button click and based on the value class is added or removed.
I have an element, for communication purposes we'll call $elA which during runtime is having some CSS properties changed via .animate()
for example:
$elA.stop().animate({
top: `${Math.floor(Math.random()*99)}%`,
left: `${Math.floor(Math.random()*99)}%`,
width: '15px',
height: '15px',
opacity: '1.0'
//etc etc etc
});
When a certain event triggers later in the code, I am in need of creating a clone of $elA. For communication purposes lets call this $elB.
How can I do something akin to $elB.css = $elA.css during this event? It doesn't need to be a jQuery method, or it can be there is no problem, I just am not sure if there is an elegant way of handling this case where there is no current class associated with it because the properties for the DOM element I wish to clone doesn't exist in a sheet anywhere.
Thank you.
if you only need the styles to be cloned you can use a function for it like that: (Fiddle hier: https://jsfiddle.net/taxostd0/2/)
function copyStyles(from, to) {
var fromStyles = getComputedStyle(from);
for(prop in fromStyles) {
to.style[prop] = fromStyles[prop];
}
}
and then call it like this:
copyStyles($elA[0], $elB[0]);
Use .clone() method of jQuery. It performs a deep copy of the set of matched elements, meaning that it copies the matched elements as well as all of their descendant elements and text nodes.
If using javascript, the following would help to copy just the styles to another element.
document.getElementById("copy2").style.cssText= document.getElementById("copy1").style.cssText;
<div id="copy1" class ="copycss" style="width: 100px; background-color: blue; color: red; font-size: 15;">
10
</div>
<div id="copy2" class ="copycss">
10
</div>
Let's say i have the following code:
HTML
<div class="container">
<input class="myAwesomeInputBox">
</div>
CSS
.input [type=text]:focus > .//ANY CLASS SOMEWHERE ON THE WEBSITE{
//Some sweet CSS.
}
Obviously this code doesnt work. I want some specific css to get executed when there is focus on my inputbox. Is this at all possible?
I'm not specificly looking for html/css only solutions. Any solution that can achieve this is welcome.
My code above is just an extremely simple example. My question is really simple. Is it possible to change styling on ANY element on your website using the :focus on an input box.
Using pseudo-classes (such as :hover or :focus) to modify other elements can only be done if the other elements are siblings or children of the element which has the pseudo-class. That's because CSS child/sibling selectors are fairly restrictive.
You can use the > selector to select a direct child, and the + selector to select a direct sibling. For example, if you have the following HTML:
<form>
<input type="text" />
<input type="submit" />
</form>
<p class="arbitrary">
This is an arbitrary element. It is neither a child nor sibling of
the text field. It cannot be selected as a result of a pseudo-class
action on the textfield using CSS, but can be selected using
client-side scripting such as JavaScript.
</p>
You could style the button when the text field has focus (because it is a direct sibling of the text field), but there is no possible way to style the arbitrary paragraph as a result of the text field receiving focus (because it is neither a child nor sibling, it is the sibling of a parent) without using client-side scripting (JavaScript, jQuery, etc.).
This CSS would style the submit button, and can be altered to select any direct or indirect child or sibling:
input[type="text"]:focus + input[type="submit"] {
/* some sweet CSS */
background-color:green;
}
Using Javascript, of course, you have much greater flexibility. The focusin and focusout events can be used to toggle CSS classes. Here's an example that demonstrates both the CSS and JavaScript techniques of achieving this.
function setFocused() {
document.querySelectorAll('.arbitrary').forEach((result) => {
result.classList.add('focused');
});
}
function unsetFocused() {
document.querySelectorAll('.arbitrary').forEach((result) => {
result.classList.remove('focused');
});
}
document.querySelectorAll('input[type="text"]').forEach((result) => {
result.addEventListener("focusin", setFocused);
result.addEventListener("focusout", unsetFocused);
});
input[type="text"]:focus + input[type="submit"] {
/* some sweet CSS */
background-color: green;
}
.arbitrary.focused {
/* even more sweet CSS */
color: red;
}
<form>
<input type="text" />
<input type="submit" />
</form>
<p class="arbitrary">
This is an arbitrary element. It is neither a child nor sibling of
the text field. It cannot be selected as a result of a pseudo-class
action on the textfield using CSS, but can be selected using
client-side scripting such as JavaScript.
</p>
Here's the jQuery equivalent of the above code, if that's your jam.
$('input[type="text"]').on('focus', function() {
$('.arbitrary').addClass('focused');
});
$('input[type="text"]').off('focus', function() {
$('.arbitrary').removeClass('focused');
});
Note that if you decide you want to do something similar, except using a "hover" trigger rather than "focus", you can use the JavaScript mouseover and mouseout functions, or the jQuery .hover() function which takes two arguments (a handler for entering the hover and another for leaving the hover).
Maybe add a ID
<div class="container">
<input class="myAwesomeInputBox" id='myAwesomeId' type="text">
</div>
and add and remove a class like this.
Wont that solve your problem.
$('#myAwesomeId').on({
focus: function () {
$(this).addClass('focused');
},
blur: function () {
$(this).removeClass('focused');
}
});
CSS
input.focused {
border:3px solid blue;
}
FIDDLE
If the element css which you want to change is sibling, you can use like this,
<div class="container">
<input class="myAwesomeInputBox">
<div className="dls-sibling">
</div>
.myAwesomeInputBox:focus ~.dls-sibling {
&::before {
transform: scale(1);
border-color:red;
}
}