I am just stuck on getting everything to work in my todo list. Where I am at the moment is that when a new todo is added I need one button to toggle that it is completed, and the other button to remove the item. It seems as though I am forgetting or not understanding the proper methodology to allow both buttons to be able to target the item. I can only get one to work and not both.
I am still new JavaScript and all this, I am trying to complete this exercise for my coding bootcamp and have been stuck on this todo list for over a week now.
Sorry if I am not formatting this correctly, first time posting here and based on how difficult this is going, I am going to be getting a lot of practice.
todoForm.addEventListener("submit", function(event) {
event.preventDefault();
let newTodos = document.createElement("li")
newTodos.innerText = document.querySelector("#add-el").value
newTodos.classList.add("item")
todoUl.appendChild(newTodos);
let completedBtn = document.createElement("button");
completedBtn.innerText = "✔️"
completedBtn.classList.add("completed-Btn");
completedBtn.type = "button"
newTodos.appendChild(completedBtn)
console.log(completedBtn)
let deleteBtn = document.createElement("button");
deleteBtn.innerText = "🗑️"
deleteBtn.classList.add("delete-Btn");
deleteBtn.type = "button"
newTodos.appendChild(deleteBtn)
console.log(deleteBtn)
newTodos.addEventListener("click", function() {
newTodos.style.textDecoration = "line-through"
})
newTodos.addEventListener("click", function() {
newTodos.removeChild()
})
//can not get the delete button to work
todoForm.reset()
console.log(newTodos)
})
Got some help from my mentor, so with the help of the first answer posted I changed the targets for the event listener after we realized that I was not targeting the parent for the removeChild, nor was I passing in an argument.
deleteBtn.addEventListener("click", function() {
console.log(deleteBtn)
todoUl.removeChild(newTodos)
})
You're just setting the event listeners on the wrong element.
newTodos.addEventListener("click", function() {
newTodos.style.textDecoration = "line-through"
})
newTodos.addEventListener("click", function() {
newTodos.removeChild()
})
You're setting both listeners on the newTodos element instead of the individual buttons, like so:
completedBtn.addEventListener("click", function() {
newTodos.style.textDecoration = "line-through"
})
deleteBtn.addEventListener("click", function() {
newTodos.remove()
})
Also, you need to call newTodos.remove() instead of newTodos.removeChild()
Related
I have a very basic question which I just cant seem to work it out
I just want to basically create a function that will showDetails when I click in an object from a list which is called "pokemonList"
Nothing I do creates a function to what I need to do which is the object having a response after being clicked:
the part of the code in the question is:
.
`function eventListener(button, pokemon) {
button.addEventListener("click", function() {
showDetails(pokemon);
});
}
///////////this following part is incorrect, it needs to make the ''showDetails'' be responsive and ///////////show the pokemon(object) name. Please help me as I'm stuck and I've tried many things
**function showDetails(pokemon) {
eventListener("button");
console.log(pokemon);
}**
///////////////
pokemonList.forEach(function(pokemon) {
let pokemonList = document.querySelector(".pokemon-list");
let listItem = document.createElement("li");
let button = document.createElement("button");
button.innerText = pokemon.name;
button.classList.add("stylez");
listItem.appendChild(button);
pokemonList.appendChild(listItem);
});`
At the moment showDetails calls eventListener which calls showDetails which calls eventListener etc - which is bad.
So ideally you want to
Cache the list element first.
Using event delegation assign one listener to that element that can catch events from its child elements when they're fired and "bubble up" the DOM.
To minimise the adverse affects of repeatedly updating the DOM within your loop create a document fragment which you can append new elements, and which - post loop - you can append to the list element.
showDetails will handle the events from the list listener. In this example it checks to see if the child element that fired the event is a button, and then logs the text content of the button - but obviously you can change that to update a separate element etc.
// Cache the list element, and add a listener to it.
const list = document.querySelector('.pokemon-list');
list.addEventListener('click', showDetails);
const pokemonList = [
{ name: 'Bob' },
{ name: 'Ron' },
{ name: 'Sue' },
{ name: 'Jane' }
];
// Check that the child element that fired
// the event is a button, and log its text content
function showDetails(e) {
if (e.target.matches('button')) {
console.log(e.target.textContent);
}
}
// Create a document fragment
const frag = document.createDocumentFragment();
// Loop over the array and for each object
// add create a list item, and a button, append the button
// to the list item, and then the list item to the fragment
pokemonList.forEach(pokemon => {
const listItem = document.createElement('li');
const button = document.createElement('button');
button.textContent = pokemon.name;
button.classList.add('stylez');
listItem.appendChild(button);
frag.appendChild(listItem);
});
// Finally append the fragment of list items
// to the cached list element
list.appendChild(frag);
.stylez { background-color: lightgreen; }
<ul class="pokemon-list" />
I am fetching a list of posts from an API and displaying them on webpage. Now, there is a Delete button associated with each post which when clicked should remove the post.
index.html
<template id="single-post">
<li class="post-item">
<h2></h2>
<p></p>
<button>DELETE</button>
</li>
</template>
<ul class="posts"></ul>
app.js
const listElement = document.querySelector('.posts');
const postTemplate = document.getElementById('single-post');
const listOfPosts = await sendHttpRequest(
'GET',
'https://jsonplaceholder.typicode.com/posts'
);
// listOfPosts is already in parsed format
for (const post of listOfPosts) {
const postEl = document.importNode(postTemplate.content, true);
postEl.querySelector('h2').textContent = post.title.toUpperCase();
postEl.querySelector('p').textContent = post.body;
listElement.append(postEl);
const btn = postEl.querySelector('button');
console.log(btn, postEl);
btn.addEventListener('click', () => {
postEl.remove();
});
}
The above code only fetches first post only and throws
Uncaught (in promise) TypeError: Cannot read properties of null (reading 'addEventListener')
at HTMLButtonElement.fetchPosts
When I remove the Event Listener, the code works fine.
I guess this is something to do with importNode method since I have done similar things with createElement and they worked fine
EDIT
I did some little experimenting. The JSON post object returned by API also consisted of an id field. So, I basically added that id to each button that was being created.
Another thing is I used event delegation to remove() the li whose button is clicked.
And very surprisingly It works
const listElement = document.querySelector('.posts');
const postTemplate = document.getElementById('single-post');
const listOfPosts = await sendHttpRequest(
'GET',
'https://jsonplaceholder.typicode.com/posts'
);
// listOfPosts is already in parsed format
for (const post of listOfPosts) {
const postEl = document.importNode(postTemplate.content, true);
postEl.querySelector('h2').textContent = post.title.toUpperCase();
postEl.querySelector('p').textContent = post.body;
postEl.querySelector('button').id = post.id; // HERE
listElement.append(postEl);
}
// delete the li element
listElement.addEventListener('click', (event) => {
if(event.target.tagName === 'BUTTON') {
console.log(event.target);
event.target.parentElement.remove();
}
})
when clicked on first list post's DELETE button, it consoles
<button id="1">DELETE</button>
and removes that item.
This bascially proves that the button tag is certainly there since we are able to query select and set its id.
Strangely, when consoled it shows null.
Your code errors out here:
btn.addEventListener('click', () => {
postEl.remove();
});
The error message clarifies that btn is null, which means that postEl.querySelector('button') returned null, which means that there is no button tag inside postEl.
You will need to carefully look at the result of document.importNode(postTemplate.content, true) and see what it contains. You will see that it does not contain a button tag. So, either the button was not added, in which case you will need to adjust importNode, or, the button is not a button tag, but something else, like <input type="button" value="foo"> for example.
I was trying to test a few novice tricks from a project tutorial. Wanted to create a small scale task app and ran into a weird problem. The last document.addEventListener below should theoretically call the closest element with the class name of ".name" should be detected since its in the same parent div with the button. However it is returning NULL. Am I applying the .closest() method wrong?
The event listener detects the button after everytime a task is created. Not sure why it returns NULL when, after creating the task via addTaskButton, the task with the class name of ".name". I even tried to create a data attribute id based off of the taskName itself to see if it'll detect, but still NULL / erroring.
const list = []
const container = document.querySelector('.container');
const itemContainer = document.querySelector('.item');
const addTaskButton = document.querySelector('.add-task');
const taskInput = document.querySelector('#task-name');
function renderTasks(){
itemContainer.innerHTML = ''
list.forEach(task => {
const itemElement = document.createElement('div')
itemElement.innerHTML = `
<div class="name">
${task.taskName}
</div>
<button class="retrieval">Retrieve ID</button>
`
itemElement.dataset.itemName = task.taskName
itemContainer.appendChild(itemElement);
})
}
addTaskButton.addEventListener('click', (e)=>{
e.preventDefault();
list.push({ taskName: taskInput.value})
renderTasks()
})
document.addEventListener('click', (e)=>{
if(e.target.matches('.retrieval')){
const taskName = e.target.closest('.name');
console.log(taskName)
}
})
Ok, I double checked the mdn article it says:
closestElement is the Element which is the closest ancestor of the
selected element. It may be null.
That means it only looks for parents and parents of parents and so on, not 'siblings'.
I want to make a drop-down menu when clicking on itemDropDown, only the first one works, and the rest does not, querySelectorAll gives an error, what am I doing wrong? I have attached jsfidle so you can help, don'ts please pay attention to scss. http://jsfiddle.net/rhy7pv1f/46/
const itemDropDown = document.querySelectorAll('.menu__list-item.drop__down'),
itemList = document.querySelector('.drop__down-list');
itemDropDown.forEach((buttonItem) => {
buttonItem.addEventListener('click', () => {
itemList.classList.toggle('active');
})
})
All your handlers toggle the class of the one itemList you create at the start.
You need to target the relevant list for each button.
const itemDropDown = document.querySelectorAll('.menu__list-item.drop__down');
itemDropDown.forEach((buttonItem) => {
const relatedDropdownList = buttonItem.querySelector('.drop__down-list');
const dropDownLink = buttonItem.querySelector('.menu__list-link');
dropDownLink.addEventListener('click', () => {
relatedDropdownList.classList.toggle('active');
});
})
Updated fiddle at http://jsfiddle.net/gaby/k7b0m95a/5/
I have a Master-Detail ag-grid. One column has checkboxes, (checkboxSelection: true). The details grid have a custom status panel with a button. When the user clicks the button in any specific Detail grid, I don't know how to get the SelectedRows from just that one specific detail grid.
The problem is they might leave multiple details displayed/open, and then looping over each Detail Grid will include results from all open grids. I'm trying to isolate to just the grid where the user clicked the button.
I tried looping through all displayed/open detail grids to get the Detail grid ID. But I don't see any info in this that shows me which one they clicked the button in.
I tried in the button component to see if, in the params, there is anything referencing the detailgrid ID that the button is in, but I did not see anything there either.
This is the button component:
function ClickableStatusBarComponent() {}
ClickableStatusBarComponent.prototype.init = function(params)
{
this.params = params;
this.eGui = document.createElement('div');
this.eGui.className = 'ag-name-value';
this.eButton = document.createElement('button');
this.buttonListener = this.onButtonClicked.bind(this);
this.eButton.addEventListener("click", this.buttonListener);
this.eButton.innerHTML = 'Cancel Selected Records <em class="fas fa-check" aria-hidden="true"></em>';
console.log(this.params);
this.eGui.appendChild(this.eButton);
};
ClickableStatusBarComponent.prototype.getGui = function()
{
return this.eGui;
};
ClickableStatusBarComponent.prototype.destroy = function()
{
this.eButton.removeEventListener("click", this.buttonListener);
};
ClickableStatusBarComponent.prototype.onButtonClicked = function()
{
getSelectedRows();
};
Here is the code to loop through and find all open detail grids:
function getSelectedRows()
{
this.gridOptions.api.forEachDetailGridInfo(function(detailGridApi) {
console.log(detailGridApi.id);
});
I was able to work this out, so thought I'd post my answer in case others have the same issue. I'm not sure I took the best approach, but it's seemingly working as I need.
First, I also tried using a custom detail cell renderer, as per the documentation, but ultimately had the same issue. I was able to retrieve the DetailGridID in the detail onGridReady function--but couldn't figure out how to use that variable elsewhere.
So I went back to the code posted above, and when the button was clicked, I do a jquery .closest to find the nearest div with a row-id attribute (which represents the the DetailgridID), then I use that specific ID to get the rows selected in just that detail grid.
Updated button click code:
ClickableStatusBarComponent.prototype.onButtonClicked = function()
{
getSelectedRows(this);
};
Updated getSelectedRow function:
function getSelectedRows(clickedBtn)
{
var detailGridID = $(clickedBtn.eButton).closest('div[row-id]').attr('row-id');
var detailGridInfo = gridOptions.api.getDetailGridInfo(detailGridID);
const selectedNodes = detailGridInfo.api.getSelectedNodes()
const selectedData = selectedNodes.map( function(node) { return node.data })
const selectedDataStringPresentation = selectedData.map( function(node) {return node.UniqueID}).join(', ')
console.log(selectedDataStringPresentation);
}