Iteration on CSS class array and Toggle class on click - javascript

I have this code
The problem is I need to toggle class on click. I have classes
.col
.col-1 / .col-2 / col-3 etc.
and I need to apply on click to the right .col-1 / col-2 an expand class, so it would like
.col .col-1 .col-1--expand
.col .col-2 .col-2--expand
Before I had this on hover in CSS and it works, but make it on click is little problematic. I searched whole day Javascript and jQuery cases, but I haven't found any right solution to it.
What I learned, I must use forEach function in Javascript, but a solution in jQuery is also what I want.
I tried something like this on my project, but I'm still bad at Javascript :(
if (document.querySelector('.slider__col')) {
const expandElement = element.querySelectorAll('.slider__col')
expandElement.forEach(element => {
element.addEventListener('click', function() {
element.classList.toggle("");
})
})

You can use a regular expression to match element classes and toggle the expand class on that element when clicked.
var matched = this.className.match(/col-\d{1,2}/);
This will find any classes in your element's class attribute that contains col- followed by any numbers up to two digits so you can cater for 1-99.
matched.length && (matched = matched.pop())
.match() returns an array of matches so you can determine if any matches were found and pop the first match off of the array.
var expandClass = matched + '--expand';
Because you're matching, for example, col-1 you can use this string and append --expand to make col-1--expand.
$(this).toggleClass(expandClass);
You can use jQuery's toggleClass to add/remove the expandClass depending on the class's presence. See col-3 for demonstration.
$(document).on('click', '.col', function () {
var matched = this.className.match(/col-\d{1,2}/);
if (matched.length && (matched = matched.pop())) {
var expandClass = matched + '--expand';
$(this).toggleClass(expandClass);
}
});
.col {
background-color: blue;
color: white;
padding: .25rem 1rem;
margin: .25rem 0;
}
.col-1--expand,
.col-2--expand,
.col-3--expand,
.col-4--expand,
.col-5--expand,
.col-6--expand {
background-color: green;
padding-top: 1rem;
padding-bottom: 1rem;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="grid">
<div class="col col-1">1</div>
<div class="col col-2">2</div>
<div class="col col-3 col-3--expand">3</div>
<div class="col col-4">4</div>
<div class="col col-5">5</div>
<div class="col col-6">6</div>
</div>

Welcome to StackOverflow! Since you want a solution in jQuery, let me give you some guidance.
1. The forEach function is in jQuery available as the
.each() method. Making your code:
// Select your classes, for each of them run a function
$('.col-1').each(function() { });
2. To make something happen on a click, jQuery has a .click() method available, it will call a function on click. Making your code:
$('.col-1').each(function() {
// Click method:
$(this).click(function() { } );
});
// Or just do:
$('.col-1').click(function() { });
jQuery does not need a loop and can bind the click method to all classes by itself. There are cases where it still might be useful, but let's keep it basic for now and get your code working!
3. Adding the new CSS class. In jQuery there are many methods you can use for it. In this case you are probably looking for the .toggleClass() method. Other useful ones might me .addClass() and .removeClass(). Making your code:
$('.col-1').click(function() {
$(this).toggleClass("col-1--expand");
});
// Or just add it:
$('.col-1').click(function() {
$(this).addClass("col-1--expand");
});
Done, repeat for other classes where you want to do some magic. Next time try spending a day on the jQuery API Documentation. All the methods are there, with great examples included! Best of luck with it.

Related

How do I target all elements with the same class with getElementByClassName instead of just the first element with that class

I'm very new to JS, and really don't understand a lot of it. Trying to learn as I go.
I'm trying to add some new divs to buttons to style them to look like the rest of the buttons on my site as I cant edit the plugins HTML. I've managed to successfully do this for one button. But it won't work for the other buttons. I've tried to read into it and it looks like because I am using getElementsByClassName its only selecting the first button and not the others.
So I dont know if this is right or not and correct me if it ain't. but I think I need to set up a Node loop? so that getElementsByClassName doesn't just select the first node on the page. However I got no Idea how to set up a node loop and reading about it is just confusing me more.
Can someone help and possibly explain this to me so I can make sense of it for future reference.
Thanks
This is the code I currently have, I just don't know how to make it target all elements with that class rather than just the first element with that class.
var btnSwirls = document.createElement('div');
btnSwirls.id = 'dbtb-button-swirl-wrap';
btnSwirls.className = 'dbtb-button-swirl-wrap';
document.getElementsByClassName("dbtb-add-btn-assets")[0].appendChild(btnSwirls);
const btnSwirls = document.createElement('div');
btnSwirls.id = 'dbtb-button-swirl-wrap';
btnSwirls.className = 'dbtb-button-swirl-wrap';
document.getElementsByClassName("dbtb-add-btn-assets").forEach(element => {
element.appendChild(btnSwirls);
})
learn more about forEach(): https://www.youtube.com/watch?v=SXb5LN_opbA
learn more about arrow functions: https://www.youtube.com/watch?v=h33Srr5J9nY
learn more about var, let, and const: https://www.youtube.com/watch?v=9WIJQDvt4Us
First of all, you are handling an "array" of elements. For that, you'd need a loop to iterate over the array.
you shouldn't be using this
document.getElementsByClassName("dbtb-add-btn-assets")[0] <-- because this part [0] denotes that you are targeting the first element in the array, hence 0, since all arrays start with the index 0; i.e. [0, 1, 2, 3, ...] indices
so for iterating over an array you can either use a (for loop) or a (for of) loop
for loop:
let dbtb_add_btn_assets = document.getElementsByClassName("dbtb-add-btn-assets"); //you are assigning a variable to the array;
for(let i = 0; i < dbtb_add_btn_assets.length; i++) {
var btnSwirls = document.createElement('div');
btnSwirls.id = 'dbtb-button-swirl-wrap';
btnSwirls.className = 'dbtb-button-swirl-wrap'; //create btnswirls per iteration of the loop
dbtb_add_btn_assets[i].appendChild(btnSwirls);
}
the i is the current index of the loop, the i++ part of the for loop
will automatically add 1 to itself upon executing the statement inside
the for loop and ends when i is not less than the dbtb_add_btn_assets
length. Length meaning the number of elements inside the array.
for of:
let dbtb_add_btn_assets = document.querySelectorAll('.dbtb-add-btn-assets'); //personally I'd use querySelectorAll instead of getElementsByCLassName just add . for classes # for ids
for(let dbtb of dbtb_add_btn_assets) { //name whatever variable you want to use
var btnSwirls = document.createElement('div');
btnSwirls.id = 'dbtb-button-swirl-wrap';
btnSwirls.className = 'dbtb-button-swirl-wrap'; //create btnswirls per dbtb
dbtb.appendChild(btnSwirls);
}
the for of loop takes the contents from a specified array and put them into a temporary variable, successfully giving access to the individual content/object, and then automatically iterates over each one as you manipulate it however you like inside the loop.
You need to loop over all the elements. You only access the first of many elements using [0].
There are multiple ways to do this.
Here are two ways to do it using a sample application which just toggles a class (adds/ removes a class) every two seconds.
#1 - Use querySelectorAll() and forEach()
You can use querySelectorAll() to get a NodeList of HTML elements which match the given CSS selector .someClass.
Notice that the CSS selector requires a . before a class name.
window.addEventListener("DOMContentLoaded", e => {
const allElements = document.querySelectorAll(".someClass");
// add/ remove class every 2 seconds
setInterval(() => {
// loop over all elements and add/ remove a class
allElements.forEach(element => {
element.classList.toggle("anotherClass");
});
}, 2000)
})
.someClass {
padding: 20;
background-color: black;
color: white;
margin: 5px;
}
.anotherClass {
border: 2px solid red;
}
<div class="someClass">div 1</div>
<div class="someClass">div 2</div>
<div class="someClass">div 3</div>
<div class="someClass">div 4</div>
#2 - Use getElementsByClassName() and for .. of ... loop
Alternatively you can use getElementsByClassName() which returns a HTMLCollection. You can then use a for ... of ... loop to iterate over all the elements in the collection.
Notice that here for the getElementsByClassName() call we MUST NOT use a . before the class name.
window.addEventListener("DOMContentLoaded", e => {
const allElements = document.getElementsByClassName("someClass");
// loop
setInterval(() => {
// loop over all elements and add/ remove a class
for (const element of allElements) {
element.classList.toggle("anotherClass");
}
}, 2000)
})
.someClass {
padding: 20;
background-color: black;
color: white;
margin: 5px;
}
.anotherClass {
border: 2px solid red;
}
<div class="someClass">div 1</div>
<div class="someClass">div 2</div>
<div class="someClass">div 3</div>
<div class="someClass">div 4</div>
Please note: You should use DOMContentLoaded event so you wait till the HTML document is ready before you try to access the DOM.

How can i minimize the code in a class toggler that targets nav?

I love clean code but I'm zero in javascript. I'd love to do two things to the super easy code below:
function nav_open() {
var myNav = document.getElementById('nav_anim');
if (myNav.className == 'nav_closed') {
myNav.className = 'nav_open';
} else {
myNav.className = 'nav_closed';
}
}
Use getElementsByTagName to target the nav instead of giving it a useless id (in order to use only <nav> instead of <nav id="nav_anim"> in the body. I tried some combos but none of them works.
Get rid of that ugly myNav name, is it mandatory? I know I can change it, but can I remove it? Is it possible to use something like
nav.className=='nav_closed' or even better className=='nav_closed' instead of myNav.className=='nav_closed'
I would suggest keeping the id on your nav, targetting your DOM elements using ids or classes is something that is commonly done and can speed up lookup. Using getElementsByTagName() adds unnecessary complexity and would need to traverse your entire DOM to find your element, so it isn't very efficient, espeicially if you just have one nav element. If you really want to, you could use querySelector to select the first nav item:
const myNav = document.querySelector("nav");
At the end of the day though, if you want to interact with elements in your JavaScript code, you'll need to explicitly grab them (not counting named access on the global window object as it is recommended not to use this).
To further improve your toggle code, you could perform two toggles using DOMTokenList.toggle(), one to hide your first class and one to add your other. Every time you run both toggles, they will add/remove both classes depending on whether they exist or not:
const myNav = document.getElementById("nav_anim");
myNav.classList.toggle('nav_closed');
myNav.classList.toggle('nav_open');
See example below:
const btn = document.getElementById("btn");
btn.addEventListener("click", () => {
btn.classList.toggle("on");
btn.classList.toggle("off");
});
.on {
background-color: lightgreen;
}
.off {
background-color: red;
}
<button id="btn" class="on">Click me</button>
Depending on your code, you may even be able to remove the nav_closed class by targeting your nav element that does not have the nav_open class:
#nav_anim:not(.nav_open) {
/* nav_closed styles */
}
With this setup, you can use just one toggle:
const myNav = document.getElementById("nav_anim");
myNav.classList.toggle('nav_open');
See example below:
const btn = document.getElementById("btn");
btn.addEventListener("click", () => {
btn.classList.toggle("on");
});
.on {
background-color: lightgreen;
}
#btn:not(.on) {
background-color: red;
}
<button id="btn" class="on">Click me</button>
You should be able to do this with a conditional (ternary) operator, like so:
var nav = document.getElementsByTagName("nav")[0];
(nav.classList.contains('nav_open')) ? nav.classList.remove('nav_open') : nav.classList.add('nav_open'));
This is like a 1 line if statement with 3 parameters:
condition ? exprIfTrue : exprIfFalse

event doesn't clone after deleting first element

I'm supposed to clone some elements with Jquery and it works well but when i delete the first element which i'm cloning the others with it, after that the new cloned elements don't have the events which supposed to have!
i tried .clone(true, true) method and it clone event but not after the deleting the first element.
var card = $(".newCard"); //class name of first element
$("#addBtn0").click(function() {
$(".row").append(card.clone(true, true)); //it works well but...
});
$("[class^=btnDelete]").click(function() {
$(this).closest(".newCard").remove(); //it works too but not after deleting first element and creating again
});
I don't know why this is happening, actually every element should have the events even after deleting the first element and recreate.
The problem is the click event is being bound to that first element, and as a result that binding is removed along with the element. When dealing with dynamic elements you should use event delegation by using the .on method on a static element. Such as the document itself.
EDIT: You won't notice any performance issues on a small document like this, but using the document as your event delegator can cause performance issues on larger documents. You can read more about event delegation performance here.
var card = $(".newCard");
$("#addBtn0").click(function() {
$(".row").append(card.clone(true, true));
});
$(document).on('click', '.btnDelete', function() {
$(this).closest(".newCard").remove();
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<button id="addBtn0">Add</button>
<div class="row">
<div class="newCard">Card <button class="btnDelete">Delete</button></div>
</div>
This happens because of
$("[class^=btnDelete]").click(function() {
the above line will target the existing (!!!) element and it's inner button.
Since you're cloning that existing element, you're also cloning the Event bound to it's button on-creation.
Once you delete that card (stored in variable), you're also destroying the Event bound to it.
To fix that issue use .on() with dynamic event delegation:
$(".row").on('click', '[class^=btnDelete]', function() {
var card = $(".newCard"); //class name of first element
$("#addBtn0").click(function() {
$(".row").append(card.clone(true, true));
});
$(".row").on('click', '[class^=btnDelete]', function() {
$(this).closest(".newCard").remove();
});
<div class="row">
<div class="newCard">CARD <button class="btnDelete">DELETE</button></div>
</div>
<button id="addBtn0">ADD</button>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
https://api.jquery.com/on/#direct-and-delegated-events
Other issues and solution
Other issues you have in your code are mainly naming stuff. [class^=btnDelete] is just waiting for you one day adding a style class to that poor button and see your JavaScript fail miserably. Also, why btnAdd0 what's the 0? Why .clone(true, true) at all?
Here's a better rewrite:
const $Cards = $('.Cards');
const $Cards_add = $('.Cards-btn--add');
const $Cards_item = (html) => {
const $self = $('<div/>', {
appendTo: $Cards,
class: `Cards-item`,
html: html || `New Card`,
});
$('<button/>', {
appendTo: $self,
class: `Cards-btn Cards-btn--delete`,
text: `Delete`,
on: {
click() {
$self.remove();
}
},
});
}
let card_id = 0;
$Cards_add.on('click', () => $Cards_item(`This is Card ${++card_id}`));
// Create some dummy cards
$Cards_item(`This is Card ${++card_id}`);
$Cards_item(`This is Card ${++card_id}`);
$Cards_item(`This is Card ${++card_id}`);
/**
* Cards component styles
*/
.Cards {} /* scss extend .row or rather define styles directly */
.Cards-item { padding: 5px; margin: 5px 0; background: #eee; }
.Cards-btn { }
.Cards-btn--add { color: blue; }
.Cards-btn--delete { color: red; }
<div class="Cards"></div>
<button class="Cards-btn Cards-btn--add">ADD</button>
<script src="//code.jquery.com/jquery-3.4.1.js"></script>
$(".row").append(card.clone(true, true));
You are still using the original result of $('.newCard') that does not include any new cards you've added.
$(".row").append($(this).parent().clone(true, true));
this works

How can I attach a class to each element separately?

I have a list of 4 boxes, each have two sections .content-primary-wrapper and .content-secondary-wrapper, inside both these sections is a div called .content-inner. I am running a conditional based on the heights of the content-inner classes to decide which one get's a border-class addd to it.
As I have it now, the function runs, then applies the results to all of the classes, how can I get it to apply it on an element basis? I am trying to get it so it works like this:
Run function, determine heights of first element
determine heights of first element
Apply border class to this element only
Rinse and repeat for the other 3 objects
Here is my script I am using, any ideas how I could loop through these?
var primary_height = $('.content-primary-wrapper .content-inner').height();
var secondary_height = $('.content-secondary-wrapper .content-inner').height();
if ( primary_height >= secondary_height ){
$(this).find('.content-primary-wrapper .content-inner').addClass('add-border-right');
} else {
$(this).find('.content-secondary-wrapper .content-inner').addClass('add-border-left');
}
Is this what you're going for? Loop through all of your boxes, find the .content-inners in each one, and apply your class.
$(".box").each(function() {
var $pri = $(this).find(".content-primary-wrapper .content-inner"),
$sec = $(this).find(".content-secondary-wrapper .content-inner");
if ($pri.height() >= $sec.height()) {
$pri.addClass("add-border-right");
} else {
$sec.addClass("add-border-left");
}
});
.box {
float: left;
clear: both;
}
.add-border-left {
border-left: 1px solid red;
}
.add-border-right {
border-right: 1px solid red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="box">
<div class="content-primary-wrapper">
<div class="content-inner">two<br>lines</div>
</div>
<div class="content-secondary-wrapper">
<div class="content-inner">one</div>
</div>
</div>
<div class="box">
<div class="content-primary-wrapper">
<div class="content-inner">one</div>
</div>
<div class="content-secondary-wrapper">
<div class="content-inner">two<br>lines</div>
</div>
</div>
There are multiple ways you could do this. I would probably run a selector to get the elements that contain the four boxes, then run localized queries for the primary and secondary wrappers on them, like so:
$('.box-container').each(function(index, container) {
var primary_height = $('.content-primary-wrapper .content-inner', container).height();
var secondary_height = $('.content-secondary-wrapper .content-inner', container).height();
// ...
}
Alternatively, if you're sure that they're always going to be paired like this, you could simply do something like:
var primary_elements = $('.content-primary-wrapper .content-inner');
var secondary_elements = $('.content-secondary-wrapper .content-inner');
var i;
for (i = 0; i < primary_elements.length; ++i) {
var primary_height = $(primary_elements[i]).height();
var secondary_height = $(secondary_elements[i]).height();
// ...
}
This version seems a little less robust to me, just in case you start removing one or the other element at some point and they're no longer always paired in the way that this assumes.

Only one Active class and Visible Div at a time

There are a bunch of div elements on the page.
I have a nested div inside of them.
I want to be able to add a class to the clicked element, and .show() the child div.
$('.container').on('click', function(){
$(this).toggleClass('red').children('.insideItem').slideToggle();
});
I can click on it, it drops down.
Click again, it goes away.
So, now I need some method to removeClass() and slideUp() all of the other ones in the event of a click anywhere except the open div. Naturally, I tried something like this:
$('html').on('click', function(){
$('.container').removeClass('red').children('div').slideUp();
});
Well, that just stops the effect from staying in the first place. I've read around on event.Propagation() but I've read that should be avoided if possible.
I'm trying to avoid using any more prebuilt plugins like accordion, as this should be a pretty straightforward thing to accomplish and I'd like to know a simple way to make it work.
Would anyone be able to show a quick example on this fiddle how to resolve this?
Show only one active div, and collapse all others if clicked off
https://jsfiddle.net/4x1Lsryp/
One way to go about it is to update your code with the following:
1) prevent the click on a square from bubbling up to the parent elements
2) make sure to reset the status of all the squares when a new click is made anywhere.
$('.container').on('click', function(){
$this = $(this);
$('.container').not($this).removeClass('red').children('div').slideUp();
$this.toggleClass('red').children('div').slideToggle();
return false;
});
See the updated JSfiddle here: https://jsfiddle.net/pdL0y0xz/
You need to combine your two approaches:
for (var i = 0; i < 10; i++) {
$('#wrap').append("<div class='container'>" + i + "<div class='insideDiv'>Inside Stuff</div></div>");
}
$('.container').on('click', function() {
var hadClassRed = $(this).hasClass('red');
$('.container').removeClass('red').children('div').slideUp();
if (!hadClassRed) {
$(this).toggleClass('red').children('div').slideToggle();
}
});
.container {
width: 200px;
height: 200px;
float: left;
background: gray;
margin: 1em;
}
.insideDiv {
display: none;
}
.red {
background: red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="wrap"></div>

Categories