Why does my jQuery app work ONLY after page refresh? - javascript

Problem Summary: jQuery todo list app offers 3 functions; add items, edit items, and remove items. I can add items continuously, but cannot edit or remove items continuously. A single item can be edited or removed at a time. A page refresh is required to remove or edit another item. The goal is to have all 3 functions working without having to refresh the page. There are no error messages displayed in the console.
What I have tried: I have attempted to remove event listeners with .off() at the completion of a function and then reinitialize the event listeners after the fact. This does not seem to help or make things worse.
Live Demonstration: codepen.io
jQuery:
function checkTodos() {
// todos in the key of my localStorage
let dataInLocalStorage = localStorage.getItem("todos");
let todos;
// check if it is null or not
if (dataInLocalStorage == null) {
todos = [];
$('#notices').html(`<div class='card'>No list items exist yet. Use the input box on the left to add items.</div>`);
} else {
todos = JSON.parse(dataInLocalStorage);
let html = "";
todos.forEach((todo, index) => {
html += `<div id='item-${index}' class='card' data-index='${index}'>${todo}</div>`;
});
$(".incomplete").empty().append(html);
$('#notices').empty();
}
}
$(document).ready(function() {
checkTodos();
// adding items in todos
$("input").keydown((e) => {
if (e.code === 'Enter' && $("input").val() !== "") {
todo = $("input").val();
let todosData = localStorage.getItem("todos");
if (todosData == null) {
todos = [];
} else {
todos = JSON.parse(todosData);
}
todos.push(todo);
localStorage.setItem("todos", JSON.stringify(todos));
$("input").val("");
}
checkTodos();
});
// list item removal
$('.incomplete > div').click((e) => {
let id = $(e.target).attr('id');
let selector = '#' + id;
let todosData = localStorage.getItem('todos');
let index = $(selector).attr('data-index');
todos = JSON.parse(todosData);
if (e.shiftKey) {
if (confirm("Remove the list item?")) {
todos.splice(index, 1);
localStorage.setItem('todos', JSON.stringify(todos));
checkTodos();
}
}
});
// list item editing
$('.incomplete > div').click((e) => {
let id = $(e.target).attr('id');
let selector = '#' + id;
let k = $(selector).attr('data-index');
let todosData = localStorage.getItem('todos');
todos = JSON.parse(todosData);
if (e.altKey) {
$(selector).attr('contenteditable','true');
$(selector).keydown(function(evt) {
if (evt.code === 'Enter') {
$(selector).removeAttr('contenteditable');
todos[k] = $(selector).html();
localStorage.setItem('todos', JSON.stringify(todos));
checkTodos();
}
});
}
});
});

Currently your event handlers are only registered for items that exist when loading the page (after the first call to checkTodos()). You can use event delegation to also handle events on dynamically added items by replacing
$('.incomplete > div').click((e) => { ... })
$('input').keydown((e) => { ... })
with
$(document).on('click', '.incomplete > div', (e) => { ... })
$(document).on('keydown', 'input', (e) => { ... })
etc.

Related

EventListener in javascript not working on updating DOM

Javascript event Listeners are not working on updating the DOM.
**HTML CODE:
**
<div class="task-list">
</div>
**JAVASCRIPT CODE: **
function showList(num) {
const taskBox = document.querySelector('.task-list');
let listHtml = "";
for(let i =1;i<=num;i++){
listHtml += `
<li class="list-item">Hello ${i}</li>
`;
}
taskBox.innerHTML = listHtml;
}
showList(5);
const listItem = document.querySelectorAll('.list-item');
listItem.forEach((item) => {
item.addEventListener('click', (e) => {
console.log(e.target.innerText);
showList(4);
});
});
With this code event Listener just working once. After that it is not working and not even showing error. So why this is happening. How to udpation of the DOM affecting eventlistener exactly.
I have faced this problem multiple times, I solved this by using onclick() function on each elmeent, but i never got solution of why it is not working in this way.
The reason is that after you invoke showList() again,you have replaced the old elements with new elements,and they do not have event binds,you need to add click event again
const listItem = document.querySelectorAll('.list-item');
listItem.forEach((item) => {
item.addEventListener('click', (e) => {
console.log(e.target.innerText);
showList(4);// after create new element,need to add click event again
});
});
function showList(num) {
const taskBox = document.querySelector('.task-list');
let listHtml = "";
for(let i =1;i<=num;i++){
listHtml += `<li class="list-item">Hello ${i}</li>`;
}
taskBox.innerHTML = listHtml;
addClickEvent(); // add click event again
}
function addClickEvent(){
const listItem = document.querySelectorAll('.list-item');
listItem.forEach((item) => {
item.addEventListener('click', (e) => {
console.log(e.target.innerText);
showList(4);
});
});
}
showList(5);
<div class="task-list">
</div>
it's because listener is removed...
if I follow the steps
showList(5);
will render the list
after that
const listItem = document.querySelectorAll('.list-item');
listItem.forEach((item) => {
item.addEventListener('click', (e) => {
console.log(e.target.innerText);
showList(4);
});
});
this will add listener to each item. but if you click, it will call showList again and it will rerender the list and remove the listener..
so if you want to the listener is added every render, move it to the showList function
function showList(num) {
const taskBox = document.querySelector('.task-list');
let listHtml = "";
for(let i =1;i<=num;i++){
listHtml += `
<li class="list-item">Hello ${i}</li>
`;
}
taskBox.innerHTML = listHtml;
const listItem = document.querySelectorAll('.list-item');
listItem.forEach((item) => {
item.addEventListener('click', (e) => {
console.log(e.target.innerText);
showList(4);
});
});
}
Here's an implementation using #VLAZ's event delegation suggestion.
Put the click listener on the container and use the event to identify what was clicked.
function showList(num) {
const taskBox = document.querySelector('.task-list');
let listHtml = "";
for(let i =1;i<=num;i++){
listHtml += `<li class="list-item">Hello ${i}</li>`;
}
taskBox.innerHTML = listHtml;
}
// listen on the container and use the
// event to figure out what was clicked
// instead of adding a listener on every
// child.
document.querySelector('.task-list').addEventListener(
'click',
(e) => { console.log(e.target.innerText) }
)
showList(5);
<div class="task-list">
</div>

Check if element is focused with Vanilla JS, not jQuery

I have this big class Search, which controls my search bar on my website. Now, when a input is focused, i dont want my s key (which pops out the search bar) to execute when a input is focused. I tried with document.activeElement, but then, the search bar wont even open, whilst the input not being focused. You can see it, under keydown event listener, under Events comment
class Search {
// Describe and create object
constructor() {
this.openButton = document.querySelectorAll('.js-search-trigger');
this.closeButton = document.querySelector('#close-button');
this.searchOverlay = document.querySelector('.search-overlay');
this.searchField = document.getElementById('search-term');
this.typingTimer;
this.events();
this.isSpinnerVisible = false;
this.resultsDiv = document.getElementById('search-overlay__results');
this.previousValue;
console.log(this.openButton);
}
// Events
events() {
this.openButton.forEach(e => {
e.addEventListener('click', () => {
this.openOverlay();
document.body.classList.add('body-no-scroll');
});
})
this.closeButton.addEventListener('click', () => {
this.closeOverlay();
document.body.classList.remove('body-no-scroll');
})
document.addEventListener('keydown', (e) => {
if(e.key === 's' && !(this === document.activeElement)){
this.openOverlay();
document.body.classList.add('body-no-scroll');
console.log("s pressed")
}
if(e.key === 'Escape' && this.isOverlayOpen){
this.closeOverlay();
document.body.classList.remove('body-no-scroll');
console.log("esc pressed");
}
});
this.searchField.addEventListener('keyup', () => {
this.typingLogic();
})
}
// Methods
openOverlay(){
this.searchOverlay.classList.add('search-overlay--active');
this.isOverlayOpen = true;
}
closeOverlay(){
this.searchOverlay.classList.remove('search-overlay--active');
}
typingLogic(){
if(this.searchField.value != this.previousValue){
clearTimeout(this.typingTimer);
if(this.searchField.value){
if(!this.isSpinnerVisible){
this.resultsDiv.innerHTML = '<div class="spinner-loader"></div>';
this.isSpinnerVisible = true;
}
this.typingTimer = setTimeout(this.getResults(),2000);
}else{
this.resultsDiv.innerHTML = '';
this.isSpinnerVisible = false;
}
}
this.previousValue = this.searchField.value;
}
getResults(){
this.typingTimer = setTimeout(()=> {
this.resultsDiv.innerHTML = 'Some here';
this.isSpinnerVisible =false;
},2000)
}
}
export default Search
You can check tagName property of activeElement. And if it is not input then proceed with your code. Update your condition like below.
if(e.key === 's' && document.activeElement.tagName.toLowerCase() != 'input')

After drag & drop using HTML5 Api, one of two event listeners get removed from both dragged and the replaced item

I have a list of items that have two event listeners each, one for clicking on them and making them greyed out and one for the X button that deletes the item.
I have implemented drag and drop functionality using HTML5 api. After two items have switched positions the delete item event listener is removed while the grey event listener still works.
The delete item event listener
li.querySelector(".delete-todo-item").addEventListener("click", deletetodoEventListener)
function deletetodoEventListener(e) {
e.stopPropagation();
const toDo = this.parentElement;
const itemID = toDo.getAttribute("data-id")
todos = todos.filter(function (item) {
return item.timeCreated != itemID;
})
addToLocalStorage(todos);
toDo.remove()
emptyTodoList()
}
The "grey item out" event listener
function toDoEventListener(e) {
console.log("click listner ")
const itemID = this.getAttribute("data-id")
const itemImg = this.querySelector(".todo-image");
if (this.classList.contains("completed")) {
this.classList.remove("completed");
itemImg.src = "media/circle.svg";
todos.forEach(function (item) {
if (item.timeCreated == itemID) {
item.completed = false;
}
});
} else {
this.classList.add("completed");
itemImg.src = "media/check.svg";
todos.forEach(function (item) {
if (item.timeCreated == itemID) {
item.completed = "completed";
}
});
}
addToLocalStorage(todos);
}
The drag and drop functions
function handleDragStart(e) {
this.style.opacity = '0.2';
dragSrcEl = this;
e.dataTransfer.effectAllowed = 'move';
e.dataTransfer.setData('text/html', this.innerHTML);
console.log(e.dataTransfer)
}
function handleDrop(e) {
e.stopPropagation();
e.preventDefault();
const tempId = dragSrcEl.getAttribute("data-id");
const tempClassList = dragSrcEl.classList.value
dragSrcEl.className = this.classList.value;
dragSrcEl.setAttribute("data-id", this.getAttribute("data-id"))
this.className = tempClassList;
this.setAttribute("data-id", tempId)
if (dragSrcEl !== this) {
dragSrcEl.innerHTML = this.innerHTML;
this.innerHTML = e.dataTransfer.getData('text/html');
}
return false;
}
A todo item
<li class="todo-item" data-id="1634245326430" draggable="true">
<div class="todo-item-container">
<div class="img-container">
<img class="todo-image" src="media/circle.svg" alt="">
</div>
<span class="todo-item-content">test</span>
</div>
<span class="delete-todo-item">×</span>
</li>
Full code here: https://github.com/xhuljanoduli/todo-app
This is my first time posting, i always find a solution on my own but this time it is driving me crazy after countless hours of troubleshooting.
Any help will be much appreciated.

JavaScript function cannot recognise input.checked elment correctly

I have a problem with my "CheckCheck" function. The following part of the code should generate a to-do task. The input tag dynamically created in JS provides an option to set the priority to the given task. There is an option to set the task to "normal" or "priotity". However, the code sets the fisk task to "on" and after continues with the imposed "priority" and "normal" but inversely. How to prevent this from happening?
The code:
let tasklist = [];
function Apply() {
const Tasktask = document.querySelector(".task-form");
const Taskdate = document.querySelector(".date");
const Taskpriority = document.querySelector(".check-box");
function Prevent() {
if (Tasktask.value.length === 0 || Taskdate.value === "") {
alert("Fields cannot be empty!");
} else {
Pushed();
render();
clear();
}
}
Prevent();
function Pushed() {
let newTasks = new Tasks(Tasktask.value, Taskdate.value, Taskpriority.value);
tasklist.push(newTasks);
updateLocalStorage();
}
function render() {
CheckCheck();
insertTd();
}
function CheckCheck() {
if (Taskpriority.checked === true) {
Taskpriority.value = "priority"
} else {
Taskpriority.value = "normal"
}
}
function clear() {
Tasktask.value = "";
Taskdate.value = "";
Taskpriority.checked = false;
}
function insertTd() {
checkLocalStorage();
const parent2 = document.querySelector(".table-body");
parent2.innerHTML = "";
tasklist.forEach((item) => {
const table = `<tr>
<td>${item.task}</td>
<td>${item.date}</td>
<td>${item.priority}</td>
<td><a class="delete">delete</a></td>
</tr>`;
parent2.insertAdjacentHTML("afterbegin", table);
});
}
function deleteItem() {
const Table = document.querySelector("table").addEventListener("click", (e) => {
const currentTarget = e.target.parentNode.parentNode.childNodes[1];
if (e.target.innerHTML == "delete") {
if (confirm(`Are you sure you want to delete ${currentTarget.innerText}?`))
deleteTask(findTask(tasklist, currentTarget.innerText));
}
if (e.target.classList.contains("status-button")) {
findTask(tasklist, currentTarget.innerText);
}
updateLocalStorage();
insertTd();
});
}
deleteItem();
function deleteTask(currentTask) {
tasklist.splice(currentTask, currentTask + 1);
}
function findTask(taskArray, task) {
if (taskArray.length === 0 || taskArray === null) {
return;
}
for (let item of taskArray)
if (item.task === task) {
return taskArray.indexOf(item);
}
}
}
The other thing which is not working as intended is the confirm prompt. The more tasks I add, the more confirm prompts I get. I.e. for 1 task it is only one confirm window, for 3 tasks - 3 windows etc. Why is that?
I also attach below a JSFiddle link how better understanding.
Link
Thanks in advance for answers.
You don't get the state of a checkbox by reading its value but its checked property. Try document.querySelector('.check-box').checked
You keep reusing the same buttons and add an event listener to them each time. Either clone them every time, or add the listener once right after creating them.
Simple illustration of the problems here
document.querySelector('#readstates').addEventListener('click', e => {
e.preventDefault();
const disp = `Checked\n 1: ${chk1.checked}, 2: ${chk2.checked} \n`
+ `Value\n 1: ${chk1.value}, 2: ${chk2.value}`;
alert(disp);
});
const spawnBut = document.createElement('button');
spawnBut.id = 'spawned';
spawnBut.textContent = 'Spawned';
document.querySelector('#spawnDirty').addEventListener('click', e => {
const previous = document.querySelector('form #spawned');
if (previous) previous.remove();
document.querySelector('#spawnHere').append(spawnBut);
spawnBut.addEventListener('click', e => {
e.preventDefault();
alert('click!');
});
});
document.querySelector('#spawnClone').addEventListener('click', e => {
const previous = document.querySelector('form #spawned');
if (previous) previous.remove();
const nSpawnBut = spawnBut.cloneNode(true);
document.querySelector('#spawnHere').append(nSpawnBut);
nSpawnBut.addEventListener('click', e => {
e.preventDefault();
alert('click!');
});
});
<form>
<p class="inputs">
<label for="chk1">chk1:</label> <input type="checkbox" id="chk1" />
<label for="chk2">chk2:</label> <input type="checkbox" id="chk2" value="mycheckedvalue" />
<button id="readstates">Read chks</button>
</p>
<p class="button-spawners">
Try spamming those then click below:
<button type="button" id="spawnDirty"
title="Each time you click this one, the button below is respawned and a new handler is attached">
Spawn button
</button>
<button type="button" id="spawnClone"
title="This one will spawn a clone each time, so the click handler is attached only once">
Spawn button clone
</button>
</p>
<p id="spawnHere">
New button will spawn here
</p>
</form>

Issue moving list item from one ul element to another ul element

I'm trying to move individual li elements from one ul to another when a checkbox is selected.
Full code can be found here:http://jsfiddle.net/8f27L0q3/1/
My function that moves the li item can be found below.
ul.addEventListener('change', (e) => {
const checkbox = e.target;
const checked = checkbox.checked;
const listItem = e.target.parentNode.parentNode;
const completedItems =
document.querySelector('.completedItems');
const label = document.querySelector('.completedLabel');
if (checked) {
completedItems.appendChild(listItem);
label.style.display = 'none';
}
});
Once the li is moved to the other ul, the child span containing a label and checkbox disappear. This functionality works when the first child li moves but doesn't work when a li after the first child is moved. Also the first li's span disappears and therefore cannot be moved to the other ul
Looks like you are asking for the .completedLabel selector globally when you just need to search for it inside the item that was clicked.
May reducing the scope of the query selector to the element you are storing in listItem may work. Here is an example:
const label = listItem.querySelector('.completedLabel');
That way it works reusing your sample code:
//move li item to completed list when checkbox selected
ul.addEventListener('change', (e) => {
const checkbox = e.target;
const checked = checkbox.checked;
const listItem = e.target.parentNode.parentNode;
const completedItems = document.querySelector('.completedItems');
const label = listItem.querySelector('.completedLabel');
if (checked) {
completedItems.appendChild(listItem);
label.style.display = 'none';
}
});
However the implementation can be tweaked a little bit more.
would you mind reconsidering your strategy in solving this case? It is recommended to work with data such as arrays and objects instead of DOM nodes.
Please consider this example
const form = document.forms.form;
const todoList = document.querySelector('#todoList');
const completedList = document.querySelector('#completedList');
const tasks = [];
form.addEventListener('submit', handleSubmit, true);
todoList.addEventListener('change', handleInputChange, true);
function handleSubmit(event) {
event.preventDefault();
const task = this.task;
if (task.value === '') {
return;
}
const item = createTask(task.value);
tasks.push(item);
task.value = '';
syncTodoList();
}
function handleInputChange(event) {
const target = event.target;
if (target.nodeName === 'INPUT') {
const id = event.target.id;
const task = tasks.find(task => task.id === parseInt(id, 10));
task.status = 'completed';
syncTodoList();
syncCompletedList();
}
}
function createTask(task) {
return {
id: Date.now(),
text: task,
status: 'todo'
};
}
function syncTodoList() {
const todos = tasks
.filter(task => task.status === 'todo')
.map(task => `<li>${task.text} <input type="checkbox" id="${task.id}"></li>`)
.join('');
todoList.innerHTML = todos;
}
function syncCompletedList() {
const completeds = tasks
.filter(task => task.status === 'completed')
.map(task => `<li>${task.text}</li>`)
.join('');
completedList.innerHTML = completeds;
}
<form name="form">
<input id="task">
<button>Send</button>
</form>
<p>Todo</p>
<ul id="todoList"></ul>
<p>Completed</p>
<ul id="completedList"></ul>

Categories