I know that in jquery you can do this but how can it be done in pure javascript?
$(this).parent().siblings().find('.children').addClass("unselected");
Possible implementation of siblings():
const sib = (dir) => (node) => (
node[dir]
? [node[dir], ...sib(node[dir])]
: []
);
const prevSiblings = sib('prevSibling');
const nextSiblings = sib('nextSibling');
const siblings = (node) => [
...prevSiblings(node),
...nextSiblings(node),
];
Reference for getSiblings part.
document.onclick = e => {
var siblings = getSiblings(e.target.parentElement);
siblings.forEach(element => {
var children = element.querySelectorAll(".children");
children.forEach(el => {
el.classList.add("unselected");
})
})
}
var getSiblings = function(elem) {
// Setup siblings array and get the first sibling
var siblings = [];
var sibling = elem.parentNode.firstChild;
// Loop through each sibling and push to the array
while (sibling) {
if (sibling.nodeType === 1 && sibling !== elem) {
siblings.push(sibling);
}
sibling = sibling.nextSibling
}
return siblings;
};
.unselected {
color: red
}
<div>siblings div
<div>CLICK HERE</div>
</div>
<div>siblings div2
<div>OR CLICK HERE</div>
</div>
<div>siblings div3
<div class="children">div with class children</div>
</div>
<div>siblings div4
<div>OR CLICK HERE</div>
</div>
There is no direct analog for siblings() in DOM API.
this.parentNode.parentElement.querySelectorAll('.children')
.forEach(child => child.classList.add('unselected'));
Related
I am trying to switch two paragraphs after clicking the button but I am stuck. Is there any way how to do this using inner HTML without using IF or boolean? I tried this code but it doesn't work. Thanks
let elmsButton = document.querySelectorAll("button")[0];
let elmsParagraf1 = document.querySelectorAll(".prvni")[0];
let elmsParagraf2 = document.querySelectorAll(".druhy")[0];
elmsButton.addEventListener("click", () => {
elmsParagraf1.innerHTML = "<div class='druhy'></div>"
elmsParagraf2.innerHTML = "<div class='prvni'></div>"
});
Assign each DOM.innerHTML of paragraph to a variable then swap them like below example:
let elmsButton = document.querySelectorAll("button")[0];
let elmsParagraf1 = document.querySelectorAll(".prvni")[0];
let elmsParagraf2 = document.querySelectorAll(".druhy")[0];
elmsButton.addEventListener("click", () => {
const p1 = elmsParagraf1.innerHTML;
const p2 = elmsParagraf2.innerHTML
elmsParagraf1.innerHTML = p2;
elmsParagraf2.innerHTML = p1
});
<button>Click</button>
<div class='prvni'>Paragraph 1</div>
<div class='druhy'>Paragraph 2</div>
Why don't you use querySelector in place of using querySelectorAll and choose the first element?
By the way, I advise to delete and re-add the elements from the parent rather than using innerHTML. The use of innerHTML would not preserve listeners and have worse performances:
let elmsButton = document.querySelector("button");
let elmsParagraf1 = document.querySelector(".prvni");
let elmsParagraf2 = document.querySelector(".druhy");
elmsButton.addEventListener("click", () => {
swapElements(elmsParagraf1, elmsParagraf2);
});
function swapElements(elem1, elem2) {
// Check if siblings
if (elem1.parentElement !== elem2.parentElement) {
console.error('No sibling elements!');
return;
}
elem1.replaceWith(elem2.cloneNode(true));
elem2.replaceWith(elem1.cloneNode(true));
}
Example:
let elmsButton = document.querySelector("button");
elmsButton.addEventListener("click", () => {
let elmsParagraf1 = document.querySelector(".prvni");
let elmsParagraf2 = document.querySelector(".druhy");
swapElements(elmsParagraf1, elmsParagraf2);
});
function swapElements(elem1, elem2) {
// Check if siblings
if (elem1.parentElement !== elem2.parentElement) {
console.error('No sibling elements!');
return;
}
elem1.replaceWith(elem2.cloneNode(true));
elem2.replaceWith(elem1.cloneNode(true));
}
<button>Click me</button>
<div class="prvni">I am the first div</div>
<div class="druhy">I am the second div</div>
You can switch those two divs by creating a temporary variable that holds Paragraf1 and then change Paragraf1 to Paragraf2 and vice versa, but with the variable.
let temp = elmsParagraf1.innerHTML;
elmsParagraf1.innerHTML = elmsParagraf2.innerHTML
elmsParagraf2.innerHTML = temp
In GTM i'm trying to return the inner text of a sibling element of the clicked element.
<div class="repair-item-n ">
<div class="repair-slide--54894d33-6c88-488f-95d7-3ec9b6a3ade4">
<div class="restoration_wrap text-center">
<img class="restoration-image">
</div>
<p class="title">Bags</p>
</div>
</div>
For example, on click of class "restoration-image" I want to return the value "Bags".
I have multiple occurrences of this HTML on the page with varinats such as "Shoes", "Hats" etc so I want to know on click of each, which would be the respective text of the "title" class
Try this:
var images = document.querySelectorAll('.restoration-image');
images.forEach(function (image) {
image.addEventListener('click', function (event) {
var parent = this.parentNode;
var nextSibling = parent.nextElementSibling;
alert(nextSibling.innerText)
})
});
This should work for GTM custom JavaScript variables.
function getImgTitle() {
if({{event}} === 'gtm.click') {
var image = {{Click Element}};
var parent = image.parentNode,
nextSibling = parent.nextElementSibling;
return nextSibling.innerText;
}
}
I would do this in a Custom Javascript Variable:
Click - Repair Item Title
function() {
var clickedEl = {{Click Element}};
if (!clickedEl) return;
// Find the mutual parent element
var repairItemEl = clickedEl.closest(".repair-item-n");
if (!repairItemEl) return;
var titleEl = repairItemEl.querySelector(".title");
if (!titleEl) return;
return titleEl.innerText
}}
I am under the impression if we target an element by element name of class name it will be applicable to all elements or elements with that class name. But in the following code when I clicked a button it will only change the innerHTML of the nearest span element even though I have add event listener by selecting just button element. Please clarify. Thanks.
const myArray = [0, 1, 2];
let myContainer = document.querySelector('.container');
const newArray = myArray.map((item) => {
let newArticle = document.createElement('article');
const myHTML = `<article>
<span></span>
<button>Click ${item}</button>
</article>
`;
newArticle.innerHTML = myHTML;
let myBtn = newArticle.querySelector('button');
myBtn.addEventListener('click', (e) => {
newArticle.querySelector('span').innerHTML = 'clicked';
})
return newArticle;
});
newArray.forEach((item) => {
myContainer.appendChild(item);
});
<div class="container">
<section>
</section>
</div>
let myBtn = newArticle.querySelector('button');
returns the FIRST button in newArticle
let myBtn = document.querySelectorAll('button');
returns ALL buttons in document
You can tell us what you expect by clicking on each one button?
I need to find a very performant way to find out if a custom element or any of its parent elements has display: none;
First approach:
checkVisible() {
let parentNodes = [];
let el = this;
while (!!(el = el.parentNode)) {
parentNodes.push(el);
}
return [this, ...parentNodes].some(el => getComputedStyle(el).display === 'none')
}
Is there anything that runs faster than this? Is this even a safe method?
The reason I need this: We have a <data-table> custom element (native webcomponent) which does very heavy lifting in its connectedCallback(). We have an application that has like 20-30 of those custom elements in a single page, which leads to IE 11 taking like 15 seconds until the page is rendered.
I need to delay initialisation of those <data-table> components which are initially not even visible, so I need a way to test inside the connectedCallback() if the element is visible (which it is not if it is in one of the 18 tabs initially not shown).
The easiest way to see if an element or its parent has display:none is to use el.offsetParent.
const p1 = document.getElementById('parent1');
const p2 = document.getElementById('parent2');
const c1 = document.getElementById('child1');
const c2 = document.getElementById('child2');
const btn = document.getElementById('btn');
const output = document.getElementById('output');
function renderVisibility() {
const p1state = isElementVisible(p1) ? 'is visible' : 'is not visible';
const p2state = isElementVisible(p2) ? 'is visible' : 'is not visible';
const c1state = isElementVisible(c1) ? 'is visible' : 'is not visible';
const c2state = isElementVisible(c2) ? 'is visible' : 'is not visible';
output.innerHTML = `Parent 1 ${p1state}<br>Parent 2 ${p2state}<br/>Child 1 ${c1state}<br/>Child 2 ${c2state}`;
}
function isElementVisible(el) {
return !!el.offsetParent;
}
function toggle() {
p1.style.display = (p1.style.display ? '' : 'none');
p2.style.display = (p2.style.display ? '' : 'none');
renderVisibility();
}
btn.addEventListener('click', toggle),
renderVisibility();
<div id="parent1" style="display:none">
<div id="child1">child 1</div>
</div>
<div id="parent2">
<div id="child2">second child</div>
</div>
<button id="btn">Toggle</button>
<hr>
<div id="output"></div>
This code converts el.offsetParent into a boolean that indicates if the element is showing or not.
This only works for display:none
Not sure about performance, but it should be faster than your approach at least:
HTMLElement.prototype.isInvisible = function() {
if (this.style.display == 'none') return true;
if (getComputedStyle(this).display === 'none') return true;
if (this.parentNode.isInvisible) return this.parentNode.isInvisible();
return false;
};
For a pure function that:
returns TRUE if and only if neither the element itself nor any of its parents up to the document itself have style display === 'none'
returns FALSE if and only if either the element itself or any parent element up to the document itself have style display === 'none'
You can define the below function and then call it on the element you wish to validate:
function isVisible(element) {
// Start with the element itself and move up the DOM tree
for (let el = element; el && el !== document; el = el.parentNode) {
// If current element has display property 'none', return false
if (getComputedStyle(el).display === "none") {
return false;
}
}
// Neither element itself nor any parents have display 'none', so return true
return true;
}
How do I add the ability to drag certain content from one element over to another element, and back again, in pure Javascript?
I need this functionality to change the position of the content based on desktop and mobile sizes.
I have made my own function but the problem is that it's not possible to do the last action, to move the content to it's Original position again. It needs some bind functionality I think?
function moveContent(fromid, toid)
{
// Insert After This
var ref_el = document.getElementById(toid);
var parent = ref_el.parentNode;
// From Element
var from_el = document.getElementById(fromid);
if (from_el != null)
{
var from_el_parent = from_el.parentNode;
tparent = from_el.parentNode;
if (tparent === null || tparent.id !== toid)
{
var holder = from_el.outerHTML;
from_el.innerHTML = '';
// Insert inside
ref_el.innerHTML = holder;
}
}
}
Function example
function ChangeContent(aid,bid)
{
if((document.getElementById(aid)!=null)&&(document.getElementById(bid)!=null))
{
var atemp=document.getElementById(aid).innerHTML;
var btemp=document.getElementById(bid).innerHTML;
document.getElementById(aid).innerHTML=btemp;
document.getElementById(bid).innerHTML=atemp;
}
}
HTML example
<div id='A'><hr>
First div content<hr>
</div>
<div id='B'>
<strong>List</strong><br>
<ul>
<li>1</li>
<li>2</li>
<ul>
<li>3.1</li>
<li>3.2</li>
</ul>
</ul>
</div>
<input type='button' onclick='SwapContent(\"A\",\"B\");' value='Swap'></button>
Notes
You must place JavaScript after HTML, because otherwise JavaScript will not be able to find the elements to swap the content of.
Quotes in "onclick" function parameters are of this type because all code written for PHP+Html printing width ".
I'm not sure whether how practical your approach is but here is a JavaScript solution which will remove an element from the DOM and append it inside another element.
If the parent element doesn't have an id atribute, one is created using a Counter.
Restoring the element is simply a case of keeping track of the id of the parent element using a data-parent attribute.
(function() {
var Counter = function () {
if (Counter.prototype._singletonInstance) {
return Counter.prototype._singletonInstance;
}
Counter.prototype._singletonInstance = this;
this.getValue = function() {
if (this.value) {
this.value++;
} else {
this.value = 1;
}
return this.value;
};
};
function moveContent(fromId, toId) {
var from_el = document.getElementById(fromId);
var target_el = document.getElementById(toId);
if (from_el != null && from_el != target_el) {
var from_el_parent = from_el.parentNode;
var parent_id = from_el_parent.getAttribute("id");
if (!parent_id) {
// parent element doesn't have an id
// so generate a new parent id
var counter = new Counter();
parent_id = "gen_" + counter.getValue();
from_el_parent.setAttribute("id", parent_id);
}
if (!from_el.getAttribute("data-parent")) {
// the data-parent attribute is our route home
from_el.setAttribute("data-parent", parent_id);
}
// Insert After This
target_el.appendChild(from_el);
}
}
function restoreContent(id) {
var el = document.getElementById(id);
var parent = el.getAttribute("data-parent");
if (parent) {
// data-parent attribute exists
var target = document.getElementById(parent);
if (target) {
// target is valid
target.appendChild(el)
}
}
}
document.getElementById("switchAtoB").onclick = function switchAtoB() {
moveContent("contentA", "parentB");
}
document.getElementById("restore").onclick = function restoreA() {
restoreContent("contentA");
}
})();
#parentA {
background-color: #0aa;
min-height: 100px;
}
#parentB {
background-color: #aa0;
min-height: 100px;
}
<div>
<div id="parentA">
<div id="contentA">
<h1>Simple Title</h1>
<p>Lorem ipsum...</p>
</div>
</div>
<div id="parentB">
<div id="intro">
<p>Blah blah blah ...</p>
</div>
</div>
<div>
<button id="switchAtoB">A -> B</button>
<button id="restore">Switch Back</button>
</div>
</div>