Hello I've got a basic todo going but I am having an issue where if user clicks on a todo item as completed and then deletes it, the counter showing how many todos are left decreases twice. Is there a way to prevent this?
let todoCounter = 0;
counterDisplay = document.querySelector('#todo-counter');
counterDisplay.textContent = `${todoCounter} todos left`;
document.querySelector('#new-todo').addEventListener('keypress', e =>
{
text = e.target.value;
if (e.keyCode === 13 || e.which === 13)
{
addTodo(text);
todoCounter++;
counterDisplay.textContent = `${todoCounter} todos left`;
}
});
const addTodo = text =>
{
const todoEl = document.createElement('li');
const todoText = document.createTextNode(text);
const deleteButton = document.createElement('button');
todoEl.appendChild(todoText);
todoEl.appendChild(deleteButton);
const todoDom = document.querySelector('#todos');
todoDom.appendChild(todoEl);
};
document.querySelector('#todos').addEventListener('click', e =>
{
target = e.target;
if (!target.matches('button'))
{
if(target.matches('li'))
{
target.classList.toggle('completed');
if (target.classList.contains('completed'))
{
todoCounter--;
counterDisplay.textContent = `${todoCounter} todos left`;
}
else
{
todoCounter++;
counterDisplay.textContent = `${todoCounter} todos left`;
}
}
return;
}
target.parentNode.parentNode.removeChild(target.parentNode);
todoCounter--;
counterDisplay.textContent = `${todoCounter} todos left`;
})
https://jsfiddle.net/dk5690/aLn310y2/
The condition in your if statement when true triggers the .completed CSS Class and the todoCounter is either decremented or incremented (toggles as you click on the li tag), when it's decremented there's no (code) to check if the parent node is in the completed state, not to decrement the counter twice for the same todo when the delete button is clicked.
Here's the code fragment that you could add to ensure the completed state of the parent node (i.e the li tag) is checked. The fragment should be added immediately after your if statement closing tag, and the rest of the code after that deleted.
else if (target.matches('button') && target.parentNode.classList.contains('completed')) {
target.parentNode.parentNode.removeChild(target.parentNode);
}
else if (target.matches('button')) {
target.parentNode.parentNode.removeChild(target.parentNode);
todoCounter--;
counterDisplay.textContent = `${todoCounter} todos left`;
}
Below is the code refactored and running:
let todoCounter = 0;
counterDisplay = document.querySelector('#todo-counter');
counterDisplay.textContent = `${todoCounter} todos left`;
document.querySelector('#new-todo').addEventListener('keypress', e => {
text = e.target.value;
if (e.keyCode === 13 || e.which === 13) {
addTodo(text);
todoCounter++;
counterDisplay.textContent = `${todoCounter} todos left`;
document.getElementById('new-todo').value = '';
}
});
const addTodo = text => {
const todoEl = document.createElement('li');
const todoText = document.createTextNode(text);
let deleteButton = document.createElement('button');
// and give it some content
let deleteContent = document.createTextNode("Delete");
// add the text node to the newly created button
deleteButton.appendChild(deleteContent);
todoEl.appendChild(todoText);
todoEl.appendChild(deleteButton);
const todoDom = document.querySelector('#todos');
todoDom.appendChild(todoEl);
};
document.querySelector('#todos').addEventListener('click', e => {
target = e.target;
if (target.matches('li')) {
target.classList.toggle('completed');
if (target.classList.contains('completed')) {
todoCounter--;
counterDisplay.textContent = `${todoCounter} todos left`;
}
else {
todoCounter++;
counterDisplay.textContent = `${todoCounter} todos left`;
}
}
else if (target.matches('button') && target.parentNode.classList.contains('completed')) {
target.parentNode.parentNode.removeChild(target.parentNode);
}
else if (target.matches('button')) {
target.parentNode.parentNode.removeChild(target.parentNode);
todoCounter--;
counterDisplay.textContent = `${todoCounter} todos left`;
}
});
.completed {
text-decoration: line-through;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Todo</title>
</head>
<body>
<div id="todo-counter"></div>
<input type="text" id="new-todo"/>
<ul id="todos"></ul>
</body>
</html>
Related
document.addEventListener('DOMContentLoaded', function () {
// get button by id
// document.getElementById('closedjs_$').style.display='none';
var trips = document.querySelectorAll('*[id^="closedjs"]');
// trips = document.querySelectorAll('.closedjs')
trips.forEach((element) => {
element.style.display = 'none';
});
// checking if current passengers >= max passengers so changing Join btn to Closed
/*document.querySelectorAll('.joinleave').forEach((element) => {
var route_id = element.dataset.id;
var no_pass = parseFloat(document.querySelector(`#thepassengercounter_${route_id}`).innerHTML);
var max_pass = parseFloat(document.querySelector(`#max_pass_${route_id}`).innerHTML);
if (no_pass == max_pass) {
if (element.innerHTML = "Join") {
element.style.color = "#B22222";
element.style.border = "1px solid #B22222";
element.title = "Closed";
element.innerHTML = "Closed";
} else {
element.title = "Leave";
element.innerHTML = "Leave";
}
}
});*/
// so as to not reload the page after joining or leaving a trip. It updates the innerHTML instantly
//and then fetches as POST the route_id json page so as to update the DB
document.querySelectorAll('.joinleave').forEach((element) => {
element.onclick = () => {
var route_id = element.dataset.id;
fetch(`/route/${route_id}`)
.then((response) => response.json())
.then((route) => {
if (route.error) {
console.log(route.error);
alert(route.error);
}
var no_pass = parseFloat(
document.querySelector(`#thepassengercounter_${route_id}`).innerHTML
);
var max_pass = route['no_pass'];
var dist = route['dist'];
var key_num = route['key_num'];
var co2 = dist * 2.4 * 2;
var fin = document.querySelector(`#fin_${route_id}`).innerHTML;
var diff_p = max_pass - no_pass;
//console.log(route_id);
console.log('max passengers ', max_pass);
console.log('current passengers ', no_pass);
console.log('is date or time passed? ', fin);
console.log('diff pass ', diff_p);
alert('presssed');
//CODE EXECUTES UP TO HERE THE ALERT 'PRESSED' IS DISLAYED//
//console.log(dist)
//console.log(key_num)
// checking if current passengers >= max passengers so not allowing joinining but only leaving
if (no_pass < max_pass) {
if (diff_p == 1) {
if (element.title == 'Join') {
element.style.color = '#B22222';
element.style.border = '1px solid #B22222';
element.title = 'Leave';
element.innerHTML = 'Leave';
document.querySelector(`#thepassengercounter_${route_id}`)
.innerHTML++;
document.querySelector(`#closedjs_${route_id}`).style.display =
'block';
alert(
'Congrats you just saved ' +
co2.toFixed(2) +
' kg of CO2 by Carpooling'
);
join_route(route_id);
est_cost(route_id);
}
} else if (element.title == 'Leave') {
console.log(element);
element.title = 'Join';
element.innerHTML = 'Join';
element.style.color = '#228B22';
element.style.border = '1px solid #228B22';
document.querySelector(`#thepassengercounter_${route_id}`)
.innerHTML--;
leave_route(route_id);
est_cost(route_id);
}
} else if (no_pass == max_pass) {
if (fin === 'False') {
if (element.title == 'Leave') {
//console.log(element);
element.title = 'Join';
element.innerHTML = 'Join';
element.style.color = '#228B22';
element.style.border = '1px solid #228B22';
document.querySelector(`#thepassengercounter_${route_id}`)
.innerHTML--;
leave_route(route_id);
est_cost(route_id);
document.querySelector(`#closedjs_${route_id}`).style.display =
'none';
document.querySelector(`#closed_${route_id}`).style.display =
'none';
}
}
/* else {
if(element.title == "Closed")
{
alert('Sorry, max number of passengers reached');
element.style.color = "#B22222";
element.style.border = "1px solid #B22222";
element.title="Closed";
element.innerHTML="Closed";
//document.querySelector(`#thepassengercounter_${route_id}`).innerHTML++;
//join_route(route_id);
//est_cost(route_id);
}*/
/* else if (element.title == "Leave"){
console.log(element);
element.title="Join";
element.innerHTML="Join";
element.style.color = "#228B22";
element.style.border = "1px solid #228B22";
document.querySelector(`#thepassengercounter_${route_id}`).innerHTML--;
leave_route(route_id);
est_cost(route_id);
}*/
}
});
};
});
});
/* function sort() {
alert('sorting');
document.querySelectorAll('.media').forEach((element) => {
var route_id = element.dataset.id;
var fin = element.dataset.fin;
var fin_set = element.dataset.fin_set;
console.log(
route_id,
'style',
document.querySelector('.media').style.display
);
console.log(route_id, 'fin', fin);
console.log(route_id, 'fin_set', fin_set);
if (fin == 'True' || fin_set == 'True') {
document.querySelector('.media').style.display = 'none';
} else {
document.querySelector('.media').style.display = 'block';
}
});
} */
function join_route(route_id) {
fetch(`/join/route/${route_id}`, {
method: 'POST',
})
.then((response) => response.json())
.then((data) => console.log(data));
}
function leave_route(route_id) {
fetch(`/leave/route/${route_id}`, {
method: 'POST',
})
.then((response) => response.json())
.then((data) => console.log(data));
}
// Calculating the cost per passenger depending on the number of passengers instantly on the page (changing innerHTML)
// The updating of the DB is being done in models by the cost method separately
function est_cost(route_id) {
fetch(`/route/${route_id}`)
.then((response) => response.json())
.then((route) => {
if (route.error) {
console.log(route.error);
alert(route.error);
}
var origin = route['origin'];
var destination = route['destination'];
var init_cost = route['cost'];
var max_pass = route['no_pass'];
//console.log('the max passengers are', max_pass);
//console.log(init_cost);
var no_passengers = parseFloat(
document.querySelector(`#thepassengercounter_${route_id}`).innerHTML
);
//console.log(no_passengers);
if (no_passengers === 0) {
var cost = init_cost;
document.querySelector(`#costpp_${route_id}`).innerHTML = cost + '$';
} else {
var cost = init_cost / no_passengers;
document.querySelector(`#costpp_${route_id}`).innerHTML = cost + '$';
}
});
}
Code executes up to the point that PRESSED alert is displayed.
It doesn't execute the fetch nor the alert("Congrats you just saved "+(co2.toFixed(2))+' kg of CO2 by Carpooling').
No errors in console.
I can't understand how it can execute partially since it runs on the front end which means it has to do with the browser
Locally it runs smoothly.
What could be the prob?
It's a Django app.
Tabs how can I add next and prev buttons?
I'm have a tab component that needs the next and prev button as requirements.
I tried to build the component and now the extra bit is needed but I'm a bit stuck.
I'm not sure how to call the function and make it work within the existing one.
How do I add an extra click function for the buttons next and prev?
would be really useful to have it working. If anyone is able to put me on the right path, perhaps using a click even with some console.log in the right place?
class Tabs {
constructor() {
this.tabsBlocks = document.querySelectorAll(".tabs-block");
}
init() {
if (this.tabsBlocks.length > 0) {
Array.prototype.forEach.call(this.tabsBlocks, (tabBlock, index) => {
const tabContainer = tabBlock.querySelector(".tab-wrapper");
const tabs = tabBlock.querySelectorAll("button");
const panels = tabBlock.querySelectorAll(".panel");
const buttonNext = tabBlock.querySelector(".buttonNext");
const buttonPrev = tabBlock.querySelector(".buttonPrev");
tabContainer.setAttribute("role", "tablist");
Array.prototype.forEach.call(tabs, (tab) => {
if (tab.dataset.open === "true") this.toggleTabs(tab, panels);
tab.setAttribute("role", "tab");
tab.setAttribute(
"aria-controls",
`panel-${tab.dataset.target}-block-${index + 1}`
);
const associatedPanel = tabBlock.querySelector(
`[data-panel="${tab.dataset.target}"]`
);
if (associatedPanel !== null) {
associatedPanel.id = `panel-${tab.dataset.target}-block-${
index + 1
}`;
tab.id = `tab-${tab.dataset.target}-block-${index + 1}`;
}
tab.addEventListener("click", () => {
this.toggleTabs(tab, panels);
});
});
Array.prototype.forEach.call(panels, (panel) => {
const associatedTab = tabBlock.querySelector(
`[data-target="${panel.dataset.panel}"]`
);
panel.setAttribute("role", "tabpanel");
panel.setAttribute("aria-labelledby", `${associatedTab.id}`);
});
});
}
}
toggleTabs = (currentTab, panels, buttonNext, buttonPrev) => {
const tabs = currentTab.closest(".tabs-block").querySelectorAll("button");
const target = currentTab.dataset.target;
Array.prototype.forEach.call(tabs, (tab) => {
if (tab.dataset.target !== target) {
tab.classList.remove("is-active");
tab.setAttribute("aria-selected", "false");
}
});
Array.prototype.forEach.call(panels, (panel) => {
if (panel.dataset.panel !== target) {
panel.classList.remove("is-active");
} else {
panel.classList.add("is-active");
currentTab.classList.add("is-active");
currentTab.setAttribute("aria-selected", "true");
}
});
};
}
const components = {
Tabs: new Tabs()
};
components.Tabs.init();
.tabs-block .tab-wrapper li {
flex: 1 1 0%;
text-align: center;
}
.tabs-block .tab-wrapper li button {
font-weight: lighter;
font-size: 20px;
}
.tabs-block .tab-wrapper li button.is-active {
font-weight: normal;
}
.tabs-block .panel {
display: none;
}
.tabs-block .panel.is-active {
display: block;
}
<section class="tabs-block">
<ul class="tab-wrapper">
<li><button data-target="1" data-open="true">Tab title 1</button></li>
<li><button data-target="2">Tab title 2</button></li>
</ul>
<div class="panel-wrapper">
<div data-panel="1" class="panel">
<p>Panel 1 content</p>
</div>
<div data-panel="2" class="panel">
<p>Panel 2 content</p>
</div>
</div>
<button class="buttonNext"><< Prev</button>
<button class="buttonPrev">Next >></button>
</section>
This example works on codepen but does not in here.. Maybe babel preprozessor does something which i do not know...
Copy this to codepen and you will be happy.
class Pagination {
constructor(tabComponent, prevBtnId, nextBtnId) {
this.arrayUtils = new ArrayUtils()
this.tabComponent = tabComponent
this.prevBtn = document.getElementById(prevBtnId)
this.nextBtn = document.getElementById(nextBtnId)
// listen to tabComponents newly created Toggle event
// in which we wanna make sure to disable Btns or something..
this.tabComponent.onTabsToggle((tabs, tabIndex) => {
this.applyPaginationRules(tabs, tabIndex)
})
}
setDisabled(btn, value) {
if(value) {
btn.setAttribute('disabled', 'true')
} else {
btn.removeAttribute('disabled')
}
}
applyPaginationRules(tabs, newActiveIndex) {
const nextBtnDisabled = newActiveIndex === (tabs.length -1)
const prevBtnDisabled = newActiveIndex === 0
this.setDisabled(this.nextBtn, nextBtnDisabled)
this.setDisabled(this.prevBtn, prevBtnDisabled)
}
paginate(btn, action) {
const block = btn.closest('.tabs-block')
const panels = block.querySelectorAll('.panel')
const tabs = block.querySelectorAll('.tab-wrapper > li > button')
const activeIndex = Array.from(tabs)
.findIndex(t => t.getAttribute('data-open') === 'true')
if (tabs.length < 2) {
console.log('0 OR 1 tabs to toggle so no action.')
return
}
var newActiveIndex
if(action === 'next') {
newActiveIndex = this.arrayUtils.nextIndex(activeIndex, tabs)
}
else if(action === 'prev') {
newActiveIndex = this.arrayUtils.previousIndex(activeIndex, tabs)
}
else {
throw 'Invalid toggle action ' + action
}
// enable / disable next and previous btns..
this.applyPaginationRules(tabs, newActiveIndex)
this.tabComponent.toggleTabs(tabs[newActiveIndex], panels)
}
addPaginationListener(btn, action) {
btn.addEventListener('click', e => {
this.paginate(btn, action)
})
}
init() {
this.addPaginationListener(this.prevBtn, 'prev')
this.addPaginationListener(this.nextBtn, 'next')
// disable prev button on beggining since we start at 0..
this.setDisabled(this.prevBtn, true)
}
}
class ArrayUtils {
// getting next index in array
nextIndex(currentIndex, array) {
// if 0 OR 1 elements, index stays the same..
if(array.length < 2) return currentIndex
// if possible increment.
if(currentIndex < array.length -1) {
return currentIndex + 1
}
// if index would exceed array size go to start.
return 0
}
// getting previous INdex in array:
previousIndex(currentIndex, array) {
// if 0 OR 1 elements, index stays the same..
if(array.length < 2) return currentIndex
// if possible decrement!
if(currentIndex > 0) {
return currentIndex - 1
}
// start at the end of array when end is reached ofc.
return array.length -1
}
}
class Tabs {
constructor() {
this.tabsBlocks = document.querySelectorAll(".tabs-block");
this.onToggleHandlers = []
}
onTabsToggle(fn) {
this.onToggleHandlers.push(fn)
}
emitTabsToggle(tabs, tabIndex) {
this.onToggleHandlers.forEach(fn => fn(tabs, tabIndex))
}
init() {
if (this.tabsBlocks.length > 0) {
Array.prototype.forEach.call(this.tabsBlocks, (tabBlock, index) => {
const tabContainer = tabBlock.querySelector(".tab-wrapper");
const tabs = tabBlock.querySelectorAll(".tab-wrapper li button");
const panels = tabBlock.querySelectorAll(".panel");
tabContainer.setAttribute("role", "tablist");
Array.prototype.forEach.call(tabs, (tab, tabIndex) => {
if (tab.dataset.open === "true") this.toggleTabs(tab, panels);
tab.setAttribute("role", "tab");
tab.setAttribute(
"aria-controls",
`panel-${tab.dataset.target}-block-${index + 1}`
);
const associatedPanel = tabBlock.querySelector(
`[data-panel="${tab.dataset.target}"]`
);
if (associatedPanel !== null) {
associatedPanel.id = `panel-${tab.dataset.target}-block-${
index + 1
}`;
tab.id = `tab-${tab.dataset.target}-block-${index + 1}`;
}
tab.addEventListener("click", () => {
this.toggleTabs(tab, panels);
this.emitTabsToggle(tabs, tabIndex)
});
});
Array.prototype.forEach.call(panels, (panel) => {
const associatedTab = tabBlock.querySelector(
`[data-target="${panel.dataset.panel}"]`
);
panel.setAttribute("role", "tabpanel");
panel.setAttribute("aria-labelledby", `${associatedTab.id}`);
});
});
}
}
toggleTabs = (currentTab, panels) => {
const tabs = currentTab.closest(".tabs-block").querySelectorAll("button");
const target = currentTab.dataset.target;
Array.prototype.forEach.call(tabs, (tab) => {
if (tab.dataset.target !== target) {
tab.classList.remove("is-active");
tab.setAttribute('data-open', 'false')
tab.setAttribute("aria-selected", "false");
}
});
Array.prototype.forEach.call(panels, (panel) => {
if (panel.dataset.panel !== target) {
panel.classList.remove("is-active");
} else {
panel.classList.add("is-active");
}
});
/// activate tab..
currentTab.classList.add("is-active");
currentTab.setAttribute("data-open", 'true');
currentTab.setAttribute("aria-selected", "true");
};
}
const components = {
Tabs: new Tabs()
};
components.Tabs.init();
// have the pagination more decoupled from tabs!
// it uses tabs component but you can remove it OR apply it to other
// classes like so more easily..
const prevBtnId = 'pagination-prev'
const nextBtnId = 'pagination-next'
const pagination = new Pagination(components.Tabs, prevBtnId, nextBtnId)
pagination.init()
.tabs-block .tab-wrapper li {
flex: 1 1 0%;
text-align: center;
}
.tabs-block .tab-wrapper li button {
font-weight: lighter;
font-size: rem(20px);
}
.tabs-block .tab-wrapper li button.is-active {
font-weight: normal;
}
.tabs-block .panel {
display: none;
}
.tabs-block .panel.is-active {
display: block;
}
<section class="tabs-block">
<ul class="tab-wrapper">
<li><button data-target="1" data-open="true">Tab title 1</button></li>
<li><button data-target="2">Tab title 2</button></li>
<li><button data-target="3">Tab title 3</button></li>
<li><button data-target="4">Tab title 4</button></li>
</ul>
<div class="panel-wrapper">
<div data-panel="1" class="panel">
<p>Panel 1 content</p>
</div>
<div data-panel="2" class="panel">
<p>Panel 2 content</p>
</div>
<div data-panel="3" class="panel">
<p>Panel 3 content</p>
</div>
<div data-panel="4" class="panel">
<p>Panel 4 content</p>
</div>
</div>
<button class="buttonPrev" id="pagination-prev"><< Prev</button>
<button class="buttonNext" id="pagination-next">Next >></button>
</section>
You have to add eventListener to your button elements
buttonNext.addEventListener("click", myFunction);
buttonPrev.addEventListener("click", myFunction);
function myFunction() {
console.log("next")
}
Here are your Button Elements
<button id="nextBtn" class="buttonNext"><< Prev</button>
<button id="prevBtn" class="buttonPrev">Next >></button>
You have to get elements directly from DOM. use a unique class name or id for this purpose
const buttonNext = document.querySelector("#nextBtn");
const buttonPrev = document.querySelector("#prevBtn");
Now if the buttonNext and buttonPrev variables have the elements you can add eventListener. The event listener is of type click so whenever user clicks on any button the respective function will be called
buttonNext && buttonNext.addEventListener("click", handleNext);
buttonPrev && buttonPrev.addEventListener("click", handlePrev);
const handleNext = () => {
console.log("next")
}
const handlePrev = () => {
console.log("next")
}
I hope this will work for you. You can add any logic on next and prev buttons in respective functions
Working codepen demo
class Tabs {
constructor() {
this.tabsBlocks = document.querySelectorAll(".tabs-block");
}
init() {
if (this.tabsBlocks.length > 0) {
Array.prototype.forEach.call(this.tabsBlocks, (tabBlock, index) => {
const tabContainer = tabBlock.querySelector(".tab-wrapper");
const tabs = tabBlock.querySelectorAll("button");
const panels = tabBlock.querySelectorAll(".panel");
const Navigate= () => {
const buttonNext = document.querySelector("#nextBtn");
const buttonPrev = document.querySelector("#prevBtn");
const handleNext = () => {
console.log("next");
};
const handlePrev = () => {
console.log("prev");
};
buttonNext && buttonNext.addEventListener("click", handleNext);
buttonPrev && buttonPrev.addEventListener("click", handlePrev);
}
Navigate()
tabContainer.setAttribute("role", "tablist");
Array.prototype.forEach.call(tabs, (tab) => {
if (tab.dataset.open === "true") this.toggleTabs(tab, panels);
tab.setAttribute("role", "tab");
tab.setAttribute(
"aria-controls",
`panel-${tab.dataset.target}-block-${index + 1}`
);
const associatedPanel = tabBlock.querySelector(
`[data-panel="${tab.dataset.target}"]`
);
if (associatedPanel !== null) {
associatedPanel.id = `panel-${tab.dataset.target}-block-${
index + 1
}`;
tab.id = `tab-${tab.dataset.target}-block-${index + 1}`;
}
tab.addEventListener("click", () => {
this.toggleTabs(tab, panels);
});
});
Array.prototype.forEach.call(panels, (panel) => {
const associatedTab = tabBlock.querySelector(
`[data-target="${panel.dataset.panel}"]`
);
panel.setAttribute("role", "tabpanel");
panel.setAttribute("aria-labelledby", `${associatedTab.id}`);
});
});
}
}
toggleTabs = (currentTab, panels, buttonNext, buttonPrev) => {
const tabs = currentTab.closest(".tabs-block").querySelectorAll("button");
const target = currentTab.dataset.target;
Array.prototype.forEach.call(tabs, (tab) => {
if (tab.dataset.target !== target) {
tab.classList.remove("is-active");
tab.setAttribute("aria-selected", "false");
}
});
Array.prototype.forEach.call(panels, (panel) => {
if (panel.dataset.panel !== target) {
panel.classList.remove("is-active");
} else {
panel.classList.add("is-active");
currentTab.classList.add("is-active");
currentTab.setAttribute("aria-selected", "true");
}
});
};
}
const components = {
Tabs: new Tabs()
};
components.Tabs.init();
My local storage works in terms of saying true and false, but when the page is refreshed, it reverts but keeps the value. For example, if I switch from default darkmode (false) to lightmode (true), it shows as true in the local storage (which is good). However, when I refresh the page, although the value still is true, the page has reverted to its default (false).
HTML:
<body onload="stored()">
<label for="ID_HERE" class="toggle-switchy">
<input checked type="checkbox" name="server" id="ID_HERE" />
<span class="toggle" onclick="toggle()" id="saveDarkLight"></span>
<span class="switch"></span>
</label>
</body>
JS:
function stored() {
var storedValue = localStorage.getItem("server");
if (storedValue) {
lightmode()
document.getElementById("ID_HERE").checked = true;
} else {
darkmode()
document.getElementById("ID_HERE").checked = false;
}
}
function toggle() {
if (document.getElementById("ID_HERE").checked) {
lightmode()
var input = document.getElementById("ID_HERE");
localStorage.setItem("server", input.checked);
}
else {
darkmode()
var input = document.getElementById("ID_HERE");
localStorage.setItem("server", input.checked);
}
}
function darkmode() {
const bodyChanges = document.querySelectorAll('.margin_body');
for (let i = 0; i < bodyChanges.length; i++) {
bodyChanges[i].style.background = '#0c0a0f';
}
}
function lightmode() {
const bodyChanges = document.querySelectorAll('.margin_body');
for (let i = 0; i < bodyChanges.length; i++) {
bodyChanges[i].style.background = 'white';
}
}
localStorage only saves string values, so saving true or false will convert it to 'true' or 'false'.
When you read back, you need to either JSON.parse it or check against the string values.
function stored() {
const storedValue = localStorage.getItem("server");
if(storedValue === 'true'){ // <------
lightmode()
document.getElementById("ID_HERE").checked = true;
}else{
darkmode()
document.getElementById("ID_HERE").checked = false;
}
}
First and foremost, I think you'll want the onclick event handler on the input element.
Also, the following is a quick and dirty implementation:
JS:
const SAVED_USER_INPUT = "savedUserInput";
const TARGET_ID = "toggle-text";
const BLUE = "blue";
const RED = "red";
const text = {
[BLUE]:
"Text color currently set to blue (default/unchecked state). Click to change to red",
[RED]: "Text color currently set to red. Click to change to blue",
};
const handleToggle = (event) => {
const isChecked = event.target.checked;
const color = isChecked ? RED : BLUE;
setColor(color);
setText(color);
savePreference(color);
};
const setColor = (fontColor = BLUE) => {
document.getElementById(TARGET_ID).style.color = fontColor;
return true;
};
const setText = (color) => {
document.getElementById(TARGET_ID).innerText = text[color];
return true;
};
const setChecked = (color) => {
if (color === RED) {
document.getElementsByTagName("input")[0].setAttribute("checked", true);
}
};
const savePreference = (color) => localStorage.setItem(SAVED_USER_INPUT, color);
const getSavedPreference = () => localStorage.getItem(SAVED_USER_INPUT);
// localStorage.clear()
window.onload = () => {
if (!(getSavedPreference() === null)) {
const savedColor = getSavedPreference();
setColor(savedColor);
setText(savedColor);
setChecked(savedColor);
} else {
setColor();
setText(BLUE);
setChecked(BLUE);
}
};
HTML:
<!DOCTYPE html>
<html>
<head>
<title>localStorage</title>
<script src="app.js"></script>
</head>
<body>
<label>
<input type="checkbox" onclick="handleToggle(event)">
<span id="toggle-text"></span>
</span>
</label>
</body>
</html>
I used javascript, html, and css to create a usable todo list by moving tabs (project todo, personal todo). But it only works on the first tab, not the other tabs. (But it does animate the button being pressed.) I think it's a matter of order, but maybe not. PLEASE HELP ME !! Below is the code in javascript.
const todoObjectList1 = [];
const todoObjectList2 = [];
$(".menu1").click(function(){
$(this).addClass('on');
$(".menu2").removeClass("on");
})
$(".menu2").click(function(){
$(this).addClass('on');
$(".menu1").removeClass("on");
})
// project todo
class Todo_Class1 {
constructor(item){
this.ulElement1 =item;
}
add() {
const todoInput1 = document.querySelector("#myInput1").value;
if (todoInput1 == "") {
alert("You did not enter any item!")
} else {
const todoObject1 = {
id1 : todoObjectList1.length,
todoText1 : todoInput1,
isDone1 : false,
}
todoObjectList1.unshift(todoObject1);
this.display();
document.querySelector("#myInput1").value = '';
}
}
done_undone(x) {
const selectedTodoIndex1 = todoObjectList1.findIndex((item)=> item.id1 == x);
console.log(todoObjectList1[selectedTodoIndex1].isDone1);
todoObjectList1[selectedTodoIndex1].isDone1 == false ? todoObjectList1[selectedTodoIndex1].isDone1 = true : todoObjectList1[selectedTodoIndex1].isDone1 = false;
this.display();
}
deleteElement(z) {
const selectedDelIndex1 = todoObjectList1.findIndex((item)=> item.id1 == z);
todoObjectList1.splice(selectedDelIndex1,1);
this.display();
}
display() {
this.ulElement1.innerHTML = "";
todoObjectList1.forEach((object_item1) => {
const liElement1 = document.createElement("li");
const delBtn1 = document.createElement("i");
liElement1.innerText = object_item1.todoText1;
liElement1.setAttribute("data-id1", object_item1.id1);
delBtn1.setAttribute("data-id1", object_item1.id1);
delBtn1.classList.add("far", "fa-trash-alt");
liElement1.appendChild(delBtn1);
delBtn1.addEventListener("click", function(e) {
const deleteId1 = e.target.getAttribute("data-id1");
myTodoList1.deleteElement(deleteId1);
})
liElement1.addEventListener("click", function(e) {
const selectedId1 = e.target.getAttribute("data-id1");
myTodoList1.done_undone(selectedId1);
})
if (object_item1.isDone) {
liElement1.classList.add("checked");
}
this.ulElement1.appendChild(liElement1);
})
}
}
// personal todo
class Todo_Class2 {
constructor(item){
this.ulElement2 =item;
}
add() {
const todoInput2 = document.querySelector("#myInput2").value;
if (todoInput2 == "") {
alert("You did not enter any item!")
} else {
const todoObject2 = {
id2 : todoObjectList2.length,
todoText2 : todoInput2,
isDone2 : false,
}
todoObjectList1.unshift(todoObject2);
this.display();
document.querySelector("#myInput2").value = '';
}
}
done_undone(x) {
const selectedTodoIndex2 = todoObjectList2.findIndex((item)=> item.id2 == x);
console.log(todoObjectList2[selectedTodoIndex2].isDone2);
todoObjectList2[selectedTodoIndex2].isDone2 == false ? todoObjectList1[selectedTodoIndex2].isDone2 = true : todoObjectList2[selectedTodoIndex2].isDone2 = false;
this.display();
}
deleteElement(z) {
const selectedDelIndex2 = todoObjectList2.findIndex((item)=> item.id2 == z);
todoObjectList2.splice(selectedDelIndex2,1);
this.display();
}
display() {
this.ulElement2.innerHTML = "";
todoObjectList2.forEach((object_item2) => {
const liElement2 = document.createElement("li");
const delBtn2 = document.createElement("i");
liElement2.innerText = object_item2.todoText2;
liElement2.setAttribute("data-id2", object_item2.id2);
delBtn2.setAttribute("data-id2", object_item1.id2);
delBtn2.classList.add("far", "fa-trash-alt");
liElement2.appendChild(delBtn2);
delBtn2.addEventListener("click", function(e) {
const deleteId2 = e.target.getAttribute("data-id2");
myTodoList2.deleteElement(deleteId2);
})
liElement2.addEventListener("click", function(e) {
const selectedId2 = e.target.getAttribute("data-id2");
myTodoList1.done_undone(selectedId2);
})
if (object_item2.isDone) {
liElement2.classList.add("checked");
}
this.ulElement2.appendChild(liElement2);
})
}
}
////-----MAIN PROGRAM------------
const listSection1 = document.querySelector("#myUL1");
myTodoList1 = new Todo_Class1(listSection1);
// project todo add
document.querySelector(".addBtn1").addEventListener("click", function() {
myTodoList1.add()
})
document.querySelector("#myInput1").addEventListener("keydown", function(e) {
if (e.keyCode == 13) {
myTodoList1.add()
}
})
// personal todo add
const listSection2 = document.querySelector("#myUL2");
myTodoList2 = new Todo_Class2(listSection2);
document.querySelector(".addBtn2").addEventListener("click", function() {
myTodoList2.add()
})
document.querySelector("#myInput2").addEventListener("keydown", function(e) {
if (e.keyCode == 13) {
myTodoList2.add()
}
})
I just gave different javascript to different todo taps.
In other javascript, the existing personal part was removed and pasted.😂
I'm a little confused. So I have a JavaScript game build in JS, HTML5 canvas, and Firebase. The weird thing I'm experiencing is when I play a game there isn't a problem as it sends the information to the backend. But if I refresh it's as if the previous score still is present so when information gets send, it gets sent twice. If I refresh again then it's 3X etc. In the image, you can see the name and score are present from previous rounds.
Backend showing repetition
The block of code where the information gets sent is
showInput() {
document.getElementById("inputName").type = "text";
document.getElementById("inputName").addEventListener("keyup", e => {
e.preventDefault();
if (e.keyCode === 13) {
const scores = firebase.database().ref("scores/");
let name = document.getElementById("inputName").value;
let score = this.score;
let highScore = { name, score };
scores.push(highScore);
document.getElementById("inputName").type = "hidden";
this.showLeaderBoard();
}
});
}
But it is strange since when a new game gets created. The information in it, for example, the score says this.score = 0 and it shows that. Can someone explain why a previous score still persist?
Thanks for all your help and explanation.
** Remove Listener **
showInput() {
document.getElementById("inputName").type = "text";
document.getElementById("inputName").addEventListener("keyup", e => {
console.log("event added");
e.preventDefault();
if (e.keyCode === 13) {
const scores = firebase.database().ref("scores/");
let name = document.getElementById("inputName").value;
let score = this.score;
let highScore = { name, score };
scores.push(highScore);
document.getElementById("inputName").type = "hidden";
this.showLeaderBoard();
}
});
document.getElementById("inputName").removeEventListener("keyup", e => {
console.log("event removed");
e.preventDefault();
if (e.keyCode === 13) {
const scores = firebase.database().ref("scores/");
let name = document.getElementById("inputName").value;
let score = this.score;
let highScore = { name, score };
scores.push(highScore);
document.getElementById("inputName").type = "hidden";
this.showLeaderBoard();
}
});
}
** Reset Function **
const Game = require("./game");
const Background = require("./background");
var GameInstance = null;
document.addEventListener("DOMContentLoaded", () => {
let preGame = () => {
const canvasStart = document.getElementById("start");
if (canvasStart.getContext) {
const ctxStart = canvasStart.getContext("2d");
ctxStart.font = "30px games";
ctxStart.fillStyle = "red";
ctxStart.fillText(
"Press R to Start!",
canvasStart.width / 2 - 110,
canvasStart.height / 2
);
}
};
document.getElementById("inputName").addEventListener("keyup", e => {
e.preventDefault();
if (e.keyCode === 13 && GameInstance) {
const scores = firebase.database().ref("scores/");
let name = document.getElementById("inputName").value;
let score = GameInstance.score;
let highScore = { name, score };
scores.push(highScore);
document.getElementById("inputName").type = "hidden";
GameInstance.showLeaderBoard();
}
});
preGame();
document.addEventListener("keypress", e => {
if (
e.key === "r" &&
document.getElementById("inputName").type === "hidden"
) {
let score = 0;
const canvasStart = document.getElementById("start");
if (canvasStart.getContext) {
const ctxStart = canvasStart.getContext("2d");
ctxStart.clearRect(0, 0, canvasStart.width, canvasStart.height);
}
const canvas = document.getElementById("canvas");
const canvasEnemy = document.getElementById("enemy");
const canvasScore = document.getElementById("scoreBoard");
const canvasGameOver = document.getElementById("gameOver");
if (canvas.getContext) {
const ctx = canvas.getContext("2d");
const ctxEnemy = canvasEnemy.getContext("2d");
const ctxScore = canvasScore.getContext("2d");
const ctxGameOver = canvasGameOver.getContext("2d");
GameInstance = new Game(
ctx,
canvas,
ctxEnemy,
canvasEnemy,
ctxScore,
canvasScore,
ctxGameOver,
canvasGameOver,
score
).start();
}
}
});
Your code need to be slightely refactored as the way it is now is causing some very serious problems:
Each instance of the game creates its own event listener for the input. That will send more and more requests with each new instance.
The instances you think are destroyed are still alive. They are trapped in the closures created by the multiple event listeners. That is a major memory leak.
Remove the event listener from showInput function. Define it only once. It should just check the current game instance and sends its score to the server when an input happens:
var gameInstance = null;
document.getElementById("inputName").addEventListener("keyup", function(e) {
e.preventDefault();
if (e.keyCode === 13 && gameInstance) { // check if there is a game instance
const scores = firebase.database().ref("scores/");
let name = this.value; // this is the input now
let score = gameInstance.score; // use gameInstance
let highScore = { name, score };
scores.push(highScore);
this.type = "hidden"; // here too
gameInstance.showLeaderBoard(); // ...
}
});
document.addEventListener("keypress", e => {
// ...
gameInstance = new Game(/* ... */); // don't forget to store the gameInstance so the event listener will have access to it
// ...
});
showInput should look like this:
showInput() {
document.getElementById("inputName").type = "text";
}
Which just shows the input as it supposed to be.