I am working on a TODO app and I am having issues with the event listeners triggering on all of the elements with the same class.
Here is what I have so far:
"Add New Card" btn on page load, when the user clicks a dynamically generated list gets appended to the page.
The list is wrapped in <div>, containing input and "Add" btn to dynamically add list items to the list.
Roadblock:
When the user click on the "Add" btn from the dynamically generated list, it adds list items to all lists.
What I've tried:
I found solutions for triggering the 'click' on the "Add" btn only on the e.target. This doesn't work in my situation as I am not clicking on the element that needs to be added, I am clicking on the button that should add the content from the input field.
I tried this inside the function configuring the 'click' but it was unsuccessful.
e.stopPropagation();
e.stopImmediatePropagation();
I have created a codepen here where you can run the existing code: https://codepen.io/skarmen/pen/qBZYZJQ
I would appreciate any guidance and help here.
Thank you!
1 - Save the button add you clicked:
const add=$(e.target);
2 - pass it into addTask function along side input
addTask(userInput, add)
3 - Now use that button to find first list. So inf parent form, then immediately following sibling ol
$(add).parent().next("ol").append(
4 - You are generating same ids when you generate taskCardContainerthat wont work, use classes:
id="to-do-list" and id="clear-btn" needs to be: class="to-do-list" and class="clear-btn", ids needs to be unique
$(document).ready(function(event) {
/* SUBMIT FUNCTION
- listen to a click event on submit & prevent the default behaviour of the submit event
- validate the userInput and add it to the list by calling (addTask f)
*/
function configureSubmitBehaviour() {
$('.add-new-task-btn').on('click', function(e) {
e.preventDefault()
const add=$(e.target);
const $eventTargetPreviousEl = $(e.target).prev() // e target = btn, so we are looking for the input field before the add task btn
//console.log('e target:', e.target, 'this:', $(this))
//console.log('evenTargetPreviousEl:', $eventTargetPreviousEl)
// store userInput in a variable
let userInput = $($eventTargetPreviousEl).val().trim()
//console.log('userInput:', userInput)
// check if the input is valid
if (userInput !== '') {
addTask(userInput, add)
} else {
alert('Input cannot be empty. Please enter a valid task.')
}
})
}
/* ADD NEW CARD FUNCTION */
function configureAddCardBehaviour() {
$('#add-new-card-btn').on('click', function(e) {
e.preventDefault()
// append the task card container on btn click
addCard()
configureSubmitBehaviour()
})
}
function addCard() {
let $taskCardContainer = $(`
<div class="task-card-container">
<h2 class="editable"></h2>
<!-- Input New Task -->
<form>
<label for="new-task" class="sr-only">New Task</label>
<input class="new-task" type="text" placeholder="New Task" name="new-task"/>
<button type="submit" class="btn add-new-task-btn">Add</button>
</form>
<!-- Task List -->
<ol class="to-do-list" class="to-do-list sortable">
<!-- To do items added dynamically here -->
</ol>
<button class="clear-btn" class="btn clear-list-btn">Clear</button>
</div>
<!-- Task Board Container ENDS -->
`)
$('.main').append($taskCardContainer)
//console.log('addList works')
}
/* ADD TASK FUNCTION
- add the user input to the list
- clear the input field
- function is called upon submit
*/
function addTask(userInput, add) {
let removeItem = '<button id="remove">x</button>'
let checkbox = '<input type="checkbox">'
// append the added element from the list
$(add).parent().next("ol").append(`<li>${checkbox} <span data-id="editable-list-item">${userInput}</span> ${removeItem}</li>`);
$('.new-task').val('')
}
configureAddCardBehaviour()
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/jeditable.js/2.0.17/jquery.jeditable.min.js'></script>
<!-- jQuery UI -->
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
<div class="wrapper">
<div class="main">
<h2>Goals List</h2>
<p><em>App description and instructions go here</em></p>
<button type="submit" id="add-new-card-btn" class="btn">Add New Task Card</button>
</div>
<!-- Main Container ENDS -->
</div>
<!-- Wrapper ENDS -->
EDIT:
Just noticed. You are doing something wrong from start, you have a loop somewhere. All this functions call each other with click events inside them is messed up.
Now when you have multiple cards, when adding item to list that is not the last one, it will add a list in right place but also loop all next inputs and issue an alert if empty. Pretty sure this is not intentional.
Related
please I am trying to create a FAQ like functionality, I have some elements hidden so when I click on a button it opens and hides it. I have been able to do this but I am not getting what I actually want. I might have done something wrong I suppose. So, there are 5 elements with the same className, this will help me target them all and run a for loop to kind of break them apart. However if I click on this button to open one of the element the other ones open.
const openBtn = document.querySelectorAll(".openBtn")
const openContent = document.querySelectorAll(".openContent")
for(btn of openBtn) {
btn.addEventListener('click', () => {
for(content of openContent) {
if (content.classList.contains('hidden')) {
content.classList.remove('hidden');
content.classList.add('flex')
} else {
content.classList.remove('flex');
content.classList.add('hidden')
}
}
})
}
So as you can see, If I click on the chevron icon for just one of the wither About Us, Careers or just any of the 5 every other one opens. How do I fix this ?
Since you aren't going to post even the most general version of your HTML, here is a general outline.
First, each button gets a data attribute for target,then each FAQ div gets an ID attribute that matches the data target attribute.
I attach the click handler to the document and look for openBTN on the clicked element. Then I loop through every OPENED div to close it. Then I get the target data attribute and add the appropriate classes.
document.addEventListener("click", function(e) {
if (e.target.classList.toString().includes("openBtn")) {
let opened = document.querySelectorAll(".openContent.flex");
opened.forEach(function(el) {
el.classList.add("hidden");
el.classList.remove("flex");
});
let target = document.querySelector(e.target.dataset.target)
target.classList.remove("hidden");
target.classList.add("flex");
}
});
.hidden {
display: none
}
<button data-target="#faq1" class="openBtn">OPEN</button>
<div id="faq1" class="openContent hidden">1</div>
<button data-target="#faq2" class="openBtn">OPEN</button>
<div id="faq2" class="openContent hidden">2</div>
<button data-target="#faq3" class="openBtn">OPEN</button>
<div id="faq3" class="openContent hidden">3</div>
<button data-target="#faq4" class="openBtn">OPEN</button>
<div id="faq4" class="openContent hidden">4</div>
I have 2 containers titled learned__container and I want one to pop up depending on which button is pressed. So for example, if I press the Hello button I want the container that has the header hello to pop up as well. If I press the goodbye button I want the container that has the header of goodbye. Is there an easy way to do this without making the id names unique for each button and container? Or do I need to give unique class/id names and select them both and apply an event listener to both buttons?
HTML:
<button id="subheader--link">Hello</button>
<button id="subheader--link">Goodbye</button>
<div class="hide--learn learned__container">
<h1>Hello</h1>
</div>
<div class="hide--learn learned__container">
<h1>Goodbye</h1>
</div>
JS:
const overlay = document.querySelector('.overlay');
const learnContainer = document.querySelector('.learned__container');
const link = document.querySelectorAll('#subheader--link');
link.forEach(link => {
link.addEventListener('click', function (e) {
overlay.classList.remove('hide--overlay');
console.log(link)
console.log(e)
})
})
I have a dynamically generated form with groups of checkboxes representing categories of companies. These eventually get plotted on a dynamic chart (not shown here). Each group of companies is in a div, and each div has a button called Only that should check all the checkboxes in its own category (div) and uncheck all the other checkboxes on the page.
Here's a Fiddle with all the code: https://jsfiddle.net/c2kn78a9/
The Only buttons have this code in them:
// Uncheck all checkboxes outside this div
$(this).closest("div").not(this).find('input[type=checkbox]').prop('checked', false).change();
// Check all checkboxes in this div
$(this).closest("div").find('input[type=checkbox]').prop('checked', true).change();
But it's not working. Any idea how to fix this?
Here's the code for the entire page.
<!-- This button is different than the other buttons -->
<button class="button-text" id="customize-button">Open User Settings</button>
<!-- Placeholder for dynamic form -->
<div id="company-selection-form"></div>
<script type="text/javascript">
function toMachineString(humanString) {
var machineString = humanString.replace(/\s+/g, '-').toLowerCase();
machineString = machineString.replace('&','');
return machineString;
}
// Setup the form
var categories = new Map([
['Tech Giants',['Alphabet','Amazon','Apple','Facebook','Microsoft']],
['Handset Manufacturers',['Apple','Samsung','Motorola','Sony']],
['Semiconductors', ['AMD','Intel','Nvidia']]
// ... more ...
]);
// Build company selection form inputs
let companySelectionHTML = '';
for (let category of categories) {
categoryName = category[0];
categoryList = category[1];
// Setup a div to differentiate each category of companies.
// Will be used for turning on/off categories en masse
companySelectionHTML += `<div id="${toMachineString(categoryName)}">\n`;
// Category heading
companySelectionHTML += `<h4>${categoryName}</h4>\n`;
// Only button
companySelectionHTML += `<button class="only" id="btn-only-${toMachineString(categoryName)}">Only</button>\n`;
categoryList.forEach(companyName => {
companySelectionHTML += `
<label class="checkbox-label">
<input id="x-${toMachineString(companyName)}" class="checkbox" type="checkbox" name="company" value="${companyName}" checked>
<label for="x-${toMachineString(companyName)}">${companyName}</label>
</label>`;
});
companySelectionHTML += '</div>\n</div>\n</div>\n';
}
// Append to DOM
const companySelectionId = document.getElementById('company-selection-form');
companySelectionId.insertAdjacentHTML('beforeend', companySelectionHTML);
// Make the ONLY buttons check all the checkboxes in their div and uncheck everything else
$(document).ready(function() {
$(document).on("click", ".only", function() {
// Uncheck all checkboxes outside this div
$(this).closest("div").not(this).find('input[type=checkbox]').prop('checked', false).change();
// Check all checkboxes in this div
$(this).closest("div").find('input[type=checkbox]').prop('checked', true).change();
});
});
</script>
Thanks!
Your .not(this) is trying to filter out the button element from the single closest div. You need to get all div's on the page and remove the closest div to "this" button.
From your JSFiddle like this:
var temp = $(this).closest("div");
$("div").not(temp).find('input[type=checkbox]').prop('checked', false).change();
OR (to avoid a new variable)
$("div").not($(this).closest("div")).find('input[type=checkbox]').prop('checked', false).change();
Matt G's solution works fine, it deselects all the checkboxes on the page.
I'd suggest to further refine it by first narrowing the selection to only your #company-selection-form
`$("#company-selection-form")
.find("div")
.not($(this)
.closest("div"))
.find('input[type=checkbox]')
.prop('checked', false)
.change();`
Nevertheless, allow me to suggest that you're maybe wasting your time learning this stuff. This programming paradigm is too problematic and anachronistic. It's slow, gets out of hand very quickly, and never brings anything but suffering. Even the slightest update to the UI can force you to revisit (after months sometimes), debug, and rewrite your code. It's never testable, no one would even bother to test this rigorously.
I mean, if your employer holds a gun to your head every day and you have to choose either to do it this way or die, you'd soon choose to die over this ordeal.
I am trying to create a visual element where you can add and remove 2 input fields and a p element, while I found a way to do it, While removing them not in chronological order the last div wont be removed and prints me "cant remove of undefied"
I tried doing it in a few ways, through if function, throgh array methods etc... always the same problem
so the Html code goes this way
<main id="mainBlock">
<div class="divBlock">
<input class="name" type="text">
<input class="workingHours" type="text">
<p class="money"></p>
<button class="deleteButton">delete</button>
</div>
<button id="addButton">add</button>
</main>
and the js:
let addButton = document.getElementById('addButton');
let allDivs = document.getElementsByClassName('divBloc');
addButton.onclick = function(){
let deleteButtons = document.querySelectorAll('button.deleteButton');
let allDeleteButtonsArr = Array.from(allDeleteButtons)
allDeleteButtonsArr.forEach(item => {
item.onclick = function(){
let indexNumber = allDeleteButtonsArr.indexOf(item);
allDivs[indexNumber].remove();
};
});
I think i should explain while the onclick function is related to the create button at first. For the purpose of giving you easier time to read I delete all the part where I create all the new p div and input elements when you click on it. because each time you click on add element there is a new index number I thought it will be better to include it inside the addButton onclick fucntion.
Thanks in advance :)
Since you're dynamically appending nodes, and then you wish to remove them, adding/removing event handlers to the delete button might be very annoying.
A better way is to use event delegation by adding the event listener to the container #mainBlock, and when it's called check if the the delete button was called, and if so remove it's parent.
const item = `
<div class="divBlock">
<input class="name" type="text">
<input class="workingHours" type="text">
<p class="money"></p>
<button class="deleteButton">delete</button>
</div>
`;
const container = document.querySelector('#mainBlock');
const addButton = document.querySelector('#addButton');
addButton.addEventListener('click', () => {
addButton.insertAdjacentHTML('beforebegin', item);
});
container.addEventListener('click', e => {
if(!e.target.matches('.deleteButton')) return;
e.target.parentNode.remove();
});
<main id="mainBlock">
<button id="addButton">add</button>
</main>
I am currently having trouble figuring out exactly how to delete DOM elements.
I know that you can find the element you want by id and then delete it, the thing is in my case that i create the elements with a function each time the 'create' key is pressed. I also add a close button to each element to be able to delete it.
I know that you probably can find this online but i don't even know what to search for.
I want to add a click event to each button to be able to detect which one was pressed and then delete the corresponding element.
This is what I use to create the elements each time the button is pressed.
(I use the RE:DOM library to add the elements)
var count_id = 0;
function addChart(){
const test = el('.row',
el('.col',
el('.card mb-3',[
el('.card-header',[
el('i', {class: 'fa fa-area-chart'}),
el('a', {class: 'btn float-right', id: 'close-chart-'+count_id.toString()},
el('i', {class: 'fa fa-times'}))]),
el('.card-body',
el('#areaTest', {style:'width : 100%;'},
el('.loader'))),
el('.card-footer',
el('.row',
el('.col-lg-2',[
el('h6','Select a date'),
el('div', {class:'input-group date','data-provide':'datapicker'},[
el('.form-control', {type:'text'}),
el('.input-group-addon')])])
))])
));
test.id = count_id.toString();
mount(document.getElementById('charts-container'),test);
count_id++;
console.log(count_id);
}
Relevant HTML section. I am adding everything in this container.
<div class="row">
<div id="charts-container" class="container">
</div>
</div>
Create function delete and pass it id of element which is dynamicly assigned, you can see one example below, element has unique id "el-" and id assigned, that id is passed to function by button click and rest is done by remove() function
<button onclick="delete(123)"> DELETE </button>
<p id="el-123"> i will be deleted </p>
<script>
function delete(id){
document.getElementById("el-" + id).remove();
}
</script>
For your button to be clicked to delete the referred element(with parent i am assuming as corresponding complete row should be deleted), taking the structure as:
<div class="parentDiv">
<span>ele to be deleted</span>
<input class="delBtn" type ="button">Delete</input>
</div>
<script>
$(document).ready(function(){
$(".delBtn").click(function(event){
$(event.target).closest(".parentDiv").remove();
});
});
</script>
You can add click event on the element and pass its reference. And then you can call this function on its click event.
<div #divRef onclick="deleteElement(divRef)">I'm a div</div>
In script:
function deleteElement(element) {
element.parentNode.removeChild(element);
}