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>
Related
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.
I create multiple div's dynamically with Javascript
var cart = document.createElement("div");
cart.classList.add("buy-item");
cart.setAttribute("name", "buy-food");
div.appendChild(cart);
and when i collect all the elements with the "buy-item" class i get the elements as an HTMLCollection but when i want to know when it was clicked nothing happens
var elements = document.getElementsByClassName("buy-item");
console.log(elements)
function addFoodToCart() {
console.log("One of buy-item class itemes was clicked")
};
for (var i = 0; i < elements.length; i++) {
elements[i].addEventListener('click', addFoodToCart);
}
The HTML element looks like this
<div class="food-wrap dynamic-food">
<img class="food-item-img" src="/img/foodItem.png">
<p class="food-title">Csípős</p><p class="food-price">190</p>
<div class="buy-item" name="buy-food"></div>
<div class="tooltip">
<span class="tooltiptext">Csípős szósz</span>
</div>
</div>
tl;dr: Add the event listener to the parent div (the one holding all the other elements that you have/will be creating) and use event.target to know which one was clicked.
Not sure if this helps and if this is your case, but you could be benefited by the understanding of Event Bubbling and Event Delegation in Javascript.
In my example below, run the code and click in each paragraph. The console log will show which element you cliked.
function findTheOneClicked(event) {
console.log(event.target)
}
const mainDiv = document.getElementById("parent");
mainDiv.addEventListener('click', findTheOneClicked);
<div id="parent">
<p>"Test 1"</p>
<p>"Test 2"</p>
<p>"Test 3"</p>
</div>
This is really good when you have an element with many other elements on it and you want to do something with them, or when you want to add an event handler to an element that is not available (not created yet) on your page.
Good reading on this topic:
https://javascript.info/bubbling-and-capturing
I am trying to make it so each child input added have the same event listener as the parent input.
Snippet (also on Codepen):
var main = document.getElementById("main").getElementsByTagName("a");
var button = document.createElement('input');
// Loop over A tags in #main
for (var i = 0; i < main.length; i++) {
// # of A tags
console.log(main.length);
// Event listener per # of A tags
main[i].addEventListener("click",function(e){
// Clone parentElement #main
var node = e.target.parentElement;
var clone = node.cloneNode(true);
// Append to DOM
document.getElementById('main').appendChild(clone);
});
}
<div id="main">
<div class="input__container">
<label>Input</label>
<input placeholder="Placeholder..." class="input" id="" name="" type="text"/>
<a class="btn">+</a>
</div>
</div>
Instead of trying to duplicate the event handler, use a single delegated event handler attached to #main:
var main = document.getElementById("main");
main.addEventListener("click", function(e) {
// Delegated event handler returning early if wrong target
if (!e.target.matches(".btn")) return;
// Clone parentElement .input__container
var node = e.target.parentElement;
var clone = node.cloneNode(true);
// Append to main
this.appendChild(clone);
});
<div id="main">
<div class="input__container">
<label>Input</label>
<input placeholder="Placeholder..." class="input" id="" name="" type="text" />
<a class="btn">+</a>
</div>
</div>
cloneNode method does not copy event listeners.
Cloning a node copies all of its attributes and their values, including intrinsic (in–line) listeners. It does not copy event listeners added using addEventListener() or those assigned to element properties (e.g. node.onclick = fn).
cloneNode description
You're not adding any a elements to the DOM within your for loop, so it stops when it's done with the ones within #main that are there when it runs (there's only one).
You probably want to use event delegation on main instead: You handle the click on main, but then only do something with it if the click passed through an a.btn; see comments:
// Get the element, not the `a` elements within it
var main = document.getElementById("main")
// Listen for clicks on it
main.addEventListener("click", function(e) {
// If this click passed through an `a.btn` within #main (this``)...
var btn = e.target.closest("a.btn");
if (btn && this.contains(btn)) {
// Clone the btn's parentElement, append to #main
var node = btn.parentElement;
var clone = node.cloneNode(true);
main.appendChild(clone);
}
});
<div id="main">
<div class="input__container">
<label>Input</label>
<!-- Note that `id` and `name` are not allowed to be blank; just leave them off -->
<input placeholder="Placeholder..." class="input" type="text"/>
<a class="btn">+</a>
</div>
</div>
I wouldn't say this is necessarily the best way to achieve this, but trying to stick with your general strategy here:
let cloneSelf = function(e) {
var parent = e.target.parentElement;
var clone = parent.cloneNode(true);
// Event listeners are not cloned with "cloneNode" so we have to do it manually.
clone.getElementsByTagName("a")[0].addEventListener("click", cloneSelf);
document.getElementById('main').appendChild(clone);
}
// Get the first link, and add the event listener
var firstLink = document.getElementById("main").getElementsByTagName("a")[0];
firstLink.addEventListener("click", cloneSelf);
<div id="main">
<div class="input__container">
<label>Input</label>
<input placeholder="Placeholder..." class="input" id="" name="" type="text" />
<a class="btn">+</a>
</div>
</div>
I can't edit this code to work in buttons based on text instead of class or make it for both by class name and text.
For example:
<button class="same">plz-click-me</button>
<button class="same">dont-click-me</button>
Now I want code to click on the "click-me" button
This what I used in my code to click by class
var inputs = document.querySelectorAll('.same:not(.hidden_elem)');
Thanks and hope to find answers help me
You'll have to iterate through the possible matching elements and select those whose textContent matches what you want. You can't use jQuery's .contains because the substring click-me is included in don't-click-me:
const matching = Array.prototype.filter.call(
document.querySelectorAll('.same'),
({ textContent }) => textContent === 'click-me'
);
console.log(matching);
<button class="same">click-me</button>
<button class="same">dont-click-me</button>
Note that if the substring of the one you want to select is not included in the elements you don't want to select, you can use .contains:
$('.same:contains("click-this-here")').click(() => console.log('clicked'));
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button class="same">click-this-here</button>
<button class="same">dont-click-me</button>
var buttons = document.querySelectorAll("button");
var clickButton = Array.from(buttons).filter( button => button.textContent === "click-me")[0];
<button class="same">click-me</button>
<button class="same">dont-click-me</button>
For some reason the amount of 1's duplicates every time a category is chosen. If the oneBtn is clicked, I want there to only be a single 1 added to the text field per click. In other words, for each time the category is changed (thisOne or thatOne), another 1 is strung along when the oneBtn is clicked. so if the category is changed twice, the oneBtn will input 11 per click. I would like to understand why this is happening and if it can be remedied in vanilla js.
My guess is that somewhere there is a count of clicks being storing away and maybe that's whats affecting the outcome. If thats true, is there a way to reset this count to zero for each time a catgory is changed to prevent the duplication? I might be way off, i'm just guessing here.
/*GET MEAL CATEGORY ITEMS*/
let thisOne = document.getElementById('thisOne');
let thatOne = document.getElementById('thatOne');
function addOne(x){
/*GET OPTION ITEMS*/
let oneBtn = document.getElementById('oneBtn');
let inputText = document.getElementById('inputText');
/*FUNCTION TO CHANGE CATEGORY HEADER*/
let changeHeader = () => {
let header = document.getElementById('header');
header.innerHTML = x.target.innerHTML;
}; changeHeader();
/*FUNCTION TO ADD 1 TO TEXTFIELD*/
let addInput = (x) => {
inputText.value += x.target.innerHTML;
}
oneBtn.addEventListener('click', addInput);
};
thisOne.addEventListener('click', addOne);
thatOne.addEventListener('click', addOne);
<h1>Choose category</h1>
<button class="category" id="thisOne">thisOne</button>
<button class="category" id="thatOne">thatOne</button>
<br /><br />
<div>
<h3 id="header"></h3>
<input id="inputText" type="textfield" /><br /><br />
<button id="oneBtn">1</button>
</div>
Every time you click on thisOne thatOne you attach another eventListener to the oneBtn. So all the eventListeners are executed one after the other.
Move the eventListener outside of the addOne function and it should work as expected.