I'm using the clone method to duplicate a form. I'm adding and removing the active
class on the buttons but, once I clone the form, the duplicate buttons no longer
function because they share the same class as the original. I want the buttons to still
function regardless how many times I clone it. I used jQuery and JavaScript, and I'm
still new to programming. Can you please give me some ideas as to how to solve this.
Thanks in advance fellow developers.
Here is my HTML Code:
<div class="column-bottom phone">
<p class="para_txt">Phone</p>
<div id="main-wrapper">
<div id="wrapper_1" class="parentClass">
<div class="basic_infor">
<p>Select the nature of phone:</p>
<div class="parent_btns">
<button class="func_btns btn_first_4 " >Private</button>
<button class="func_btns btn_second_4" >Work</button>
</div>
</div>
<div class="basic_infor">
<p>Select the type of phone:</p>
<div class="parent_btns">
<button class="func_btns btn_5">Mobile</button>
<button class="func_btns btn_6 ">Telephone</button>
<button class="func_btns btn_7 ">Fax</button>
<button class="func_btns btn_8">Extension</button>
</div>
</div>
<div class="txt_area">
<input type="textarea" placeholder="+27 85 223 5258">
<span onclick="delete_el();">x</span>
</div>
</div>
</div>
<div class="btn_add">
<button class="repl_btns phone_repl" onclick="duplicate();">Add additional</button>
<p>Display on foreman contact list?</p>
<input type="checkbox" id="input_field" name="Phone_contact">
</div>
</div>
Here is my jQuery and JavaScript Code. I selected the class for the first button and
added a active class to it while removing the active class for the second button. I did
the same for the rest of the buttons.
//private btn
$(".btn_first_4").click(function () {
$(this).addClass("is_active");
$(".btn_second_4").removeClass("is_active");
});
//work btn
$(".btn_second_4").click(function () {
$(this).addClass("is_active");
$(".btn_first_4").removeClass("is_active");
});
//Bottom 5 btns
$(".btn_5").click(function () {
$(this).addClass("is_active");
$(".btn_6,.btn_7,.btn_8").removeClass("is_active");
})
$(".btn_6").click(function () {
$(this).addClass("is_active");
$(".btn_5,.btn_7,.btn_8").removeClass("is_active");
})
$(".btn_7").click(function () {
$(this).addClass("is_active");
$(".btn_5,.btn_6,.btn_8").removeClass("is_active");
})
$(".btn_8").click(function () {
$(this).addClass("is_active");
$(".btn_5,.btn_6,.btn_7").removeClass("is_active");
})
/*
Cloning Functions....
I tried to set the id of my new clone to "wrapper_2", but it only works when i clone it
once. I wanted to change the class attribute this way but I realize it wont work as
well. Please advise. Thanks
*/
function duplicate(){
const wrapper = document.getElementById("wrapper_1");
const clone = wrapper.cloneNode(true);
clone.id = "wrapper_2";
const main_wrapper = document.getElementById("main-wrapper");
main_wrapper.appendChild(clone)
}
function delete_el() {
const del_el = document.getElementById("wrapper_2");
del_el.remove();
}
Problems
If you use .cloneNode() any event handlers bound to the original will not carry over to the clone. Fortunately you are using jQuery which has it's own method .clone(). It has the ability to clone and keep event handlers, $(selector).clone(true) to copy with events and $(selector).clone(true, true) for a deep copy with events.
Note: Using .clone() has the side-effect of producing elements with duplicate id attributes, which are supposed to be unique. Where possible, it is recommended to avoid cloning elements with this attribute or using class attributes as identifiers instead.
.clone()|jQuery API Documentation
Do not clone anything with an id, in fact you are using jQuery so don't use id at all. Convert every id to a class, it might feel like a lot of work but in the long run you'll be thankful you did.
Do not use inline event handlers
<button onclick="lame(this)">DON'T DO THIS</button>
This is especially important if you use jQuery which makes event handling incredibly easy to write and very versatile.
let count = 0;
$('output').val(++count);
$('.remove').hide();
$('.select button').on('click', function() {
const $old = $(this).parent().find('.active');
if (!$old.is(this)) {
$old.removeClass('active');
}
$(this).toggleClass('active');
});
$('.clear').on('click', function() {
$(this).parent().find('input').val('');
});
$('.remove').on('click', function() {
$(this).closest('.fields').remove();
let out = $.makeArray($('output'));
count = out.reduce((sum, cur, idx) => {
cur.value = idx + 1;
sum = idx + 1;
return sum;
}, 0);
});
$('.add').on('click', function() {
const $first = $('.fields').first();
const $copy = $first.clone(true, true);
$copy.insertAfter($('.fields').last());
$copy.find('output').val(++count);
$copy.find('.remove').show();
$copy.find('input').val('');
});
html {
font: 300 2ch/1.2 'Segoe UI'
}
fieldset {
min-width: fit-content
}
.fields {
margin-top: 1rem;
}
output {
font-weight: 900;
}
menu {
display: flex;
align-items: center;
margin: 0.5rem 0 0.25rem;
}
button,
input {
display: inline-block;
font: inherit;
font-size: 100%;
}
button {
cursor: pointer;
border: 1.5px ridge lightgrey;
}
.numbers {
display: flex;
align-items: center;
margin: 1rem 0 0.5rem -40px;
}
.clear {
border: 0;
font-size: 1.25rem;
line-height: 1.25;
}
.right {
justify-content: flex-end;
}
.left {
padding-left: 0;
}
.number-3 {
width: 9rem;
}
.number-1 {
width: 3rem;
}
[class^="number-"] {
font-family: Consolas
}
.clear {
border: 0;
background: transparent;
}
label+label {
margin-left: 6px;
}
button:first-of-type {
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
button:nth-of-type(2) {
border-radius: 0;
}
button:last-of-type {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
}
.active {
outline: 2px lightblue solid;
outline-offset: -2px;
}
#foreman {
transform: translate(0, 1.5px)
}
.btn.remove {
display: block;
border-radius: 4px;
float: right;
}
<form id='phone'>
<fieldset class='main'>
<legend>Add Phone Numbers</legend>
<section class='fields'>
<fieldset>
<legend>Phone Number <output value='1'></output></legend>
<button class='btn remove' type='button'>Remove</button>
<label>Phone number is used for:</label>
<menu class='purpose select'>
<button class="btn priv" type='button'>Private</button>
<button class="btn work" type='button'>Work</button>
</menu>
<label>Select the type of phone:</label>
<menu class='type select'>
<button class="btn mob" type='button'>Mobile</button>
<button class="btn tel" type='button'>Telephone</button>
<button class="btn fax" type='button'>Fax</button>
</menu>
<menu class='numbers'>
<form name='numbers'>
<label>Number:  </label>
<input name='phone' class='number-3' type="tel" placeholder="+27 85 223 5258" required>
<label>  Ext.  </label>
<input name='ext' class='number-1' type='number' placeholder='327'>
<button class='btn clear' type='button'>X</button>
</form>
</menu>
</fieldset>
</section>
<fieldset>
<menu class='right'>
<button class='btn cancel' type='button'>Cancel</button>
<button class='btn done'>Done</button>
<button class='btn add' type='button'>Add</button>
</menu>
</fieldset>
<footer>
<menu>
<input id='foreman' name="contact" type="checkbox">
<label for='foreman'>Display on foreman contact list?</label>
</menu>
</footer>
</fieldset>
</form>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
When load page , JS add event click for elements ( elements were created)
When you clone new elements ( those do not add event click) and event click of you not working on those elements
You are using Jquery then i suggest you code same as below :
$(document).on('click', ".btn_first_4", function () {
$(this).addClass("is_active");
$(".btn_second_4").removeClass("is_active");
});
//work btn
$(document).on('click', ".btn_second_4", function () {
$(this).addClass("is_active");
$(".btn_first_4").removeClass("is_active");
});
//Bottom 5 btns
$(document).on('click', ".btn_5", function () {
$(this).addClass("is_active");
$(".btn_6,.btn_7,.btn_8").removeClass("is_active");
})
$(document).on('click', ".btn_6", function () {
$(this).addClass("is_active");
$(".btn_5,.btn_7,.btn_8").removeClass("is_active");
})
$(document).on('click', ".btn_7", function () {
$(this).addClass("is_active");
$(".btn_5,.btn_6,.btn_8").removeClass("is_active");
})
$(document).on('click', ".btn_8", function () {
$(this).addClass("is_active");
$(".btn_5,.btn_6,.btn_7").removeClass("is_active");
})
function duplicate(){
const wrapper = document.getElementById("wrapper_1");
const clone = wrapper.cloneNode(true);
clone.id = "wrapper_2";
const main_wrapper = document.getElementById("main-wrapper");
main_wrapper.appendChild(clone)
}
function delete_el() {
const del_el = document.getElementById("wrapper_2");
del_el.remove();
}
.is_active {
background-color: green;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="column-bottom phone">
<p class="para_txt">Phone</p>
<div id="main-wrapper">
<div id="wrapper_1" class="parentClass">
<div class="basic_infor">
<p>Select the nature of phone:</p>
<div class="parent_btns">
<button class="func_btns btn_first_4 " >Private</button>
<button class="func_btns btn_second_4" >Work</button>
</div>
</div>
<div class="basic_infor">
<p>Select the type of phone:</p>
<div class="parent_btns">
<button class="func_btns btn_5">Mobile</button>
<button class="func_btns btn_6 ">Telephone</button>
<button class="func_btns btn_7 ">Fax</button>
<button class="func_btns btn_8">Extension</button>
</div>
</div>
<div class="txt_area">
<input type="textarea" placeholder="+27 85 223 5258">
<span onclick="delete_el();">x</span>
</div>
</div>
</div>
<div class="btn_add">
<button class="repl_btns phone_repl" onclick="duplicate();">Add additional</button>
<p>Display on foreman contact list?</p>
<input type="checkbox" id="input_field" name="Phone_contact">
</div>
</div>
Related
I am building a game and on a certain condition I need to remove the event listener from a <div> that has just been clicked. I don't want the user to click twice on the same div. I'm trying to use .removeEventListener
Here's my code.
let Divs = document.querySelectorAll(".data")
Divs.forEach((v, k) => {
v.addEventListener("click", (e) => {
clic(e, v, k)
});
});
function clic(e, v, k) {
console.log("CLICK");
Divs[k].removeEventListener("click", (e) => {
clic(e, v, k)
});
}
a game with 9 div! Are you making a tictactoe?:)
little snippet with 9 div, I've added a button in div 6 to remove event for div5
I put the remove in a function, so you can call it with a condition somewhere in your code. You have to pass the name of the div
Array.from(document.querySelectorAll('.grid>div')).forEach(el => {
el.addEventListener('click', divclickadd);
});
function divclickadd(ev) {
console.log(ev.target);
}
document.querySelector('button').addEventListener('click', function(evt) {
evt.stopImmediatePropagation();
console.log(evt.target.className);
divclickremove(evt.target.className);
});
function divclickremove(div) {
const el = document.querySelector('#' + div);
console.log(el);
el.removeEventListener('click', divclickadd);
}
*,
*:before,
*:after {
box-sizing: border-box;
}
html,
body {
overflow: hidden;
margin: 0;
padding: 0;
}
body {
width: 100vw;
height: 100vh;
}
.grid {
display: grid;
grid-template-rows: repeat(3, 1fr);
grid-template-columns: repeat(3, 1fr);
gap: 10px;
width: 100%;
height: 100%;
}
.grid>div {
display: flex;
justify-content: center;
align-items: center;
}
<div class="grid">
<div id="div1" style="background-color: red">div 1</div>
<div id="div2" style="background-color: blue">div 2</div>
<div id="div3" style="background-color: green">div 3</div>
<div id="div4" style="background-color: yellow">div 4</div>
<div id="div5" style="background-color: purple">div 5</div>
<div id="div6" style="background-color: brown">div 6
<button class="div5">remove event 5</button>
</div>
<div id="div7" style="background-color: darkolivegreen">div 7</div>
<div id="div8" style="background-color: orangered">div 8</div>
<div id="div9" style="background-color: cadetblue">div 9</div>
</div>
as I said, I put a button to remove event as example to call the function. You can call the function to remove other way. As the function is written at the moment, you just need to pass the name of the id. Regarding what you are saying it you need to remove event listener of a specific clicked button...
let condition = false;
Array.from(document.querySelectorAll('button.game')).forEach(el => {
el.addEventListener('click', divclickadd);
});
function divclickadd(ev) {
ev.stopImmediatePropagation();
console.log(ev.target);
if (condition) {
divclickremove(ev.target);
}
}
document.querySelector('button.condition').addEventListener('click', function(evt) {
evt.stopImmediatePropagation();
condition = true;
console.log(condition);
});
function divclickremove(el) {
console.log(el);
el.removeEventListener('click', divclickadd);
}
button {
display: block;
}
<button class="game">button event</button>
<button class="game">button event</button>
<button class="game">button event</button>
<button class="game">button event</button>
<button class="game">button event</button>
<button class="game">button event</button>
<button class="game">button event</button>
<button class="game">button event</button>
<button class="game">button event</button>
<br>
<button class="condition">change condition value</button>
if you click on change condition value, next time you click on button event, it'll fire because remove not yet fired. Next next time, it's not remove event has been done.
I have some code that works perfectly for grabbing and clicking a custom upload file button. The thing is I want it to duplicate this form 2 more times to have the user use 3 upload buttons in my overall form. So the user will be able to upload three files. My question is, how do I change my vanilla JavaScript so that instead of grabbing the one element, it grabs multiple elements? Do I use getElementsByClassName? And if I do how do I iterate through each form element individually to upload the file?
Upload form 1:
<input type="file" id="real-file" hidden="hidden" />
<button type="button" id="custom-button">CHOOSE A FILE</button>
<span id="custom-text">No file chosen, yet.</span>
Upload form 2:
<input type="file" id="real-file" hidden="hidden" />
<button type="button" id="custom-button">CHOOSE A FILE</button>
<span id="custom-text">No file chosen, yet.</span>
upload form 3:
<input type="file" id="real-file" hidden="hidden" />
<button type="button" id="custom-button">CHOOSE A FILE</button>
<span id="custom-text">No file chosen, yet.</span>
const realFileBtn = document.getElementById("real-file");
const customBtn = document.getElementById("custom-button");
const customTxt = document.getElementById("custom-text");
customBtn.addEventListener("click", function() {
realFileBtn.click();
});
realFileBtn.addEventListener("change", function() {
if (realFileBtn.value) {
customTxt.innerHTML = realFileBtn.value.match(
/[\/\\]([\w\d\s\.\-\(\)]+)$/
)[1];
} else {
customTxt.innerHTML = "No file chosen, yet.";
}
});
CSS
#custom-button {
padding: 10px;
color: white;
background-color: #009578;
border: 1px solid #000;
border-radius: 5px;
cursor: pointer;
}
#custom-button:hover {
background-color: #00b28f;
}
#custom-text {
margin-left: 10px;
font-family: sans-serif;
color: #aaa;
}
As suggested by #Taplar, you'll need to change IDs to classes and then somehow link the elements (I used data-file-input-id).
One of the possible solutions could be:
HTML:
<input type="file" class="real-file" id="first" hidden="hidden" />
<button type="button" class="custom-button" data-file-input-id="first" >CHOOSE A FILE</button>
<span class="custom-text" data-file-input-id="first">No file chosen, yet.</span>
<input type="file" class="real-file" id="second" hidden="hidden" />
<button type="button" class="custom-button" data-file-input-id="second">CHOOSE A FILE</button>
<span class="custom-text" data-file-input-id="second">No file chosen, yet.</span>
<input type="file" class="real-file" id="third" hidden="hidden" />
<button type="button" class="custom-button" data-file-input-id="third">CHOOSE A FILE</button>
<span class="custom-text" data-file-input-id="third">No file chosen, yet.</span>
JS:
// Grab only buttons with data-file-input-id attribute
const buttons = document.querySelectorAll("button[data-file-input-id]");
const fileInputs = document.querySelectorAll("input[type=file]");
Array.from(buttons).forEach(function (button) {
button.addEventListener("click", function () {
const correspondingInputId = button.getAttribute("data-file-input-id");
document.getElementById(correspondingInputId).click();
});
});
Array.from(fileInputs).forEach(function (fileInput) {
fileInput.addEventListener("change", function () {
const fileInputId = fileInput.id;
const correspondingTextField = document.querySelector(
'span[data-file-input-id="' + fileInputId + '"]'
);
if (fileInput.value) {
correspondingTextField.innerHTML = fileInput.value.match(
/[\/\\]([\w\d\s\.\-\(\)]+)$/
)[1];
} else {
correspondingTextField.innerHTML = "No file chosen, yet.";
}
});
});
CSS:
.custom-button {
padding: 10px;
color: white;
background-color: #009578;
border: 1px solid #000;
border-radius: 5px;
cursor: pointer;
}
.custom-button:hover {
background-color: #00b28f;
}
.custom-text {
margin-left: 10px;
font-family: sans-serif;
color: #aaa;
}
I am trying to step away from jsTree as this is not as much as configurable as having my own custom code. I am making use of Bootstrap to have a somewhat similar functionality as jsTree. I am also stepping away from jQuery (for now), because of debugging reasons.
//Event delegation
function BindEvent(parent, eventType, ele, func) {
var element = document.querySelector(parent);
element.addEventListener(eventType, function(event) {
var possibleTargets = element.querySelectorAll(ele);
var target = event.target;
for (var i = 0, l = possibleTargets.length; i < l; i++) {
var el = target;
var p = possibleTargets[i];
while (el && el !== element) {
if (el === p) {
return func.call(p, event);
}
el = el.parentNode;
}
}
});
}
//Add content after referenced element
function insertAfter(referenceNode, newNode) {
referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling);
}
//Custom function
function LoadSubOptions(ele) {
ele = ele.parentElement.parentElement;
let newEle = document.createElement("div");
newEle.classList.add("row", "flex");
//Generated HTML Content (currently hard coded):
newEle.innerHTML = "<div class='col-xs-1'><div class='tree-border'></div></div><div class='col-xs-11'><div class='row'><div class='col-xs-12'><button class='btn btn-default btn-block btn-lg'>Test</button></div></div></div>";
insertAfter(ele, newEle);
}
//Bind method(s) on button click(s)
BindEvent("#tree-replacement", "click", "button", function(e) {
LoadSubOptions(this);
});
#tree-replacement button {
margin-top: 5px;
}
.tree-border {
border-left: 1px dashed #000;
height: 100%;
margin-left: 15px;
}
.flex {
display: flex;
}
/*Probably not wise to use this method on Bootstrap's grid system: */
#tree-replacement .row.flex>[class*='col-'] {
display: flex;
flex-direction: column;
}
<link href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet" />
<div class="container">
<div id="tree-replacement">
<div class="row">
<div class="col-xs-12">
<button class="btn btn-default btn-block btn-lg">
Option 1
</button>
</div>
</div>
<div class="row">
<div class="col-xs-12">
<button class="btn btn-default btn-block btn-lg">
Option 2
</button>
</div>
</div>
<!--The generated html as example: -->
<!--<div class="row">
<div class="col-xs-1">
<div class="tree-border">
</div>
</div>
<div class="col-xs-11">
<div class="row">
<div class="col-xs-12">
<button class="btn btn-default btn-block btn-lg">
Option 2
</button>
</div>
</div>
</div>
</div>-->
</div>
</div>
JSFiddle
I added a border in a .column-*-1 to allow for some spacing for the border:
The spacing however, I find a bit too much. How could I address this problem? I would like to refrain from styling Bootstrap's grid system (meaning I preferably would not want to touch any styling behind .col-* and .row classes etc.) because this might break the responsiveness or anything else related to Bootstrap.
Edit:
I also noticed that when adding a lot of buttons by just clicking them, the layout of tree will start failing as well. (I am aware this is a different question, so if I need to post another question regarding this problem, please do let me know) Is there a way I could address this so that the element works correctly?
Add this little CSS
#tree-replacement .row.flex > .col-xs-11:nth-child(2):before {
content: ' ';
position: absolute;
left: calc(-100% / 11 + 30px);
top: 2em;
border-top: 1px dashed #000000;
width: calc(100% / 5 - 15px);
}
//Event delegation
function BindEvent(parent, eventType, ele, func) {
var element = document.querySelector(parent);
element.addEventListener(eventType, function(event) {
var possibleTargets = element.querySelectorAll(ele);
var target = event.target;
for (var i = 0, l = possibleTargets.length; i < l; i++) {
var el = target;
var p = possibleTargets[i];
while (el && el !== element) {
if (el === p) {
return func.call(p, event);
}
el = el.parentNode;
}
}
});
}
//Add content after referenced element
function insertAfter(referenceNode, newNode) {
referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling);
}
//Custom function
function LoadSubOptions(ele) {
ele = ele.parentElement.parentElement;
let newEle = document.createElement("div");
newEle.classList.add("row", "flex");
//Generated HTML Content (currently hard coded):
newEle.innerHTML = "<div class='col-xs-1'><div class='tree-border'></div></div><div class='col-xs-11'><div class='row'><div class='col-xs-12'><button class='btn btn-default btn-block btn-lg'>Test</button></div></div></div>";
insertAfter(ele, newEle);
}
//Bind method(s) on button click(s)
BindEvent("#tree-replacement", "click", "button", function(e) {
LoadSubOptions(this);
});
#tree-replacement button {
margin-top: 5px;
}
.tree-border {
border-left: 1px dashed #000;
height: 100%;
margin-left: 15px;
}
.flex {
display: flex;
}
/*Probably not wise to use this method on Bootstrap's grid system: */
#tree-replacement .row.flex>[class*='col-'] {
display: flex;
flex-direction: column;
}
#tree-replacement .row.flex > .col-xs-11:nth-child(2):before {
content: ' ';
position: absolute;
left: calc(-100% / 11 + 30px);
top: 2em;
border-top: 1px dashed #000000;
width: calc(100% / 5 - 15px);
}
<link href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet" />
<div class="container">
<div id="tree-replacement">
<div class="row">
<div class="col-xs-12">
<button class="btn btn-default btn-block btn-lg">
Option 1
</button>
</div>
</div>
<div class="row">
<div class="col-xs-12">
<button class="btn btn-default btn-block btn-lg">
Option 2
</button>
</div>
</div>
<!--The generated html as example: -->
<!--<div class="row">
<div class="col-xs-1">
<div class="tree-border">
</div>
</div>
<div class="col-xs-11">
<div class="row">
<div class="col-xs-12">
<button class="btn btn-default btn-block btn-lg">
Option 2
</button>
</div>
</div>
</div>
</div>-->
</div>
</div>
Here I have used absolute positioning and increased height by 5px which kind of makes it touches the next div element.
Here is the Fiddle Link
and the Code Snippet:
//Event delegation
function BindEvent(parent, eventType, ele, func) {
var element = document.querySelector(parent);
element.addEventListener(eventType, function(event) {
var possibleTargets = element.querySelectorAll(ele);
var target = event.target;
for (var i = 0, l = possibleTargets.length; i < l; i++) {
var el = target;
var p = possibleTargets[i];
while (el && el !== element) {
if (el === p) {
return func.call(p, event);
}
el = el.parentNode;
}
}
});
}
//Add content after referenced element
function insertAfter(referenceNode, newNode) {
referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling);
}
//Custom function
function LoadSubOptions(ele) {
ele = ele.parentElement.parentElement;
let newEle = document.createElement("div");
newEle.classList.add("row", "flex");
//Generated HTML Content (currently hard coded):
newEle.innerHTML = "<div class='col-xs-1'><div class='tree-border'></div></div><div class='col-xs-11'><div class='row'><div class='col-xs-12'><button class='btn btn-default btn-block btn-lg'>Test</button></div></div></div>";
insertAfter(ele, newEle);
}
//Bind method(s) on button click(s)
BindEvent("#tree-replacement", "click", "button", function(e) {
LoadSubOptions(this);
});
#tree-replacement button {
margin-top: 5px;
}
.tree-border {
border-left: 1px dashed #000;
height: calc(100% + 5px);
margin-left: 20px;
position: absolute;
}
.flex {
position: relative;
display: flex;
}
.col-xs-11 .col-xs-12 {
padding-left: 0;
}
/*Probably not wise to use this method on Bootstrap's grid system: */
#tree-replacement .row.flex>[class*='col-'] {
display: flex;
flex-direction: column;
}
<link href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet"/>
<div class="container">
<div id="tree-replacement">
<div class="row">
<div class="col-xs-12">
<button class="btn btn-default btn-block btn-lg">
Option 1
</button>
</div>
</div>
<div class="row">
<div class="col-xs-12">
<button class="btn btn-default btn-block btn-lg">
Option 2
</button>
</div>
</div>
<!--<div class="row">
<div class="col-xs-1">
<div class="tree-border">
</div>
</div>
<div class="col-xs-11">
<div class="row">
<div class="col-xs-12">
<button class="btn btn-default btn-block btn-lg">
Option 2
</button>
</div>
</div>
</div>
</div>-->
</div>
</div>
I have css and js on a button group so that when you click a button from the group it shows as active, and when you click a different button, that button becomes active and the rest are cleared. I have to have 22 of these button groups (I only put 2 here for the sake of space) on my page, when I have just one the code works, but when I add the others everything comes crumbling down, can anyone help! How do use the script multiple times, where the script is applied to every group and doesn't intervene with the others.
function codeAddress() {
var header = document.getElementById("myDIV");
var btns = header.getElementsByClassName("btn");
for (var i = 0; i < btns.length; i++) {
btns[i].addEventListener("click", function() {
var current = document.getElementsByClassName("active");
current[0].className = current[0].className.replace(" active", "");
this.className += " active";
});
}
}
window.onload = codeAddress;
.btn {
background-color: white;
border: 3px solid #0099ff;
color: #0099ff;
cursor: pointer;
float: left;
padding: 10px 16px;
font-size: 18px;
}
.active,
.btn:hover {
background-color: #0099ff;
color: white;
border: 3px solid #0099ff;
cursor: pointer;
}
<div id="myDIV">
<button class="btn active">GQL</button>
<button class="btn">PSV</button>
<button class="btn">WT2</button>
<button class="btn">NBV</button>
<button class="btn">MBD</button>
</div>
<div id="myDIV">
<button class="btn active">GQL</button>
<button class="btn">PSV</button>
<button class="btn">WT2</button>
<button class="btn">NBV</button>
<button class="btn">MBD</button>
</div>
Here give this ago. I believe this is the intended response you expect when clicking button from different groups. Something like radio buttons. As already mentioned an ID can only represent one element not several. Use class instead. So i have changed your id to a class btn-group.
function codeAddress() {
const btnClick = function () {
this.parentNode.getElementsByClassName("active")[0].classList.remove("active");
this.classList.add("active");
};
document.querySelectorAll(".btn-group .btn").forEach(btn => btn.addEventListener('click', btnClick));
// This is the same as above just another way of doing it. use which ever you like
// var btns = document.querySelectorAll(".btn-group .btn");
// for (var i = 0; i < btns.length; i++) {
// btns[i].addEventListener("click", function () {
// this.parentNode.getElementsByClassName("active")[0].classList.remove("active");
// this.classList.add("active");
// });
// }
}
window.onload = codeAddress;
.btn {
background-color: white;
border: 3px solid #0099ff;
color: #0099ff;
cursor: pointer;
float: left;
padding: 10px 16px;
font-size: 18px;
}
.active,
.btn:hover {
background-color: #0099ff;
color: white;
border: 3px solid #0099ff;
cursor: pointer;
}
<div class="btn-group">
<button class="btn active">GQL</button>
<button class="btn">PSV</button>
<button class="btn">WT2</button>
<button class="btn">NBV</button>
<button class="btn">MBD</button>
</div>
<br style="clear:both">
<div class="btn-group">
<button class="btn active">GQL</button>
<button class="btn">PSV</button>
<button class="btn">WT2</button>
<button class="btn">NBV</button>
<button class="btn">MBD</button>
</div>
Here the example what you need https://jsbin.com/bomegabiqo/1/edit?html,js,output
First of all, I want to say that you don't need to have two div with the same id
The second point is that you need to attach eventListener to the parent element, due to best-practice and performance optimization (you can read about it somewhere)
So here is updated version of HTML:
<div id="myGroupButtonsWrapper">
<div id="myDIV">
<button class="btn active">GQL</button>
<button class="btn">PSV</button>
<button class="btn">WT2</button>
<button class="btn">NBV</button>
<button class="btn">MBD</button>
</div>
<div id="myDIVV">
<button class="btn">GQL</button>
<button class="btn">PSV</button>
<button class="btn">WT2</button>
<button class="btn">NBV</button>
<button class="btn">MBD</button>
</div>
</div>
And JavaScript:
function codeAddress() {
function myClickCallback(e) {
if (e.target.className === 'btn') {
var allButtons = document.querySelectorAll("#myGroupButtonsWrapper .btn");
allButtons.forEach((elem) => {
elem.className = elem.className.replace(" active", "");
});
e.target.className += ' active';
} else {
return;
}
}
var header = document.getElementById("myGroupButtonsWrapper");
header.addEventListener("click", myClickCallback);
}
window.onload = codeAddress;
It's not working because you have multiple IDs:
<div id="myDIV">...</div>
<div id="myDIV">...</div>
You can't do this - first, it's invalid HTML, and second, it'll do one of two things with the JS: cause an error, which you can see in the console, or it'll treat header as a NodeList, which is a collection of nodes that match the query selection, which means that it won't work. If you make them all have different IDs (e.g. div1, div2, div3, etc), it'll work if you modify your code to take multiple divs.
The other option is to make a class (e.g. myDIV) and modify your existing JavaScript code to use a class.
Instead of individual buttons, I would recommend using radio buttons for something like this. It already has functionality built in to group together for a selection similar to what you're going for. Then you just have to use built in commands to set the active button or check the values.
https://www.w3schools.com/jsref/prop_radio_checked.asp
https://www.w3schools.com/html/html_forms.asp
it is fairly simple to accomplish this using just 3 steps.
// First step is to create a onBtnClick handler function:
// The btn which was clicked can be accessed from event.target
// And then we can use the build in function classList.toggle to toggle the active class on that btn
const onBtnClickHandler = function (ev){ev.target.classList.toggle("active")};
// Next step is to find all btns, this can be done using the build in querySelectorAll function
const btns = document.querySelectorAll('.btn'); //returns NodeList array
// Last step is to add the eventListener callback function to each btn
btns.forEach(btn => btn.addEventListener('click', onBtnClickHandler));
Hope this helps.
I want to create search-input with drop-down list. The list must close when i have focused or clicked anywhere except search-input.
I added listClose() to "blur"-Listener. But now I can`t catch click-event from drop-down elements. Which is the event-listener I really need? Or I need the another realization?
Please, run snippet below. I tried to write it the most clear.
document.addEventListener("DOMContentLoaded", function() {
var inputElement = document.getElementById("input_word-search");
var listElement = document.getElementById("dd-menu_search-input");
// Input will focused when document is ready.
inputElement.focus();
listOpen = function() {
listElement.classList.add('dd-open');
};
listClose = function() {
listElement.classList.remove('dd-open');
};
inputElement.addEventListener("focus", function(e) {
listOpen();
});
inputElement.addEventListener("blur", function(e) {
listClose();
});
})
.dd-menu {
padding: 8px 0;
background-color: #fff;
border: 1px solid #ccc;
border-radius: 5px;
box-shadow: 0 5px 10px rgba(0, 0, 0, .2);
position: absolute;
display: none;
}
.dd-suggestion {
cursor: pointer;
text-align: left;
padding: 3px 20px;
line-height: 24px;
}
.dd-suggestion:hover {
color: #fff;
background-color: #697981;
}
.dd-open {
display: block;
}
.dd-empty {
display: none;
}
#dd-menu_search-input {
width: 74%;
}
<body>
<div id="input-group">
<input id="input_word-search" class="input_search suggest__field" type="search" autocomplete="off" name="q" placeholder="Seach">
<div id="dd-menu_search-input" class="dd-menu dd-open">
<div class="dd-dataset">
<div class="dd-suggestion" onclick="alert('Click!')">
suggestion-1
</div>
<div class="dd-suggestion" onclick="alert('Click!')">
suggestion-2
</div>
<div class="dd-suggestion" onclick="alert('Click!')">
suggestion-3
</div>
<div class="dd-suggestion" onclick="alert('Click!')">
suggestion-4
</div>
<div class="dd-suggestion" onclick="alert('Click!')">
suggestion-5
</div>
</div>
</div>
</div>
</body>
A solution (or may I say workaround) is to use onmousedown instead of onclick, so it will look like this (Note that I'm also removing the alert() and using a console.log() instead)
<body>
<div id="input-group">
<input id="input_word-search" class="input_search suggest__field" type="search" autocomplete="off" name="q" placeholder="Seach">
<div id="dd-menu_search-input" class="dd-menu dd-open">
<div class="dd-dataset">
<div class="dd-suggestion" onmousedown="console.log('Click!')">
suggestion-1
</div>
<div class="dd-suggestion" onmousedown="console.log('Click!')">
suggestion-2
</div>
<div class="dd-suggestion" onmousedown="console.log('Click!')">
suggestion-3
</div>
<div class="dd-suggestion" onmousedown="console.log('Click!')">
suggestion-4
</div>
<div class="dd-suggestion" onmousedown="console.log('Click!')">
suggestion-5
</div>
</div>
</div>
</div>
</body>
The reason is when you focusout the textbox, it call blur function and the list immediately disapear. So you can't click on the list. You can walkaround by setTimeout to listClose() like this
listClose = function() {
setTimeout(()=>{
listElement.classList.remove('dd-open');
},100)
};
I added boolean-variable that indicates mousedown-event on drop-down list
var mousedownOnNodeCalee = false;
listElement.addEventListener("mousedown", function (e) {
mousedownOnNodeCalee = true;
});
inputElement.addEventListener("blur", function (e) {
if(!mousedownOnNodeCalee) {
listClose();
return;
}
inputElement.focus();
mousedownOnNodeCalee = false;
});