change properties of two divs with one onclick and querySelectorAll() - javascript

I have multiple elements that are seperatet in two divs. The first div contains a Text and the second div a color.
When I click on one element the text and color should change and if I click it again it should change back.
The problem is that no matter which one I click, its always the last one which changes.
The HTML part:
<style>
.colorGreen {
background-color: green;
}
.colorRed {
background-color: red;
}
</style>
<div class="box2">Text1</div>
<div class="box1 colorGreen">O</div>
<div class="box2">Text1</div>
<div class="box1 colorGreen">O</div>
<div class="box2">Text1</div>
<div class="box1 colorGreen">O</div>
The JavaScript part:
<script type='text/javascript'>
var box1Temp = document.querySelectorAll(".box1");
var box2Temp = document.querySelectorAll(".box2");
for (var i = 0; i < box1Temp.length; i++) {
var box1 = box1Temp[i];
var box2 = box2Temp[i];
box2.onclick = box1.onclick = function() {
if (box1.classList.contains("colorGreen")) {
box1.classList.add("colorRed");
box1.classList.remove("colorGreen");
box2.innerHTML = "Text2";
} else {
box1.classList.add("colorGreen");
box1.classList.remove("colorRed");
box2.innerHTML = "Text1";
}
}
}
</script>
It works, when I use only one div.
Then I can use 'this', instead of the 'box1' variable, to addres the right element.
But if I replace 'box1' with 'this' its still the text div that changes.
(I know it's obvious that this is happening, but I'm lost)

With a few small tweaks, this can be written a lot more cleanly:
// Capture click event for parent container, .toggle-set
for (const ele of document.querySelectorAll(".toggle-set")) {
ele.addEventListener("click", function() {
// Grab text and color elements
const textToggle = ele.querySelector(".toggle-text");
const colorToggle = ele.querySelector(".toggle-color");
// Toggle text
// NOTE: This could use further refinement with regex or something similar to strip whitespace before comparison
textToggle.textContent = textToggle.textContent == "Text1" ? "Text2" : "Text1";
// Toggle css classes
colorToggle.classList.toggle("colorGreen");
colorToggle.classList.toggle("colorRed");
});
}
.colorGreen { background-color: green; }
.colorRed { background-color: red; }
<div class="toggle-set">
<div class="toggle-text">Text1</div>
<div class="toggle-color colorGreen">
O
</div>
</div>
<div class="toggle-set">
<div class="toggle-text">Text1</div>
<div class="toggle-color colorGreen">
O
</div>
</div>

Your code is so confused
You were right for the this option.
you can do with simple onclick function :
function change(el){
box1 = el.querySelector('.box1');
box2 = el.querySelector('.box2');
if (box1.classList.contains("colorGreen")) {
box1.classList.add("colorRed");
box1.classList.remove("colorGreen");
box2.innerHTML = "Text2";
} else {
box1.classList.add("colorGreen");
box1.classList.remove("colorRed");
box2.innerHTML = "Text1";
}
}
<style>
.colorGreen {
background-color: green;
}
.colorRed {
background-color: red;
}
</style>
<div onclick="change(this)">
<div class="box2">Text1</div>
<div class="box1 colorGreen">O</div>
</div>
<div onclick="change(this)">
<div class="box2">Text1</div>
<div class="box1 colorGreen">O</div>
</div>
<div onclick="change(this)">
<div class="box2">Text1</div>
<div class="box1 colorGreen">O</div>
</div>

I think following code snippet would help you to get your desired result
let box1 = document.querySelectorAll(".box1");
let box2 = document.querySelectorAll(".box2");
box1.forEach((b1,i) => {
b1.addEventListener("click",(ev) => {
ev.target.classList.toggle("colorGreen");
ev.target.classList.toggle("colorRed");
console.log(box2[i]);
if(ev.target.classList.contains("colorGreen")){
box2[i].textContent = "Text1";
}else{
box2[i].textContent = "Text2"
}
})
})

Related

How do I wrap adjacent elements of the same class using Javascript (no jQuery)

Everywhere I looked, it seemed that this problem has only been solved using jQuery, which I'm trying to remove completely from my project.
Here's the HTML:
<div class="codeblock"></div>
<div class="codeblock"></div>
<div class="codeblock"></div>
<p></p>
<div class="codeblock"></div>
<div class="codeblock"></div>
<p></p>
<div class="codeblock"></div>
desired result:
<div class="contentBox">
<div class="codeblock"></div>
<div class="codeblock"></div>
<div class="codeblock"></div>
</div>
<p></p>
<div class="contentBox">
<div class="codeblock"></div>
<div class="codeblock"></div>
</div>
<p></p>
<div class="contentBox">
<div class="codeblock"></div>
</div>
And here's how this can be done using jQuery, thanks to the many answers I've found on the topic
const e = '.codeblock';
$(e).not(e + '+' + e).each(function () {
$(this).nextUntil(':not(' + e + ')').addBack().wrapAll('<div class="contentBox" />');
});
Is there a way to replicate this same functionality using vanilla Javascript? I've tried using Element.nextElementSibling and checking if the class matches, but this approach wasn't very elegant and resulted in much more code than the jQuery solution.
Shortest version I could come up with:
let firstDivs = document.querySelectorAll('.codeblock:first-child, :not(.codeblock) + .codeblock');
firstDivs.forEach(function(div) {
let wrapper = document.createElement("div");
wrapper.className = 'wrapper';
div.parentNode.insertBefore(wrapper, div);
while(div.nextElementSibling && div.nextElementSibling.className == 'codeblock') {
wrapper.appendChild(div.nextElementSibling);
}
wrapper.insertBefore(div, wrapper.firstChild);
});
First, select the first .codeblock element out of each "group" - by selecting the element with that class that is the first child of its parent, and all those that do not have a .codeblock element before them.
For each of those elements, insert a new wrapper div before that element, then loop through the following element siblings, as long as they have that same class - and append those to the wrapper. And then afterwards, insert the first item to the beginning of the group. (If we did it before, the following elements would stop being siblings at this point.)
You could do something like this:
// Find all elements that match the class
document.querySelectorAll(`.${e}`).forEach(
// For each elemnt
elem => {
// If it's not the first of the group, skip it
if (elem.previousElementSibling!==null && elem.previousElementSibling.classList.contains(e)){
return;
}
// Find all adjacent elements with the same class
let o = [elem];
while (o[o.length - 1].nextElementSibling.classList.contains(e)) {
o.push(o[o.length - 1].nextElementSibling);
}
// Create a new wrapper element and give it a proper class
let wrapper = document.createElement('div');
wrapper.classList.add('contentBox');
// Insert the new wrapper immediatly before the group
elem.insertAdjacentElement('beforebegin', wrapper);
// Move the contents of the group to inside the wrapper element
wrapper.replaceChildren(...o);
}
)
It's a bit more code, but you can loop through all div and p, check every element and when matched append it to a new or existing div.codeBlock.
const isTargeted = el => el.classList.contains(`codeblock`);
const createWrap = (beforeEl) => beforeEl.insertAdjacentElement(`beforebegin`,
Object.assign(document.createElement(`div`), {className: `contentBox`}));
const divsAndPs = document.querySelectorAll(`div, p`);
divsAndPs.forEach(
(elem, i, self) => {
if (!i || isTargeted(elem)) {
const wrap = i && self[i-1].closest(`.contentBox`) ||
createWrap(elem);
wrap.appendChild(elem);
}
}
);
.contentBox {
color: green;
}
.contentBox .codeblock {
margin-left: 2rem;
}
.contentBox:before {
content: 'I am the great contentBox, here are my codeblocks:';
color: grey;
}
<div class="codeblock">x</div>
<div class="codeblock">x</div>
<div class="codeblock">x</div>
<p>paragraph</p>
<div class="codeblock">x</div>
<div class="codeblock">x</div>
<p>paragraph</p>
<div class="codeblock">x</div>
This can be a helper function (see also this stackblitz project):
const wrapIt = () => wrapAll(
document.querySelectorAll(`.codeblock, .codeblock + :not(.codeblock)`),
`codeblock`,
Object.assign(document.createElement(`div`), {className: `contentBox`}) );
setTimeout( wrapIt, 1000 );
function wrapAll(elems2Wrap, groupByClass, wrapperElement) {
const wrap = elem =>
elem.classList?.contains(groupByClass) && (elem
.previousElementSibling?.closest(`.${wrapperElement.className}`) ||
elem.insertAdjacentElement(`beforebegin`, wrapperElement.cloneNode())
).appendChild(elem);
elems2Wrap.forEach(wrap);
}
.contentBox {
color: green;
}
.contentBox .codeblock {
margin-left: 2rem;
}
.contentBox:before {
content: 'Wrapped!';
color: grey;
}
<div class="codeblock otherClass">x</div>
<div class="codeblock">x</div>
<div class="codeblock">x</div>
<p>Just a paragraph</p>
<div class="codeblock">x</div>
<div class="codeblock otherClass">x</div>
<p>Just a paragraph</p>
<div class="codeblock">x</div>

Remove/Add parent element if class is/isn't active on another element but keep the children

How would I remove multiple parents but keep in the children elements if an "active" class exists on another element, then add them back in once the "active" class is removed? No jQuery, please. Any help is appreciated.
parentElement = document.querySelectorAll('.parent');
if (document.querySelector(".otherelement").classList.contains("activeclass")) {
parentElement.forEach(parent => {
parent.parentNode.removeChild(parent);
});
}
<div class="otherelement activeclass">
...
</div>
<div class="parent">
<div class="child">c1</div>
</div>
<div class="parent">
<div class="child">c2</div>
</div>
<div class="parent">
<div class="child">c3</div>
</div>
Edit #1
Here's what removes the parents
// this works
if (document.querySelector(".otherelement").classList.contains("activeclass")) {
parentElement.forEach(parent => {
let fragment = document.createDocumentFragment();
while(parent.firstChild) {
fragment.appendChild(parent.firstChild);
}
parent.parentNode.replaceChild(fragment, parent);
});
} else { // this does not
let wrapper = document.createElement('div');
wrapper.forEach(wrap => {
child.parentNode.insertBefore(wrap, child);
wrap.appendChild(child);
});
}
Edit #2 Solution
Thanks to NcXNaV. Here's how to toggle a parent element. This time with an addEventListener :)
addRemoveParent();
let toggleParent = document.getElementById('toggleparent'),
element = document.getElementsByClassName('otherelement');
toggleParent.addEventListener('click', function() {
for(let i = 0; i < element.length; i++) {
if (!element[i].classList.contains('activeclass')) {
element[i].classList.add('activeclass');
}
else element[i].classList.remove('activeclass');
}
addRemoveParent();
});
function addRemoveParent(){
let parentElement = document.querySelectorAll('.parent'),
childElement = document.querySelectorAll('.child');
if (document.querySelector('.otherelement').classList.contains('activeclass')) {
parentElement.forEach(parent => {
let fragment = document.createDocumentFragment();
while(parent.firstChild) {
fragment.appendChild(parent.firstChild);
}
parent.parentNode.replaceChild(fragment, parent);
});
}
else {
childElement.forEach(child => {
let wrapper = document.createElement('div');
wrapper.classList.add('parent');
let parent = child.parentNode;
parent.replaceChild(wrapper, child);
wrapper.appendChild(child);
});
}
}
<div class="otherelement"> ... </div>
<div class="child">c1</div>
<div class="child">c2</div>
<div class="child">c3</div>
<button id="toggleparent">Add/Remove 'Active'</button>
To strip the enclosing divs, set their outerHTML to their innerHTML. Demonstration below. The last enclosing div isn't removed...
const parentElement = document.querySelectorAll('.parent');
const otherElement = document.querySelector(".otherelement");
if (otherElement.classList.contains("activeclass")) {
parentElement.forEach(parent => {
parent.outerHTML = parent.innerHTML
});
}
<div class="otherelement activeclass">
...
</div>
<div class="parent" style="color:red">
<div class="child">c1</div>
</div>
<div class="parent" style="color:red">
<div class="child">c2</div>
</div>
<div class="parent" style="color:red">
<div class="child">c3</div>
</div>
<div class="not_the_parent" style="color:red">
<div class="child">My parent remains, so I remain red</div>
</div>
<div class="otherelement activeclass">
...
</div>
<div class="parent">
<div class="child">c1</div>
</div>
<div class="parent">
<div class="child">c2</div>
</div>
<div class="parent">
<div class="child">c3</div>
</div>
Using DocumentFragment in javascript
EDITED: I've added the code to wrap the child back with parent div.
What this code does:
If "active" class exists on another element, removes outer <div class="parent"> while maintaining the child <div class="child">.
If "active" class does not exists on another element, it wraps <div class="child"> back with its parent <div class="parent">.
I've also added a button which toggles (add/remove) 'activeclass' to otherelement.
Let me know if this solved your problem.
addRemoveParent();
function toggleClass(){
var element = document.getElementsByClassName('otherelement');
for(var i = 0; i < element.length; i++)
{
if (!element[i].classList.contains('activeclass')){
element[i].classList.add('activeclass');
}
else element[i].classList.remove('activeclass');
}
addRemoveParent();
}
function addRemoveParent(){
parentElement = document.querySelectorAll('.parent');
childElement = document.querySelectorAll('.child');
if (document.querySelector(".otherelement").classList.contains("activeclass")) {
parentElement.forEach(parent => {
//parent.parentNode.removeChild(parent);
var fragment = document.createDocumentFragment();
while(parent.firstChild) {
fragment.appendChild(parent.firstChild);
}
parent.parentNode.replaceChild(fragment, parent);
});
}
else {
childElement.forEach(child => {
let wrapper = document.createElement('div');
wrapper.classList.add('parent');
let parent = child.parentNode;
parent.replaceChild(wrapper, child);
wrapper.appendChild(child);
});
}
}
.parent{
color: blue;
font-size: 20px;
}
/*.parent:before{
content: "<div class='parent'>";
}
.parent:after{
content: "</div>";
}*/
<div class="otherelement">
...
</div>
<!-- Wrap Parent -->
<div class="child">c1</div>
<div class="child">c2</div>
<div class="child">c3</div>
<p>Initial State: <b>Not Active</b>. (Wrapped with Parent Div)<br>You can click the button and it will toggle between Active/Not Active.</p>
<button onclick="toggleClass();">Add/Remove 'Active'</button>

move an element from a div to another div

Let's assume that I have this structure
<div class="firstDiv">
<div class="insideDiv"></div>
</div>
<div class="secondDiv"></div>
<div class="thirdDiv"></div>
How can I move the .insideDiv from the .firstDiv to the .thirdDiv but going through the .secondDiv ?
I need just a hint or an idea. Thank you!
In vanilla JS, it works like this:
var moveIt = function() {
var outerDiv = document.getElementsByClassName('insideDiv')[0].parentElement;
var innerDiv = document.getElementsByClassName('insideDiv')[0];
if (outerDiv.nextElementSibling != null) {
outerDiv.nextElementSibling.appendChild(outerDiv.removeChild(innerDiv));
}
}
.firstDiv {
background-color: yellow
}
.secondDiv {
background-color: lightblue
}
.thirdDiv {
background-color: lightpink
}
<div class="container">
<div class="firstDiv">first
<div class="insideDiv">inside div</div>
</div>
<div class="secondDiv">second</div>
<div class="thirdDiv">third</div>
</div>
<button type="button" onclick="moveIt()">Move it!</button>
OPTIONAL: wrap-around in else statement below, this needs a scope to operate in. (set by div-element of class 'container'), to be added to above if statement.
else { outerDiv.parentElement.firstElementChild.appendChild(outerDiv.removeChild(innerDiv));
}
You can see a working example here: codepen: move child-element to nextSibling
If you don't mind using jquery:
<div class="firstDiv">
<div class="insideDiv">InsideBaseball</div>
</div>
<div class="secondDiv">SecondBase</div>
<div class="thirdDiv">ThirdBase</div>
<button id="SwapButton"> Swap! </button>
<script>
document.getElementById("SwapButton").onclick = function () {
var content = $('.insideDiv').html();
var content2 = $('.thirdDiv').html();
$('.thirdDiv').replaceWith(content);
$('.insideDiv').replaceWith(content2);
};
</script>

Add class to an element without an id

I have a list of items:
<div class="crew-item>
<div class="crew-grid"></div>
<div class="crew-detail></div>
</div>
<div class="crew-item>
<div class="crew-grid"></div>
<div class="crew-detail></div>
</div>
<div class="crew-item>
<div class="crew-grid"></div>
<div class="crew-detail></div>
</div>
When I click on a selected 'crew-grid' I'd like to add a class ('active') to its 'crew-item' parent, but I have no idea how to achieve that using vanilla js or jQuery.
The goal is to reveal the 'crew-detail' part, with active class added to its parent.
Like this?:
$('.crew-grid').on('click', function () {
$(this).closest('.crew-item').addClass('active');
});
Basically, starting from the clicked element, get the closest ancestor element which matches that selector. You don't need an id to target an element, just a way to identify it based on the information you have (in this case the clicked element).
If you want to de-activate other elements at the same time:
$('.crew-grid').on('click', function () {
$('.crew-item').removeClass('active');
$(this).closest('.crew-item').addClass('active');
});
Using jQuery :
$('.crew-grid').click(function() {
$(this).closest('.crew-item').addClass('active');
});
Use Document.querySelectorAll()
var crews = document.querySelectorAll('.crew-item');
if (crews) {
for (var i = 0; i < crews.length; i++) {
var grid = crews[i].querySelector('.crew-grid');
grid.addEventListener('click', toggleActive, false);
}
}
function toggleActive() {
var grids = document.querySelectorAll('.crew-item');
for (var i = 0; i < grids.length; i++) {
if (grids[i].classList.contains('active')) {
grids[i].classList.remove('active');
}
}
this.parentNode.classList.add('active');
}
.crew-item.active {
background: #DDD;
}
.crew-grid:hover {
cursor: pointer;
background: #eee;
}
<div class="crew-item active">
<div class="crew-grid">crew-grid</div>
<div class="crew-detail">crew-detail</div>
</div>
<br>
<div class="crew-item">
<div class="crew-grid">crew-grid</div>
<div class="crew-detail">crew-detail</div>
</div>
<br>
<div class="crew-item">
<div class="crew-grid">crew-grid</div>
<div class="crew-detail">crew-detail</div>
</div>

Make div in div clickable with Javascript

Have a problem and can't get to solve it. Tried to use QuerySelectorAll and comma separating with GetElementsByClassName, but that didn't work, so I am wondering how to solve this problem.
I have this HTML:
<div class="area">Test title
<div class="some content" style="display: none">blablbala
<input></input>
</div>
<div class="two">This should be clickable too</div>
</div>
<div class="area">
Test title
<div class="some content">
blablbala
<input></input>
</div>
<div class="two">This should be clickable too</div>
</div>
JS:
function areaCollapse() {
var next = this.querySelector(".content");
if (this.classList.contains("open")) {
next.style.display = "none";
this.classList.remove("open");
} else {
next.style.display = "block";
this.classList.add("open");
}
}
var classname = document.getElementsByClassName("area");
for (var i = 0; i < classname.length; i++) {
classname[i].addEventListener('click', areaCollapse, true);
}
http://jsfiddle.net/1BJK903/nb1ao39k/6/
CSS:
.two {
position: absolute;
top: 0;
}
So now, the div with classname "area" is clickable. I positioned the div with class "two" absolute and now the whole div is clickable, except where this other div is. If you click on the div with classname "two", it doesn't work (it does not collapse or open the contents). How can I make this work, without changing the structure?
One way is using a global handler, where you can handle more than one item by checking its id or class or some other property or attribute.
Below snippet finds the "area" div and pass it as a param to the areaCollapse function. It also check so it is only the two or the area div (colored lime/yellow) that was clicked before calling the areaCollapse.
Also the original code didn't have the "open" class already added to it (the second div group), which mean one need to click twice, so I change the areaCollapse function to check for the display property instead.
function areaCollapse(elem) {
var next = elem.querySelector(".content");
if (next.style.display != "none") {
next.style.display = "none";
} else {
next.style.display = "block";
}
}
window.addEventListener('click', function(e) {
//temp alert to check which element were clicked
//alert(e.target.className);
if (hasClass(e.target,"area")) {
areaCollapse(e.target);
} else {
//delete next line if all children are clickable
if (hasClass(e.target,"two")) {
var el = e.target;
while ((el = el.parentElement) && !hasClass(el,"area"));
if (targetInParent(e.target,el)) {
areaCollapse(el);
}
//delete next line if all children are clickable
}
}
});
function hasClass(elm,cln) {
return (" " + elm.className + " " ).indexOf( " "+cln+" " ) > -1;
}
function targetInParent(trg,pnt) {
return (trg === pnt) ? false : pnt.contains(trg);
}
.area {
background-color: lime;
}
.two {
background-color: yellow;
}
.area:hover, .two:hover {
background-color: green;
}
.some {
background-color: white;
}
.some:hover {
background-color: white;
}
<div class="area">Test title clickable 1
<div class="some content" style="display: none">blablbala NOT clickable 1
</div>
<div class="two">This should be clickable too 1</div>
</div>
<div class="area">Test title clickable 2
<div class="some content">blablbala NOT clickable 2
</div>
<div class="two">This should be clickable too 2</div>
</div>
<div class="other">This should NOT be clickable</div>
You need to find your two elements while you're binding classname, and bind that as well.
var classname = document.getElementsByClassName("area");
for(var i=0; i < classname.length; i++){
classname[i].addEventListener('click', areaCollapse, true);
var twoEl = classname[i].getElementsByClassName("two")[0];
twoEl.addEventListener('click', function(e) { console.log('two clicked'); });
}
If you want to use jQuery:
$('.two').click(function(){
//action here
});

Categories

Resources