I have a simple TODO app written in vanilla javascript. Here is the application:
Issue/Problem that I am having at this point is:
When I click New todo button the existing checked state of the checkbox disappears.
I am not sure how to persist the checkbox state after prompt window OK click. Please find the source code below.
const classNames = {
TODO_ITEM: 'todo-container',
TODO_CHECKBOX: 'todo-checkbox',
TODO_TEXT: 'todo-text',
TODO_DELETE: 'todo-delete',
}
const checkbox = document.createElement( "input" );
checkbox.type = "checkbox"
checkbox.id = classNames.TODO_CHECKBOX
const list = document.getElementById('todo-list')
const itemCountSpan = document.getElementById('item-count')
const uncheckedCountSpan = document.getElementById('unchecked-count')
function newTodo() {
let newTodo = prompt("Please enter a todo item");
if(newTodo){
itemCountSpan.innerHTML = parseInt(itemCountSpan.innerHTML) + 1
list.append(checkbox)
list.innerHTML += "<li>" + newTodo
}
let allCheckBoxes = document.querySelectorAll("input[id='todo-checkbox']");
uncheckedCountSpan.innerHTML = allCheckBoxes.length
console.log(allCheckBoxes.length)
for(let i = 0; i < allCheckBoxes.length; i++){
allCheckBoxes[i].onclick = function() {
if ( this.checked ) {
uncheckedCountSpan.innerHTML = parseInt(uncheckedCountSpan.innerHTML) - 1
}
else {
uncheckedCountSpan.innerHTML = parseInt(uncheckedCountSpan.innerHTML) + 1
}
};
}
}
Please let me know if you have any thoughts/directions.
Thanks in advance.
You have two issues: first, you're appending the same checkbox every time. Second, you are directly editing innerHTML, which is forcing the DOM to re-render everything, reverting the state of the inputs. Here's how I would do it:
const classNames = {
TODO_ITEM: 'todo-container',
TODO_CHECKBOX: 'todo-checkbox',
TODO_TEXT: 'todo-text',
TODO_DELETE: 'todo-delete',
}
const list = document.getElementById('todo-list')
const itemCountSpan = document.getElementById('item-count')
const uncheckedCountSpan = document.getElementById('unchecked-count')
function newTodo() {
let newTodo = prompt("Please enter a todo item");
if(!newTodo){
return
}
itemCountSpan.innerHTML = parseInt(itemCountSpan.innerHTML) + 1
uncheckedCountSpan.innerHTML = parseInt(uncheckedCountSpan.innerHTML) + 1
const checkbox = document.createElement( "input" );
checkbox.onclick = function() {
if ( this.checked ) {
uncheckedCountSpan.innerHTML = parseInt(uncheckedCountSpan.innerHTML) - 1
}
else {
uncheckedCountSpan.innerHTML = parseInt(uncheckedCountSpan.innerHTML) + 1
}
};
checkbox.type = "checkbox"
checkbox.class = classNames.TODO_CHECKBOX
list.append(checkbox)
const listItem = document.createElement("li")
listItem.innerHTML = newTodo
list.append(listItem)
}
Note that I also replaced the id of the checkbox with a class - there are multiple checkboxes, and ids should be unique.
Related
I have been learning javascript, i'm on my second project called "expense tracker" i have been able to use DOM and functions but could not execute right on the operators.
I have 2 input text the first is "item name" and the second is "price".
I have 2 buttons first is "add" and 2nd is "deduct".
The problem is Im so confused to create functions on the operators that able to get the total price and auto update the total whenever I add items.
I want to achieve:
When ever I add Item the current total price will be updated automatically.
When ever I deduct item the current total price will be updated automatically.
This the Javascript. I can able add and append items and price but dont know how to add and deduct with automatic price total.
const itemname = document.querySelector('#itemname');
const price = document.querySelector('#price');
//set requirements and limitations
const isRequired = value => value === '' ? false : true;
//set error trapping and message
const showError = (input, message) => {
const formgroup = input.parentElement;
formgroup.classList.remove('success');
formgroup.classList.add('error');
const error = formgroup.querySelector('small');
error.textContent = message;
}
const showSuccess = (input, message) => {
const formgroup = input.parentElement;
formgroup.classList.remove('error');
formgroup.classList.add('success');
const error = formgroup.querySelector('small');
error.textContent = message;
}
// Verifying fields if correct
const checkItemname = () =>{
let valid = false;
const itemnameTrim = itemname.value.trim();
const priceTrim = price.value.trim();
if(!isRequired(itemnameTrim)){
showError(itemname, "Pls type item name!");
}
else if(!isRequired(priceTrim)){
showError(price, "Pls type price!");
}
else{
showSuccess(itemname);
showSuccess(price);
addItem();
valid = true;
}
return valid;
}
const addItem = () => {
const itemnameTrim = itemname.value.trim();
const tableRowItem = document.createElement("tr");
const tableDataItem = document.createElement("td");
const tableTxtnode = document.createTextNode(itemnameTrim);
tableRowItem.appendChild(tableDataItem);
tableDataItem.appendChild(tableTxtnode);
document.getElementById("td-item").appendChild(tableRowItem);
const priceTrim = price.value.trim();
const tableDataPrice = document.createElement("td");
const tableTxtnodePrice = document.createTextNode(priceTrim);
tableRowItem.appendChild(tableDataPrice);
tableDataPrice.appendChild(tableTxtnodePrice);
document.getElementById("itemname").value = "";
document.getElementById("price").value = "";
}
//Deduct button
const deductItem = () => {
const itemnameTrim = itemname.value.trim();
const tableRowItem = document.createElement("tr");
const tableDataItem = document.createElement("td");
const tableTxtnode = document.createTextNode(itemnameTrim);
tableRowItem.className = 'redtext';
console.log(tableRowItem);
tableRowItem.appendChild(tableDataItem);
tableDataItem.appendChild(tableTxtnode);
document.getElementById("td-item").appendChild(tableRowItem);
const priceTrim = price.value.trim();
const tableDataPrice = document.createElement("td");
const tableTxtnodePrice = document.createTextNode(priceTrim);
tableRowItem.appendChild(tableDataPrice);
tableDataPrice.appendChild(tableTxtnodePrice);
document.getElementById("itemname").value = "";
document.getElementById("price").value = "";
}
//Call the buttons and evoke functions
const deductBtn = document.querySelector('#deductBtn')
const addBtn = document.querySelector('#addBtn')
addBtn.addEventListener("click", function(){
checkItemname()});
deductBtn.addEventListener("click", function(){
deductItem()});
$(document).ready(function(){
//iterate through each textboxes and add keyup
//handler to trigger sum event
$(".txt").each(function() {
$(this).keyup(function(){
calculateSum();
});
});
});
function calculateSum() {
var sum = 0;
//iterate through each textboxes and add the values
$(".txt").each(function() {
//add only if the value is number
if(!isNaN(this.value) && this.value.length!=0) {
sum += parseFloat(this.value);
}
});
//.toFixed() method will roundoff the final sum to 2 decimal places
$("#sum").html(sum.toFixed(2));
}
I'm creating a ToDo app in vanilla JavaScript.
My goal is to be able to remove the input data from local storage when the corresponding "X" button has been clicked.
So far, when you click on an X button, the corresponding input field and checkbox are removed from the DOM with this function I created -
function removeToDoInput(button, input1, input2, input3) {
if (allToDoInputs.length > 2) {
button.addEventListener("click", () => {
toDoInputContainer.removeChild(input1);
toDoInputContainer.removeChild(input2);
toDoInputContainer.removeChild(input3);
for (let toDoInput = 0; toDoInput < allToDoInputs.length; toDoInput++) {
for (let i = 0; i < localStorage.length; i++) {
localStorage.removeItem("ToDo " + toDoInput);
console.log("test");
}
}
})
}
}
This works fine. But like I mentioned I also need to remove the corresponding input data from local storage.
Here is the 'add-to-do' button functionality. You'll notice the removeToDoInput is called which I don't think is good -
function createToDoInput() {
const newToDoInputCheckbox = document.createElement("INPUT");
newToDoInputCheckbox.setAttribute("type", "checkbox");
const newToDoInput = document.createElement("INPUT");
const newXBtn = document.createElement("SPAN");
toDoInputContainer.appendChild(newToDoInputCheckbox);
toDoInputContainer.appendChild(newToDoInput);
toDoInputContainer.appendChild(newXBtn);
newToDoInputCheckbox.classList.add("checkbox");
newToDoInput.classList.add("to-do-input");
newXBtn.classList.add("X");
newXBtn.innerHTML = "X";
newXBtn.addEventListener("click", removeToDoInput(newXBtn, newToDoInputCheckbox, newToDoInput, newXBtn));
}
And here is the save button functionality -
function saveToDoInputs() {
localStorage.setItem("Title", toDoListTitle.value.trim());
for (let toDoInput = 0; toDoInput < allToDoInputs.length; toDoInput++) {
if (createToDoInput) {
localStorage.setItem("ToDo " + toDoInput, allToDoInputs[toDoInput].value.trim());
}
}
}
Both of these last functions I mentioned are attached to buttons through click event listeners.
How can I delete the input from local storage as well as the DOM?
This is incorrect and will be called immediately you assign the event handler
newXBtn.addEventListener("click", removeToDoInput(newXBtn, newToDoInputCheckbox, newToDoInput, newXBtn));
you need
newXBtn.addEventListener("click", function() { removeToDoInput(newXBtn, newToDoInputCheckbox, newToDoInput, newXBtn)});
or similar
I have redesigned your code and now it
wraps the elements in their own container
delegates click and change to the list container
use relative addressing (closest)
gives the todo a unique ID and stores it in an object
retrieves the object from local storage when loading
adds the object to localStorage when changed
I also added a select all and delete selected
NOTE I have commented the localStorage interaction out because stacksnippets do not allow access to localStorage. Just uncomment them on your page
let todos = {};
// const saveTodos = localStorage.getItem("ToDo");
// if (saveTodos) todos = JSON.parse(saveTodos);
const toDoInputContainer = document.getElementById("toDoInputContainer");
const toggleSelDel = () => {
document.getElementById("seldel").classList.toggle("hide",Object.keys(todos).length===0); // show if there are actual entries - we can toggle on existense of checkboxes instead
}
const saveAndToggle = (id,why) => {
// localStorage.setItem("ToDo",JSON.stringify(todos));
console.log(id, why);
toggleSelDel()
};
const saveTodo = (id, text) => {
todos[id] = text;
saveAndToggle(id,"added")
};
const removeTodo = id => {
if (todos[id]) {
delete todos[id];
}
saveAndToggle(id,"deleted"); // toggle anyway
};
function createToDoInput() {
const todoContainer = document.createElement("div");
todoContainer.id = 'todo' + new Date().getTime(); // unique ID
const newToDoInputCheckbox = document.createElement("INPUT");
newToDoInputCheckbox.setAttribute("type", "checkbox");
const newToDoInput = document.createElement("INPUT");
const newXBtn = document.createElement("SPAN");
todoContainer.appendChild(newToDoInputCheckbox);
todoContainer.appendChild(newToDoInput);
todoContainer.appendChild(newXBtn);
newToDoInputCheckbox.classList.add("checkbox");
newToDoInput.classList.add("to-do-input");
newXBtn.classList.add("X");
newXBtn.innerHTML = "X";
toDoInputContainer.appendChild(todoContainer);
}
toDoInputContainer.addEventListener("click", function(e) {
const tgt = e.target;
if (tgt.classList.contains("X")) {
const parent = tgt.closest("div")
removeTodo(parent.id);
parent.remove();
}
});
toDoInputContainer.addEventListener("change", function(e) {
const tgt = e.target;
if (tgt.classList.contains("to-do-input")) {
const id = tgt.closest("div").id;
if (tgt.value === "") removeTodo(id);
else saveTodo(id, tgt.value);
}
});
document.getElementById("add").addEventListener("click", createToDoInput);
toggleSelDel(); // possibly show the select all button
document.getElementById("selAll").addEventListener("click", function() {
const checked = this.checked;
[...toDoInputContainer.querySelectorAll("[type=checkbox]")].forEach(chk => chk.checked = checked)
});
document.getElementById("delAll").addEventListener("click", function() {
[...toDoInputContainer.querySelectorAll("[type=checkbox]:checked")].forEach(chk => {
const parent = chk.closest("div");
removeTodo(parent.id);
parent.remove();
});
});
.hide { display: none; }
<button id="add">Add</button>
<div id="seldel" class="hide"><label>Select all<input type="checkbox" id="selAll"/></label><button type="button" id="delAll">Delete selected</button></div>
<div id="toDoInputContainer"></div>
I’m a newbie started js like 2 weeks ago.
I have been making a to-do list.
There are key toDos & finished in localStorage.
I want the value in toDos to go to finished, getting class name “done” if I click the finBtn. Also it should move to the bottom.
At first it works. However, every time the page gets refreshed, the stuff in finished gets duplicated.
This problem’s been bothering me for a week.
Thank you for reading.
const toDoForm = document.querySelector(".js-toDoForm"),
toDoInput = toDoForm.querySelector("input"),
toDoList = document.querySelector(".js-toDoList");
const TODOS_LS = "toDos";
const FINISHED_LS = "finished";
const NOTSTART_CN = "notStart";
const DONE_CN = "done";
let toDos = [];
let toDosDone = [];
function saveToDos(){
localStorage.setItem(TODOS_LS, JSON.stringify(toDos));
}
function updateToDos(){
localStorage.setItem(FINISHED_LS, JSON.stringify(toDosDone));
}
function deleteToDo(event){
const btn = event.target;
const li = btn.parentNode;
toDoList.removeChild(li);
const cleanToDos = toDos.filter(function(toDo){
return toDo.id !== parseInt(li.id);
});
toDos = cleanToDos;
saveToDos();
}
function finish(event){
const btn = event.target;
const li = btn.parentNode;
const oldToDos = localStorage.getItem(TODOS_LS);
const parsedOldToDos = JSON.parse(oldToDos);
const btnNum = parseInt(li.id) - 1;
const finishedStuff = parsedOldToDos.splice(btnNum, 1);
finishedStuff[0].class = DONE_CN;
li.classList.add(DONE_CN);
toDos = parsedOldToDos;
toDosDone = finishedStuff;
saveToDos();
updateToDos();
}
function makeToDos(text){
const li = document.createElement("li");
const span = document.createElement("span");
const delBtn = document.createElement("button");
const finBtn = document.createElement("button");
const newId = toDos.length + 1;
delBtn.innerText="❌";
delBtn.classList.add("delBtn");
delBtn.addEventListener("click", deleteToDo);
finBtn.classList.add("finBtn");
finBtn.innerText = "✔";
finBtn.addEventListener("click", finish);
span.innerText = text;
li.id = newId;
li.appendChild(span);
li.appendChild(delBtn);
li.appendChild(finBtn);
toDoList.appendChild(li);
const toDoObj = {
text: text,
id: newId,
class:""
};
toDos.push(toDoObj);
saveToDos();
}
function handleSubmit(event){
event.preventDefault();
const currentValue = toDoInput.value;
makeToDos(currentValue);
toDoInput.value = "";
}
function loadToDos(){
const loadedToDos = localStorage.getItem(TODOS_LS);
const loadedFinToDos = localStorage.getItem(FINISHED_LS);
if(loadedToDos !== null || loadedFinToDos !== null){
const parsedToDos = JSON.parse(loadedToDos);
const parsedFinToDos = JSON.parse(loadedFinToDos);
parsedToDos.forEach(function(toDo){
makeToDos(toDo.text);
});
parsedFinToDos.forEach(function(done){
makeToDos(done.text);
});
} //else
} //ends of loadToDos
function init(){
loadToDos();
toDoForm.addEventListener("submit", handleSubmit);
}
init();
Your localStorage is not cleaning up on page refresh, you should do localStorage.clear(); Each time page load using
I'm trying to use local storage so that my invites stay on the page when refreshed. How would I go about implementing this into my code. I really don't know where to start and I'm really struggling with it. Please cans someone just show me how to implement this into my code. Ive been creating child elements and appending them to the UL in the HTML.
const form = document.getElementById("registrar");
const input = form.querySelector("input");
const mainDiv = document.querySelector(".main");
const ul = document.getElementById("invitedList");
const div = document.createElement('div');
const filterLabel = document.createElement('label');
const filterCheckBox = document.createElement('input');
filterLabel.textContent = "Hide those who havent responded";
filterCheckBox.type = 'checkbox';
div.appendChild(filterLabel);
div.appendChild(filterCheckBox);
mainDiv.insertBefore(div, ul);
/*
This creates a checkbox to see you has confirmed if they are coming
to the event or not.
*/
filterCheckBox.addEventListener("change", (e) => {
const isChecked = e.target.checked;
const lis = ul.children;
if (isChecked) {
for (let i = 0; i < lis.length; i += 1) {
let li = lis[i]
if (li.className === 'responded') {
li.style.display = '';
} else {
li.style.display = 'none';
}
}
} else {
for (let i = 0; i < lis.length; i += 1) {
let li = lis[i]
li.style.display = '';
}
}
});
/*
This function creates new list items (the invites).
*/
createLi = (text) => {
createElement = (elementName, property, value) => {
const element = document.createElement(elementName);
element[property] = value;
return element;
}
appendElement = (elementName, property, value) => {
const element = createElement(elementName, property, value);
li.appendChild(element);
return element;
}
const li = document.createElement("li");
appendElement("span", "textContent", text);
appendElement("label", "textContent", "Confirmed")
.appendChild(createElement("input", "type", "checkbox"));
appendElement("button", "textContent", "edit");
appendElement("button", "textContent", "remove");
return li;
}
}
form.addEventListener("submit", (event) => {
event.preventDefault();
const text = input.value;
input.value = "";
const li = createLi(text);
ul.appendChild(li);
});
ul.addEventListener("change", () => {
const checkbox = event.target;
const checked = checkbox.checked;
const listItem = checkbox.parentNode.parentNode;
if (checked) {
listItem.className = "responded";
} else {
listItem.className = "";
}
});
ul.addEventListener("click", (e) => {
if (e.target.tagName === 'BUTTON') {
const button = e.target;
const li = button.parentNode;
const ul = li.parentNode;
const action = button.textContent;
const nameActions = {
remove: () => {
ul.removeChild(li);
},
edit: () => {
const span = li.firstElementChild;
const input = document.createElement('input');
input.type = 'text';
input.value = span.textContent;
li.insertBefore(input, span);
li.removeChild(span);
button.textContent = 'Save';
},
Save: () => {
const input = li.firstElementChild;
const span = document.createElement('span');
span.textContent = input.value;
li.insertBefore(span, input);
li.removeChild(input);
button.textContent = 'edit';
}
};
nameActions[action]();
}
});
I only can give you an idea for this, if you are planning to save any big data to localStorage, you can save your data to string and remake it to JSON object!
Here's an exmaple for saving data set
var toSave = [ ];
var whatToSave = (Your invites data List, maybe a "text" var for your code?, organize
to array or loop for your "UL" tag)
for(var i=0;i<whatToSave.length;i++){
var obj = [];
obj[i] = {
text:whatToSave[i].text,
data1:whatToSave[i].data1,
data2:whatToSave[i].data2,
...
}
toSave.push(obj[i]);
}
var saveToString = JSON.stringify(toSave);
localStorage.setItem('invites', saveToString); //your invite data saved in local
storage
....
//after refreshing or when you need to use your saved data
var cameBack = JSON.parse(localStorage.getItem('invites'));
//this will return same data again
//and make function to make UL using retured data array
you don't really need to for loop and push if you already organized needed data to array, just wrote it to make you understand an idea to save data in localStorage. I don't know if this will help you but get an idea and implement it to your project :)
I have this function:
function test() {
var a = document.getElementById('test');
var b = document.createElement('input');
b.type = 'checkbox';
b.addEventListener( 'change', function() {
if(this.checked) {
//do something and save the state (checked)
} else {
//do something else and save the state(not checked)
}
}
and I want to save the state of the checkbox on localstorage from the appended checkbox, what is the best way to do it?
so if you are appending multiple checkboxes and you want to probably set all of their data separately you are going to need to have a different id for each checkbox so when you append a new one I would increment the ids
//function that appends new checkbox
var i = 0;
function appendCheckBox(){
i++
checkBoxId = 'checkbox' + i;
document.getElementById('checkbox-container').innerHTML = `<input type="checkbox" id="${checkBoxId}" onclick="handleCheckBoxClick(this)"></input>`;
}
//function to handle checkbox click you would probably want to make it check if
//the checkbox is already checked if it is set value to unchecked
function handleCheckBoxClick(ev){
var checkBoxId = ev.id;
localStorage.setItem(checkBoxId, 'checked')
}
You can use :
Setting storage :
localStorage.setItem('checkbox', b.checked);
Getting storage :
var checkVal=localStorage.getItem('checkbox');
You'll have to do a bit of work. localStorage doesn't work in a snippet here, so a working example of the example code can be found # this JSFiddle
localStorage.setItem("Checkboxes", "{}");
const showCurrentCheckboxStates = () =>
document.querySelector("pre").textContent = `Checkboxes saved state ${
JSON.stringify(JSON.parse(localStorage.getItem("Checkboxes")), null, " ")}`;
const saveCheckboxState = (val, id) => {
localStorage.setItem("Checkboxes",
JSON.stringify({
...JSON.parse(localStorage.getItem("Checkboxes")),
[`Checkbox ${id}`]: val })
);
showCurrentCheckboxStates();
};
const createCheckbox = id => {
let cb = document.createElement('input');
cb.type = 'checkbox';
cb.dataset.index = id;
cb.title = `Checkbox ${id}`;
document.body.appendChild(cb);
// save the initial state
saveCheckboxState(0, id);
};
document.addEventListener("click", evt =>
evt.target.dataset.index &&
saveCheckboxState(+evt.target.checked, evt.target.dataset.index)
);
for (let i = 1; i < 11; i += 1) {
createCheckbox(i);
}