So i am making my to-do app and i have encountered this weird problem where my removeToDo function targets wrong items in weird pattern apart from the very first deleted item(which always is being removed just fine). Let's say we have items in the array with id's from 0 to 6 :
Clicked to remove item with ID = 3 - removed item with ID = 3
Clicked to remove item with ID = 4 - removed item with ID = 5
Clicked to remove item with ID = 5 - removed item with ID = 6
Clicked to remove item with ID = 0 - removed item with ID = 0
Clicked to remove item with ID = 2 - removed item with ID = 4
Clicked to remove item with ID = 1 - removed item with ID = 2
Clicked to remove item with ID = 6 - removed item with ID = 1
So it's not really following an obvious pattern (thought it may be something like id + 1 or something but it doesn't seem like it). Also i did exactly the same test as above for the second time to see if it randomizes, it doesn't, the results were exactly the same.
Here is some code
HTML
<body>
<div class='app'>
<ul id='list'>
</ul>
<div class="footer">
<i class="fas fa-plus-circle" aria-hidden="true" id='addButton'></i>
<input type="text" id='itemInput' placeholder="Add a to-do" />
</div>
</div>
<script src="./app.js"></script>
</body>
JS
const list = document.getElementById("list");
const input = document.getElementById("itemInput");
let id;
//get the item from the local storage
let data = localStorage.getItem('TODO');
//check if data is not empty
if(data) {
LIST = JSON.parse(data)
id = LIST.length; // set the list id to the last in the array
loadList(LIST); // load all the items in the array to the UI
} else {
//if data is empty
LIST = [];
id = 0;
}
function loadList(array) {
array.forEach(item => {
addToDo(item.name, item.id, item.done, item.trash);
})
}
function addToDo(toDo, id, done, trash) {
// if trash is true do not execute the code below
if (trash) {return ;}
const DONE = done ? check : uncheck;
const LINE = done ? lineThrough : "";
const text =`
<li class="item">
<i class="far ${DONE}" id='${id}'></i>
<div class="description ${LINE} wrap">${toDo}</div>
<i class="fas fa-trash-alt" id='${id}'></i>
</li>`;
const position = "beforeend";
list.insertAdjacentHTML(position, text);
}
// remove to-do
function removeToDo(element, i) {
let newList = [...LIST]
element.parentNode.parentNode.removeChild(element.parentNode);
i = newList.indexOf(newList[event.target.id]) //<-- i think that is the problem, the indexing is not working as it should, as a result app gets confused ?
alert(i)
//newList[event.target.id].trash = true;
newList.splice(i, 1);
LIST = newList;
console.log(LIST);
return LIST;
}
// click listener for job complete and job delete
list.addEventListener('click', e => {
const element = e.target;
if(e.target.className == "fas fa-trash-alt" ){
removeToDo(element);
}else if(e.target.className == "far fa-circle") {
jobComplete(element);
}else if(e.target.className == "far fa-check-circle"){
jobComplete(element);
}
}
)
//add a task with "enter" key
document.addEventListener("keyup", (event) => {
if(event.keyCode == 13){
const toDo = input.value;
if(toDo) {
addToDo(toDo, id, false, false);
LIST.push(
{
name: toDo,
id: id,
done: false,
trash: false
}
);
localStorage.setItem('TODO', JSON.stringify(LIST));
id++;
input.value = '';
}
}
})
EDIT :
The items deleted in weird pattern are in the LIST array, actual buttons i click are being deleted just fine. I think i didn't explained that well enough
I think it will be best not to consider the id for removing items.
You can consider the value as well.
Instead of splice(i,1), please try using
newList = newList.filter(function( obj ) {
return obj.name !== element.previousElementSibling.innerHTML;
});
The problem is this:
id = LIST.length; // set the list id to the last in the array
The .length property of an array returns the number of items in the array, but array indexes are zero-based. An array with 5 items in it (length === 5), will have its last item index be 4.
The index of the last item in an array is .length -1.
Related
I am trying to add delete button to each item in a list. Adding them works as long as I do not have the delete button.
const newcontainer = document.getElementById("toDoContainers");
//gets number of list items in todolist
//adds list item to list
function deleteitem(paramitem){
var element = document.getElementById(paramitem);
element.remove(paramitem);
}
function addnew(){
let numb = document.getElementById("todolistitems").childElementCount;
var listitem = document.createElement("li");
var reallist = document.getElementById("todolistitems");
var inputvar = document.getElementById("inputfield").value;
var node = document.createTextNode(inputvar);
let numbvar = numb +1;
listitem.appendChild(node);
listitem.setAttribute('id', numbvar);
listitem.addEventListener('onClick', deleteitem(listitem));
reallist.appendChild(listitem);
var inputvar = document.getElementById("inputfield").value="";
// node.appendChild(document.createTextNode(inputvar));
/// document.getElementById("toDoContainers").innerHTML=inputvar;
}
<h1>To Do List</h1>
<div class="container">
<input id="inputfield" type="text"><button id="addToDo" onclick="addnew()">Add</button>
<div class="tO" id="toDoContainers">
<ul id="todolistitems">
</ul>
</div>
</div>
I tried a thing where on each list item created, you can 'onclick'=deleteitem(item). I have tried using queryselector, getelementbyId, and queryselectorall in the delete function.
Adding list items works as long as I do not try adding the delete functionality.
There's a few errors in your code.
You've used 'onClick' instead of 'click' for the click event
Your click event assignment is actually running or interpreting the remove function and attempting to use the return value of the function as the click function.
You've also passed in the list item HTML element as opposed to the ID, which the function requires. This function then tries to use the element itself to find the element and then remove a child element with the same parameter - this will always return undefined.
You need to wrap this in another function that returns the function to be performed on click, and fix that error, as below:
const newcontainer = document.getElementById("toDoContainers");
//gets number of list items in todolist
//adds list item to list
function deleteitem(paramitem) {
var element = document.getElementById("list" + paramitem);
element.remove();
}
function addnew() {
let numb = document.getElementById("todolistitems").childElementCount;
var listitem = document.createElement("li");
var reallist = document.getElementById("todolistitems");
var inputvar = document.getElementById("inputfield").value;
var node = document.createTextNode(inputvar);
let numbvar = numb + 1;
listitem.appendChild(node);
listitem.setAttribute("id", "list" + numbvar);
listitem.addEventListener("click", function () {
deleteitem(numbvar);
});
reallist.appendChild(listitem);
var inputvar = (document.getElementById("inputfield").value = "");
// node.appendChild(document.createTextNode(inputvar));
/// document.getElementById("toDoContainers").innerHTML=inputvar;
}
<h1>To Do List</h1>
<div class="container">
<input id="inputfield" type="text"><button id="addToDo" onclick="addnew()">Add</button>
<div class="tO" id="toDoContainers">
<ul id="todolistitems">
</ul>
</div>
</div>
I have a problem in Javascript.I am adding new list items to the 'ul' elements and this list is empty at first and I do not want to add same values twice. When I write the if statement I get the exception because my list is empty so the result return null.
How can I fix this this problem?
Thank you in advance...
Html Codes
<input type="text" id="the-filter" placeholder="Search For..." />
<div class="list-container">
<ul id="myList"></ul>
<button id="button">Click</button>
Javascript Codes
let newlist = document.querySelector("#myList");
const li = document.getElementsByClassName('list-group-item');
const button = document.getElementById("button");
const button.addEventListener('click' , listName);
const input = document.getElementById("the-filter");
function listName()
const inputVal = input.value;
for (i = 0; i < li.length; i++) {
if ((li[i].innerHTML.toLocaleLowerCase().includes(inputVal) && inputVal!="") ||
(li[i].innerHTML.toUpperCase().includes(inputVal) && inputVal!="")) {
let newItem = document.createElement("li");
li[i].classList.add("list-group-item");
let textnode = document.createTextNode(li[i].innerHTML.toLocaleLowerCase());
newItem.appendChild(textnode);
if((newlist.children[0].innerHTML.toLocaleLowerCase().includes(inputVal))){
newlist.insertBefore(newItem, newlist.childNodes[0]);
}
}
}
}
If I understood the task correct, you need to add items to the list by button click.
If same item exists (case insensitive), then nothing happens.
const list = document.querySelector("#myList");
const button = document.getElementById("button");
button.addEventListener("click", listName);
const input = document.getElementById("the-filter");
function listName() {
const inputVal = input.value;
const [...lis] = document.getElementsByClassName("list-group-item");
const same = lis.find((el) => el.textContent.toLowerCase() === inputVal.toLowerCase());
if (same) {
return;
}
let newItem = document.createElement("li");
newItem.classList.add("list-group-item");
newItem.textContent = inputVal;
list.appendChild(newItem)
}
<input type="text" id="the-filter" placeholder="Search For..." />
<div class="list-container">
<ul id="myList"></ul>
<button id="button">Click</button>
</div>
You're on the right track with event listeners and element creation, but your original code didn't quite seem to match your stated goal.
Here's a solution you might find useful, with some explanatory comments:
// Identifies some DOM elements
const
input = document.getElementById("my-input"),
newList = document.getElementById("my-list"),
items = document.getElementsByClassName('list-group-item'),
button = document.getElementById("my-button");
// Focuses input, and calls addItem on button-click
input.focus();
button.addEventListener('click', addItem);
// Defines the listener function
function addItem(){
// Trims whitespace and sets string to lowerCase
const inputTrimmedLower = input.value.trim().toLocaleLowerCase();
// Clears and refocuses input
input.value = "";
input.focus();
// Ignores empty input
if (!inputTrimmedLower) { return; }
// Ignores value if a list item matches it
for (const li of items) {
const liTrimmedLower = li.textContent.trim().toLocaleLowerCase();
if (liTrimmedLower === inputTrimmedLower) {
console.log(`${inputTrimmedLower} is already listed`);
return;
}
}
// If we got this far, we want to add the new item
let newItem = document.createElement("li");
newItem.classList.add("list-group-item");
newItem.append(inputTrimmedLower); // Keeps lowerCase, as your original code
newList.prepend(newItem); // More modern method than `insertBefore()`
}
<input id="my-input" />
<ul id="my-list"></ul>
<button id="my-button">Click</button>
Im just gonna dump my code on here even though i know you're not supposed to. My todo list works pretty well, the only thing that doesn't work is when i hit the delete button it removes the todo from the user interface but doesn't remove it from the local storage. When i hit refresh the deleted item comes back.
//Select Elements
const clear = document.querySelector(".clear");
const dateElement = document.getElementById("date");
const list = document.getElementById("list");
const input = document.getElementById("input");
//Class names for to-do items
const CHECK = "fa-check-circle";
const UNCHECK = "fa-circle-thin";
const LINE_THROUGH = "lineThrough";
//Variables
let LIST;
let id;
//get item from local strorage
let data = localStorage.getItem("Todo");
//check if data is not emplty
if(data){
LIST = JSON.parse(data);
id = LIST.length;// set the id to the last item in the list
loadList(LIST); //load the list to the user interface
} else{
LIST = [];
id = 0;
};
// load items to user interface
function loadList(array){
array.forEach(function(item){
addToDo(item.name, item.id, item.false, item.delete);
});
};
//clear the local storage
clear.addEventListener("click", function(){
localStorage.clear();
location.reload();
});
//add item to local strorage
//let variable = localStorage.setItem('key');
//localStorage.setItem("Todo", JSON.stringify(LIST));
//show todays date
let options = {weekday : 'long', month:'short', day:'numeric'};
let today = new Date();
dateElement.innerHTML = today.toLocaleDateString("en-US", options);
// create a function that will add the to-do to the list when entered
function addToDo(todo, id, done, trash){
if(trash){return;};
const DONE = done ? CHECK : UNCHECK;
const LINE = done ? LINE_THROUGH : "";
const item = `
<li class="item">
<i class="fa ${DONE} co" job="complete" id="${id}"></i>
<p class="text ${LINE}">${todo}</p>
<i class="fa fa-trash-o de" job="delete" id="${id}"></i>
</li>
`;
const position = "beforeend";
list.insertAdjacentHTML(position, item);
};
//make the input become a todo item when the enter key is pressed
document.addEventListener("keyup", function(event){
if(event.keyCode == 13){
const todo = input.value;
if(todo){
addToDo(todo, id, false, false);
LIST.push({
name : todo,
id : id,
done : false,
trash : false,
});
localStorage.setItem("Todo", JSON.stringify(LIST));
id++;
}
input.value = "";
}
});
//make the todo item change to completed when the user clicks on it
function completeToDo(element){
element.classList.toggle(CHECK);
element.classList.toggle(UNCHECK);
element.parentNode.querySelector(".text").classList.toggle(LINE_THROUGH);
LIST[element.id].done = LIST[element.id].done ? false : true;
};
//remove a todo from the LIST
function removeToDo(element){
element.parentNode.parentNode.removeChild(element.parentNode);
LIST[element.id].trash = true;
};
//target the created items
list.addEventListener("click", function(event){
let element = event.target;// return clicked element inside the list
const elementJOB = element.attributes.job.value;// complete or delete
if(elementJOB == "complete"){
completeToDo(element);
} else if (elementJOB == "delete"){
removeToDo(element);
}
localStorage.setItem("Todo", JSON.stringify(LIST));
});
In removeToDo you set trash to true, but when loading the list you look for the key named delete.
Then, in addToDo you check if the 4th param (trash) if it is truthy, but you pass the value of item.delete which is undefined, so it is falsy.
You need to change this addToDo(item.name, item.id, item.false, item.delete); in this addToDo(item.name, item.id, item.false, item.trash);
I want to remove an item from an array getting the target.id, this app is for a cart, so in other words i want to remove an item from the cart when i click on the bin icon.
<span style={{fontSize:'50px'}}>Basket</span>
<ul style={{listStyle: 'none'}}>
{this.state.clickedNum.map((i) => <li key =
{this.props.gameData.id[i]} value={i}>
{this.props.gameData.gameNames[i]} <br/>
<img src={require('../assets/coins.png')} style={{width:'20px'}}/>
<strong> {this.props.gameData.prices[i]} Gil </strong>
<img src={require('../assets/bin.png')} style={{width:'20px', justifyContent:' flex-end'}} id={this.props.gameData.id[i]} onClick={this.removeClick}/>
<br/>
<hr style={{borderColor:'black'}} />
</li>
</ul>
This is the onClick={this.removeClick} function:
removeClick = (e) => {
e = e.target.id;
let clickedNum = this.state.clickedNum;
let isDisabled = this.state.isDisabled;
console.log("this is i" + e);
clickedNum.splice(e, 1);
isDisabled.splice(e,1);
this.setState({
clickedNum: clickedNum,
isDisabled:isDisabled
})
this.forceUpdate();
}
remove click is binded like this in the constructor function:
this.removeClick = this.removeClick.bind(this);
The problem comes that if you click the bin when there is more than one item in the cart it does not delete the first element and it does not always delete the correct item, however, if there is only one item in the cart it will delete the correct one.
I also note that :
console.log("this is i" + e);
returns the correct value (the id of button in which it was clicked on)
I personally find it stressful to use splice. Why not use filter instead so you'll have something like this.state.clickedNum.filter(num => num.id !== e.target.id)
removeClick = (e) => {
id = e.target.id;
let clickedNum = this.state.clickedNum.filter(num => num.id !==id);
let isDisabled = this.state.filter(num => num.id !==id;
this.setState({
clickedNum: clickedNum,
isDisabled:isDisabled
})
}
I'm trying to make an input that filters a <ul> based on the value in pure JavaScript. It should filter dynamically with the onkeyup by getting the li's and comparing their inner element name with the filter text.
Here is my function:
var searchFunction = function searchFeature (searchString) {
console.log("Is my search feature working?");
//Get the value entered in the search box
var inputString = document.getElementById('inputSearch');
var stringValue = inputString.value;
//Onkeyup we want to filter the content by the string entered in the search box
stringValue.onkeyup = function () {
//toUpperCase to make it case insensitive
var filter = stringValue.toUpperCase();
//loop through all the lis
for (var i = 0; i < eachStudent.length; i++) {
//Do this for all the elements (h3, email, joined-details, date)
var name = eachStudent[i].getElementsByClassName('student-details')[1].innerHTML;
//display all the results where indexOf() returns 0
if (name.toUpperCase().indexOf(filter) == 0)
eachStudent[i].style.display = 'list-item';
else
eachStudent[i].style.display = 'none';
}
}}
My HTML for the search bar:
<div class="student-search">
<input id="inputSearch" placeholder="Type name here.." type="text"> <button>Search</button></div>
My HTML for one of the li's:
<ul class="student-list">
<li class="student-item cf">
<div class="student-details">
<img class="avatar" src="#">
<h3>John Doe</h3>
<span class="email">John.Doe#example.com</span>
</div>
<div class="joined-details">
<span class="date">Joined 01/01/14</span>
</div>
</li>
I would like to filter all the elements (name, email, joined date) based on the value of the input.
Unfortunately, I don't get any errors and it's simply not working.
The function is correctly invoked because the console.log prints...
Here goes the codepen: http://codepen.io/Delano83/pen/qaxxjA?editors=1010
Any help or comments on my code is very appreciated.
There were several issues:
stringValue.onkeyup - stringValue is the value. You can't onkeyup it.
var eachStudent = document.querySelector(".student-item"); will fetch the first thing with student-item class. You need to use querySelectorAll or just use jquery's $('.find-item').
if (name.toUpperCase().indexOf(filter) == 0) indexOf returns 0 if the filter is found at the beginning of the name. 0 as match if found at index 0. You need to check against -1, which means it was not found at all.
Otherwise it more or less worked, good job.
I also added Jquery for me to fix it faster. If you insist on using pure javascript I am sure you will be able to edit it.
Check it out here: http://codepen.io/anon/pen/WGrrXW?editors=1010. Here is the resulting code:
var page = document.querySelector(".page");
var pageHeader = document.querySelector(".page-header");
var studentList = document.querySelector(".student-list");
var eachStudent = document.querySelectorAll(".student-item");
var studentDetails = document.querySelector(".student-details");
//Recreate Search Element in Js
var searchBar = function createBar(searchString) {
var studentSearch = document.createElement("div");
var input = document.createElement("input");
var searchButton = document.createElement("button");
input.type = "text";
var txtNode = document.createTextNode("Search");
if (typeof txtNode == "object") {
searchButton.appendChild(txtNode);
}
studentSearch.setAttribute("class", "student-search");
input.setAttribute("id", "inputSearch");
//append these elements to the page
studentSearch.appendChild(input);
studentSearch.appendChild(searchButton);
input.placeholder = "Type name here..";
return studentSearch;
}
var searchFunction = function searchFeature(searchString) {
console.log("Is my search feature working?");
//Get the value entered in the search box
var inputString = document.getElementById('inputSearch');
var stringValue = inputString.value;
//Onkeyup we want to filter the content by the string entered in the search box
inputString.onkeyup = function() {
//toUpperCase to make it case insensitive
var filter = $(this).val().toUpperCase()
//loop through all the lis
for (var i = 0; i < eachStudent.length; i++) {
//Do this for all the elements (h3, email, joined-details, date)
var name = $(eachStudent[i]).find('h3').text()
console.log(name, filter, name.toUpperCase().indexOf(filter))
//display all the results where indexOf() does not return -1
if (name.toUpperCase().indexOf(filter) != -1)
eachStudent[i].style.display = 'list-item';
else
eachStudent[i].style.display = 'none';
}
}
}
function addElements() {
console.log('Add search bar, trying to anyway...')
pageHeader.appendChild(searchBar());
// page.appendChild(paginationFilter());
onLoad();
}
window.onload = addElements;
window.onLoad = searchFunction;