I have a piece of code that adds an event listener to a number of buttons, when the user clicks a button a class is applied to the button container. How can I restrict this so the user can only select a maximum of three buttons. The code below is working to a point, when you get to three you cannot deselect. Can anyone help me achieve
var blocks = document.querySelectorAll(".block");
var btn = document.querySelectorAll("button");
var total = 0;
for (let i = 0; i < btn.length; i++) {
btn[i].addEventListener("click", function() {
if (total < 3 && blocks[i].classList.contains("active")) {
blocks[i].classList.remove("active");
total--;
} else if (total < 3 && !blocks[i].classList.contains("active")) {
blocks[i].classList.add("active");
total++;
}
});
}
.container{
display:flex;
}
.block{
padding: 50px;
border:1px solid;
max-width:
}
.block.active{
background:grey;
}
<div class="container">
<div class="block">
<button>click</button>
</div>
<div class="block">
<button>click</button>
</div>
<div class="block">
<button>click</button>
</div>
<div class="block">
<button>click</button>
</div>
</div>
this.
You simply need to remove this condition total < 3 && from your first if. The number of selected items is irrelevant if the element is already selected. You just want to de-select it.
var blocks = document.querySelectorAll(".block");
var btn = document.querySelectorAll("button");
var total = 0;
for (let i = 0; i < btn.length; i++) {
btn[i].addEventListener("click", function() {
if (blocks[i].classList.contains("active")) {
blocks[i].classList.remove("active");
total--;
} else if (total < 3 && !blocks[i].classList.contains("active")) {
blocks[i].classList.add("active");
total++;
}
});
}
.container{
display:flex;
}
.block{
padding: 50px;
border:1px solid;
max-width:
}
.block.active{
background:grey;
}
<div class="container">
<div class="block">
<button>click</button>
</div>
<div class="block">
<button>click</button>
</div>
<div class="block">
<button>click</button>
</div>
<div class="block">
<button>click</button>
</div>
</div>
Related
I have three divs and I want that every single time I click on any div, it will be swapped with a second div which is meant to be at the center.
I have tried like this and it doesn't work:
function swapDiv(event, elem) {
elem.parentNode.insertBefore(elem, elem.parentNode.secondChild);
}
<div class="all-div-container">
<div class="div1" onclick="swapDiv(event,this);">
1
</div>
<div class="div2" onclick="swapDiv(event,this);">
2
</div>
<div class="div3" onclick="swapDiv(event,this);">
3
</div>
</div>
1 2 3 and when I click 3 the result must be 1 3 2 from this result and I click on 1 it's must be 3 1 2
function swapDiv(event, elem) {
// get all elements in .all-div-container
const allElements = [...elem.parentElement.children];
// get index of target elem
const targetIndex = allElements.indexOf(elem);
// get center element
const centerElem = allElements[1];
// exit from function if we clicked at center elem
if (elem === centerElem) return;
// move center element
if (targetIndex === 0) {
elem.parentElement.prepend(centerElem)
} else {
elem.parentElement.append(centerElem)
}
}
<div class="all-div-container">
<div class="div1" onclick="swapDiv(event,this);">
1
</div>
<div class="div2" onclick="swapDiv(event,this);">
2
</div>
<div class="div3" onclick="swapDiv(event,this);">
3
</div>
</div>
Consider the following.
$(function() {
$(".all-div-container > div").click(function(event) {
if ($(this).index() == 0) {
return false;
}
var $prev = $(this).prev();
var $self = $(this).detach();
$prev.before($self);
});
});
.all-div-container>div {
height: 2em;
width: 100%;
border: 1px solid #ccc;
border-radius: 3px;
margin-bottom: 3px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="all-div-container">
<div class="div1">1</div>
<div class="div2">2</div>
<div class="div3">3</div>
</div>
You did not clarify what should happen if the first item is clicked. This code will swap the clicked element and the previous element.
I'm trying to have my form show child elements if the parent is visible and I keep getting an "undefined" error with my child element, even though I have it defined. I'm trying to have set this up where:
Q1: Checked responses will show parent elements (divs).
Q2: Depending on this response, it'll show child elements (divs).
Is there a way to do this?
//Next Tab
function next() {
var formTabOne = document.getElementById("stepOne");
formTabOne.classList.add("formTrans");
formTabOne.addEventListener("transitionend", function({
target
}) {
if (target === formTabOne) {
target.classList.add("hidden");
target.classList.remove("formTrans");
document.getElementById("stepTwo").classList.remove("hidden");
}
})
}
//Prev Tab
function prev() {
var formTabTwo = document.getElementById("stepTwo");
formTabTwo.classList.add("formTrans");
formTabTwo.addEventListener("transitionend", function({
target
}) {
if (target === formTabTwo) {
target.classList.add("hidden");
target.classList.remove("formTrans");
document.getElementById("stepOne").classList.remove("hidden");
}
})
}
function process() {
var form = document.myForm;
var biz = document.getElementById("biz");
var career = document.getElementById("career");
var change = document.getElementById("change");
var eq = document.getElementById("eq");
var empathy = document.getElementById("empathy");
var pm = document.getElementById("pm");
var bizMgr = document.getElementsByClassName("bizMgr");
var bizEE = document.getElementsByClassName("bizEE");
//Q1 - Topics
document.querySelectorAll("#chkTopic input").forEach((el) => {
const contentID = el.id.replace("chk", "").toLowerCase()
document.getElementById(contentID).style.display = el.checked ? "block" : "none";
});
//Q2 - Employee Type
var q2value = "";
for (var i = 0; i < form.q2.length; i++) {
var answer = form.q2[i];
if (answer.checked) {
q2value = answer.value;
}
}
if (q2value == "1") {
if (biz.style.display = "block") {
bizMgr.style.display = "block";
bizEE.style.display = "block";
}
} else {
if (biz.style.display = "block") {
document.getElementsByClassName("bizEE").style.display = "block";
}
}
}
html {
scroll-behavior: smooth;
}
#formWrapper {
background-color: #eaeaea;
padding: 20px;
margin-bottom: 40px;
min-height: 300px;
}
#myForm {
width: 70%;
min-height: 280px;
margin: 0 auto;
padding: 20px;
background-color: #fff;
border: 1px solid #dedede;
box-sizing: border-box;
}
.formStep {
opacity: 1;
background: #fff;
}
.formTrans {
visibility: hidden;
opacity: 0;
transition: visibility 0s 200ms, opacity 200ms linear;
}
.hidden {
display: none;
}
#biz, #career, #change, #eq, #empathy, #pm, #pd {
display: none;
width: 100%;
min-height: 200px;
box-sizing: border-box;
margin-bottom: 30px;
border: 1px solid #000;
}
.bizMgr, .bizEE, .careerMgr, .careerEE, .changeMgr, .changeEE, .eqMgr, .eqEE, .empathyMgr, .empathyEE, .pmMgr, .pmEE, .pdMgr, .pdEE {
display: none;
}
<form name="myForm" id="myForm">
<input type="button" value="Skip This" onclick="formSkip();">
<br><br>
<!--Step 1-->
<div id="stepOne" class="formStep">
<b>Select the topic(s) you're interested in:</b><br>
<div id="chkTopic">
<input id="chkBiz" type="checkbox" value="1"><label for="chkBiz">Business Structure/Acumen</label><br>
<input id="chkCareer" type="checkbox" value="2"><label for="chkCareer">Career Development</label><br>
<input id="chkChange" type="checkbox" value="3"><label for="chkChange">Change</label><br>
<input id="chkEQ" type="checkbox" value="4"><label for="chkEQ">Emotional Intelligence</label><br>
<input id="chkEmpathy" type="checkbox" value="5"><label for="chkEmpathy">Empathy</label><br>
<input id="chkPM" type="checkbox" value="6"><label for="chkPM">Performance Management</label><br>
</div>
<br>
<button type="button" id="btnStepOne" onclick="next();">Next</button>
</div>
<!--Step 2-->
<div id="stepTwo" class="formStep hidden">
<b>Are you a people leader?</b><br>
<input type="radio" name="q2" value="0">No<br>
<input type="radio" name="q2" value="1">Yes<br>
<br>
<button type="button" id="btnStepTwo" onclick="prev();">Previous</button>
<input type="button" value="Show Results" onclick="process();">
<input type="reset" value="Start Over" onclick="formReset();">
</div>
</form>
<div id="results">
<div id="biz">
Business Structure/Acumen
<div class="bizMgr">Manager Content</div>
<div class="bizEE">Employee Content</div>
</div>
<div id="career">
Career Development
<div class="careerMgr">Manager Content</div>
<div class="careerEE">Employee Content</div>
</div>
<div id="change">
Change
<div class="changeMgr">Manager Content</div>
<div class="changeEE">Employee Content</div>
</div>
<div id="eq">
Emotional Intelligence
<div class="eqMgr">Manager Content</div>
<div class="eqEE">Employee Content</div>
</div>
<div id="empathy">
Empathy
<div class="empathyMgr">Manager Content</div>
<div class="empathyEE">Employee Content</div>
</div>
<div id="pm">
Performance Management
<div class="pmMgr">Manager Content</div>
<div class="pmEE">Employee Content</div>
</div>
</div>
.getElementsByClassName returns a collection, bizMgr and bizEE are both collections. You have to iterate the collections and set each element to style.display = 'block'. You can't just call xxx.style.display on a javascript collection. You would want to change your code like the following:
if (q2value == "1") {
if (biz.style.display = "block") {
//bizMgr.style.display = "block"; -NO
//bizEE.style.display = "block"; -NO
for(let i = 0; i < bizMgr.length; i++){
bizMgr[i].style.display = "block";
}
for(let j = 0; j < bizEE.length; j++){
bizEE[j].style.display = "block";
}
}
} else {
if (biz.style.display = "block") {
//document.getElementsByClassName("bizEE").style.display = "block"; -NO
for(let j = 0; j < bizEE.length; j++){
bizEE[j].style.display = "block";
}
}
}
I have the following code:
$('button').click(function(e) {
e.preventDefault();
var count = 0;
if (count < 5) {
$(".container .column:nth-child(1)").append('<div class="element"></div>');
count++;
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<button type="button">Add</button>
<div class="container">
<div class="column"></div>
<div class="column"></div>
<div class="column"></div>
<div class="column"></div>
</div>
When I click the button I want to add 4 elements maximum in each column, one by one. For example if I click the button 6 times, the HTML will look like this:
<div class="container">
<div class="column">
<div class="element"></div>
<div class="element"></div>
<div class="element"></div>
<div class="element"></div>
</div>
<div class="column">
<div class="element"></div>
<div class="element"></div>
</div>
<div class="column"></div>
<div class="column"></div>
</div>
This adds 1 element div to the first column, each time I click a button, with a 4 element div limit.
How can I do the same for the next columns?
Bonus Question
Is it possible to also do the opposite when clicking a button to remove elements?
You can target the column div by checking the no of elements in it using the length property.
Additionally, You don't need :nth-child(1) property.
$('.add').click(function(e) {
e.preventDefault();
var count = 0;
if (count < 5) {
$(".container .column").filter(function() {
return $(this).children('.element').length < 4;
}).first()
.append('<div class="element">e</div>');
count++;
}
});
$('.remove').click(function(e) {
e.preventDefault();
$(".container .column .element:last").remove();
});
.column {
border: 1px solid red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<button type="button" class="add">Add</button>
<button type="button" class="remove">Remove</button>
<div class="container">
<div class="column"></div>
<div class="column"></div>
<div class="column"></div>
<div class="column"></div>
</div>
References:
.filter()
.first()
You can try this code:
$('button').click(function(e) {
e.preventDefault();
var columns = $(".container .column");
var added = false;
for (var i = 0; i < columns.length; i++) {
if (columns[i].childNodes.length < 4) {
$(columns[i]).append('<div class="element"></div>');
added = true;
break;
}
}
if (!added) {
console.log('All columns were filled!');
}
});
.container {
display: flex;
flex-direction: row;
justify-content: space-between;
}
.column {
flex: 1;
}
.element {
width: 10px;
height: 10px;
background: red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<button type="button">Add</button>
<div class="container">
<div class="column"></div>
<div class="column"></div>
<div class="column"></div>
<div class="column"></div>
</div>
First mouse click gives me the expected 'x' marker but the next mouse click results in the alert ("Player o: tile has already been selected) even though the grid-item is blank. What am I doing wrong?
In the HTML body, I have created a 3x3 grid, 9 gridItems in total.
$(document).ready(function(){
let grid = $("#grid-container")
let gridItem = $(".grid-item")
function select(){
for (i = 0; i < gridItem.length; i++){
gridItem[i].addEventListener("click", function(){
if ($(this).html() == ""){
$(this).html("x");
nextSelect();
} else {alert ("Player X: tile has already been selected"); select()}
})
}
}
function nextSelect(){
for (i = 0; i < gridItem.length; i++){
gridItem[i].addEventListener("click", function(){
if ($(this).html() == ""){
$(this).html("o");
select();
} else {alert ("Player o: tile has already been selected"); nextSelect()}
})
}
}
select()
});
The addEventListener should be executed only once. Your problem is that you're adding multiple event handlers. A possible version in the snippet bellow.
The idea is that you only need one function and attach it to click event only once per cell, which is done with jQuery .click(callback). To manage the turns you can use a boolean variable (isTurnOfPlayerOne).
$(document).ready(function() {
let isTurnOfPlayerOne = true;
$(".grid-item").click(handleClick);
function handleClick() {
if ($(this).html() == " ") {
let symbol = (isTurnOfPlayerOne ? "x" : "o");
$(this).html(symbol);
} else {
alert("Tile has already been selected");
}
isTurnOfPlayerOne = !isTurnOfPlayerOne;
}
});
.grid-item {
width: 40px;
height: 40px;
display: inline-block;
border: solid 1px #000;
text-align: center;
margin-bottom: 3px;
line-height: 30px;
font-size: 30px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="grid-container">
<div class="grid-item"> </div>
<div class="grid-item"> </div>
<div class="grid-item"> </div>
<br/>
<div class="grid-item"> </div>
<div class="grid-item"> </div>
<div class="grid-item"> </div>
<br/>
<div class="grid-item"> </div>
<div class="grid-item"> </div>
<div class="grid-item"> </div>
</div>
what i am trying to do is make the first 12 elements hidden and show the next 12 elements.
//this is dynamic loaded content
<div class="inner-content">
<div class="front-pro">1</div>
<div class="front-pro">2</div>
<div class="front-pro">3</div>
<div class="front-pro">4</div>
<div class="front-pro">5</div>
<div class="front-pro">6</div>
<div class="front-pro">7</div>
<div class="front-pro">8</div>
<div class="front-pro">9</div>
<div class="front-pro">10</div>
<div class="front-pro">11</div>
<div class="front-pro">12</div>
<div class="front-pro hidden">13</div>
<div class="front-pro hidden">14</div>
..... etc (200 divs more)
</div>
<div onclick="SearchNext();">next</div>
This is my javascript/jquery:
function SearchNext(){
var first = $('.inner-content').children('.front-pro:hidden:first');
first.prevAll(':lt(12)').hide();
first.nextAll(':lt(12)').show();
}
It works one time, after it stops working. (and it skips number 13)
i want to have 12 new elements visible with each Next click and hide the previous.
UPDATE - this is my end result that works perfectly
JSFIDDLE DEMO
Thanks to Alex Char
PHP for creating page numbers, you could do this also with javascript
//$counter is search results
$x = 1;
$Pnumbers = '';
while($x <= ceil($counter/12)) {
if($x == 1){ $ecl = 'bold'; } else{ $ecl = ''; }
$Pnumbers .= ' <span class="number '.$ecl.' numbering" onClick="GoTo('.$x.');">'.$x.'</span> ';
$x++;
}
if($counter > 12){ echo'<div class="page-numbers">
<span class="prev number" onclick="GoTo(\'prev\')">Prev</span>
'.$Pnumbers.'
<span class="next number" onclick="GoTo(\'next\');">Next</span>
</div>'; }
Javascript:
function GoTo(nn) {
var nng = parseInt($('.page-numbers .numbering.bold').text());
if(nn == 'next'){
nn = nng+1;
}if(nn == 'prev'){
nn = nng-1;
}
//get all child elements with class front-pro
//of element with class .inner-content
var childElems = $(".inner-content .front-pro");
var totalpages = Math.ceil(childElems.length/12);
//iterate through the elements
var first = (nn-1)*12;
var last = first+11;
childElems.each(function(i, el) {
//show the elements that match the criteria removing css class
if (i >= first && i <= last) {
$(el).removeClass('hidden');
} else {
//hide rest
$(el).addClass('hidden');
}
});
if(nn > 1){ $('.page-numbers .prev').show(); }else{ $('.page-numbers .prev').hide(); }
if(nn < totalpages){ $('.page-numbers .next').show(); }else{ $('.page-numbers .next').hide(); }
$('.page-numbers .numbering').removeClass('bold');
$('.page-numbers .numbering:eq('+(nn-1)+')').addClass('bold');
}
CSS:
.front-pro.hidden{ display:none !important; }
.prev { display: none; }
.page-numbers .number{
background: #ff0000; }
.page-numbers{ text-align:center; }
.page-numbers .number.bold{ font-weight:bold; background:#000; }
.page-numbers .number:hover{ background:#000; cursor: pointer; }
Make sure that the first 12 divs do not contain the "hidden" class, all the divs that come after should have "hidden" in there class
I change a bit the implementation to support and previous. I use a css class to hide content.
function searchNext() {
$('.inner-content').children('.front-pro:lt(12)').addClass('hidden');
$('.inner-content').children('.front-pro:gt(11)').removeClass('hidden');
$(".next").hide();
$(".prev").show();
}
function searchPrev() {
$('.inner-content').children('.front-pro:lt(12)').removeClass('hidden');
$('.inner-content').children('.front-pro:gt(11)').addClass('hidden');
$(".next").show();
$(".prev").hide();
}
.front-pro.hidden {
display: none;
}
.prev {
display: none;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="inner-content">
<div class="front-pro">1</div>
<div class="front-pro">2</div>
<div class="front-pro">3</div>
<div class="front-pro">4</div>
<div class="front-pro">5</div>
<div class="front-pro">6</div>
<div class="front-pro">7</div>
<div class="front-pro">8</div>
<div class="front-pro">9</div>
<div class="front-pro">10</div>
<div class="front-pro">11</div>
<div class="front-pro">12</div>
<div class="front-pro hidden">13</div>
<div class="front-pro hidden">14</div>
</div>
<div class="next" onclick="searchNext();">next</div>
<div class="prev" onclick="searchPrev();">prev</div>
I create a general solution after your comment with next and previous(I use step 3 for demo purposes but you can use what ever you want):
var pager = (function() {
/*declare private variables*/
var first = 0,
last = 2,
step = 3;
function searchNext() {
//next function
//increasing first and last variables
first += step;
last += step;
pagerHelper();
}
function searchPrev() {
//previous function
//decrease first and last variables
first -= step;
last -= step;
pagerHelper();
}
function pagerHelper() {
//get all child elements with class front-pro
//of element with class .inner-content
var childElems = $(".inner-content .front-pro");
//iterate through the elements
childElems.each(function(i, el) {
//show the elements that match the criteria removing css class
if (i >= first && i <= last) {
$(el).removeClass('hidden');
} else {
//hide rest
$(el).addClass('hidden');
}
});
nextPreviousToggle(childElems.length);
}
function nextPreviousToggle(maxEle) {
//here the code is to show/hide next/previous buttons
if (last >= maxEle) {
$(".next").hide();
} else {
$(".next").show();
}
if (first === 0) {
$(".prev").hide();
} else {
$(".prev").show();
}
}
return {
searchNext: searchNext,
searchPrev: searchPrev
}
})();
.front-pro.hidden {
display: none;
}
.prev {
display: none;
}
.prev:hover,
.next:hover {
text-decoration: underline;
cursor: pointer;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="inner-content">
<div class="front-pro">1</div>
<div class="front-pro">2</div>
<div class="front-pro">3</div>
<div class="front-pro hidden">4</div>
<div class="front-pro hidden">5</div>
<div class="front-pro hidden">6</div>
<div class="front-pro hidden">7</div>
<div class="front-pro hidden">8</div>
<div class="front-pro hidden">9</div>
<div class="front-pro hidden">10</div>
<div class="front-pro hidden">11</div>
<div class="front-pro hidden">12</div>
<div class="front-pro hidden">13</div>
<div class="front-pro hidden">14</div>
</div>
<span class="next" onclick="pager.searchNext();">next</span>
<span class="prev" onclick="pager.searchPrev();">prev</span>
References
:gt()
:lt()
You use the following code to handle any number of divs,
var x = $(".inner-content div").hide();
$("div:contains(next)").click(function() {
var cnt = $(this).data("cnt") || 0;
if((cnt * 12) > x.length){ cnt = 0; }
x.hide().filter(":eq("+ (cnt * 12) + "), :lt(" + ((cnt * 12) + 12) + "):gt(" + (cnt * 12) + ")").show();
$(this).data("cnt", ++cnt);
});
DEMO
Try this instead
$('.inner-content').children().each(function (i, x) {
if (i < 12) // Hide First 12 i.e 0-11
$(x).addClass('hidden');
else if (i < 24) // Show Next 12 i.e 12-23
$(x).removeClass('hidden');
})
Also make sure you have css rule defined as
.hidden {
display: none;
}